DEV Community

mohamed Tayel
mohamed Tayel

Posted on

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

Meta Description: 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 guide you in transforming C# code from a simple, rigid function to a more flexible, object-oriented approach. Each step builds upon the previous one, making the code easier to modify and extend over time.

Step 1: The Basic Example – Summing All Numbers

We’ll start with a simple function that sums up all the 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

Step-by-Step Explanation:

  1. int Sum(int[] numbers): This is a function that takes an array of integers (numbers) as input.
  2. int total = 0;: We initialize a variable (total) to hold the sum of the numbers.
  3. foreach (var number in numbers): We loop through each number in the array.
  4. total += number;: For each number, we add it to total.
  5. return total;: Finally, we return the total sum.

Problem: This function sums all numbers. If you want to sum only odd or even numbers, you’d have to modify it repeatedly, making it inflexible.

Step 2: Adding a Condition – Summing Odd Numbers

Let’s add a 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

Step-by-Step Explanation:

  1. int SumOddNumbers(int[] numbers): This function is similar to the previous one but focuses on summing odd numbers.
  2. if (number % 2 != 0): We check if a number is odd (remainder when divided by 2 is not 0).
  3. If it’s odd, we add it to total.
  4. return total;: We return the sum of only the odd numbers.

Problem: This solution is slightly better but still lacks flexibility. You’d have to create a new function for each condition (e.g., for even numbers).

Step 3: Making It Flexible with a Delegate

To make the code more flexible, we’ll use a delegate to determine which numbers to sum.

Example 3: Using a Delegate

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

Step-by-Step Explanation:

  1. Func<int, bool> selector: This is a delegate that takes an integer and returns a boolean. It acts as a filter for the numbers to sum.
  2. if (selector(number)): We call selector for each number. If it returns true, the number is added to total.
  3. Example Usage:
   int[] numbers = { 1, 2, 3, 4, 5 };

   int sumOfOdds = Sum(numbers, n => n % 2 != 0);  // Sums odd numbers
   int sumOfEvens = Sum(numbers, n => n % 2 == 0); // Sums even numbers
Enter fullscreen mode Exit fullscreen mode
  1. Explanation of Usage:
    • n => n % 2 != 0 is a lambda expression that checks if n is odd.
    • n => n % 2 == 0 is another lambda expression that checks if n is even.

Now, you can sum based on any condition without changing the Sum function.

Step 4: Refactoring to an Object-Oriented Approach

To fully embrace object-oriented programming (OOP), let’s separate the selection 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

Step-by-Step Explanation:

  1. Selector Class: This class encapsulates the selection logic.
  2. Func<int, bool> _criteria: A private field that stores the selection logic.
  3. IsSelected(int number): This method checks if a number meets the criteria.

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

Step-by-Step Explanation:

  1. NumberArray Class: This class encapsulates the array of numbers and the summing logic.
  2. Sum(Selector selector): Uses the Selector class to filter numbers before summing them.
  3. 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
  1. Explanation of Usage:
    • We create a NumberArray with an array of numbers.
    • We create two Selector objects: one for odd numbers and one for even numbers.
    • The Sum method uses the Selector to determine which numbers to add.

Step 5: Making It Extensible

With this OOP structure, adding new criteria becomes easy.

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

Step-by-Step Explanation:

  1. We create a new Selector that sums every other number.
  2. The Sum method uses this selector to filter the numbers.
  3. You can add different selectors without changing the existing classes.

Conclusion

By refactoring code step-by-step, we moved from a simple summing function to a flexible, object-oriented solution. This approach makes the code more adaptable, maintainable, and easier to extend as requirements change.

Each step was explained in detail to help you understand the benefits of transitioning to an object-oriented design.

Top comments (0)