DEV Community

mohamed Tayel
mohamed Tayel

Posted on

Flexible C# with OOP Principles: Moving from Static Functions to Flexible Object-Oriented

Meta Descripation:
Learn how to refactor C# code step-by-step, transitioning from static functions to a flexible, object-oriented design. Discover practical examples that showcase the benefits of applying OOP principles for better code flexibility, maintainability, and extensibility.

Introduction

This article aims to explain how to make C# code more object-oriented by starting with simple examples and gradually moving to more complex ones. We’ll start by writing basic code and refactoring it step by step to make it flexible and maintainable.

Step 1: The Basic Example – Summing All Numbers

Let’s start with a basic function that sums up all numbers in an array.

Example 1: Summing All Numbers

int Sum(int[] numbers)
{
    int total = 0;
    foreach (var number in numbers)
    {
        total += number;
    }
    return total;
}
Enter fullscreen mode Exit fullscreen mode

Explanation: This function simply loops through the array and adds all the numbers together. It's basic but lacks flexibility.

Problem:

What if we only want to sum up odd numbers? Modifying this function to include conditions will make the code messy.

Step 2: Adding a Simple Condition – Summing Odd Numbers

We add a basic condition to sum only odd numbers.

Example 2: Summing Odd Numbers

int SumOddNumbers(int[] numbers)
{
    int total = 0;
    foreach (var number in numbers)
    {
        if (number % 2 != 0)
        {
            total += number;
        }
    }
    return total;
}
Enter fullscreen mode Exit fullscreen mode

Explanation: Here, we only sum numbers that are odd. However, if requirements change again (e.g., sum even numbers), we have to modify the function each time, making it inflexible.

Step 3: Introducing a More Flexible Solution – Using a Function Delegate

Let’s make our code more flexible by allowing the user to pass a selection criterion.

Example 3: Using a Delegate for Selection

int Sum(int[] numbers, Func<int, bool> selector)
{
    int total = 0;
    foreach (var number in numbers)
    {
        if (selector(number))
        {
            total += number;
        }
    }
    return total;
}
Enter fullscreen mode Exit fullscreen mode

Example Usage:

int[] numbers = { 1, 2, 3, 4, 5 };

int sumOfOdds = Sum(numbers, n => n % 2 != 0);  // Sum only odd numbers
int sumOfEvens = Sum(numbers, n => n % 2 == 0); // Sum only even numbers
Enter fullscreen mode Exit fullscreen mode

Explanation: The function now uses a delegate (Func<int, bool>) to decide which numbers to include in the sum. This makes the code flexible—you can easily change the selection criteria without modifying the core logic.

Step 4: Refactoring to an Object-Oriented Approach

To fully embrace OOP, we need to encapsulate the logic into classes.

Example 4: Creating a Selector Class

public class Selector
{
    private readonly Func<int, bool> _criteria;

    public Selector(Func<int, bool> criteria)
    {
        _criteria = criteria;
    }

    public bool IsSelected(int number)
    {
        return _criteria(number);
    }
}
Enter fullscreen mode Exit fullscreen mode

Example 5: Refactoring to a NumberArray Class

public class NumberArray
{
    private readonly int[] _numbers;

    public NumberArray(int[] numbers)
    {
        _numbers = numbers;
    }

    public int Sum(Selector selector)
    {
        int total = 0;
        foreach (var number in _numbers)
        {
            if (selector.IsSelected(number))
            {
                total += number;
            }
        }
        return total;
    }
}
Enter fullscreen mode Exit fullscreen mode

Example Usage:

var numbers = new NumberArray(new[] { 1, 2, 3, 4, 5 });

var oddSelector = new Selector(n => n % 2 != 0);
var sumOfOdds = numbers.Sum(oddSelector);  // Output: 9

var evenSelector = new Selector(n => n % 2 == 0);
var sumOfEvens = numbers.Sum(evenSelector);  // Output: 6
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • NumberArray encapsulates the summing logic.
  • Selector encapsulates the selection criteria.
  • The solution is now more flexible, maintainable, and aligned with OOP principles.

Step 5: Making It Extensible

The OOP approach allows you to easily extend the code. For example, you can add new selectors without modifying existing classes.

Example 6: Adding a New Selector

var everyOtherSelector = new Selector((n, index) => index % 2 == 0);
var sumOfEveryOther = numbers.Sum(everyOtherSelector);  // Output: 6
Enter fullscreen mode Exit fullscreen mode

Conclusion

By refactoring code step by step, we’ve moved from a simple, rigid function to a flexible, object-oriented solution. Using classes and delegates makes the code easier to extend, test, and maintain.

This approach should help you understand how OOP adds value to your code and makes it adaptable to changing requirements.

Top comments (0)