Skip to content

Instantly share code, notes, and snippets.

@dietergoetelen
Last active April 9, 2022 20:31
Show Gist options
  • Save dietergoetelen/576fdbb1b170e327510f429fd34f1977 to your computer and use it in GitHub Desktop.
Save dietergoetelen/576fdbb1b170e327510f429fd34f1977 to your computer and use it in GitHub Desktop.
SOLID

Dependency inversion control

One should "depend upon abstractions, [not] concretions.

The most basic example is the following, imagine the following implementation, while LINQ does not exist:

/**
 * Filter out all 0 items
 */
public IEnumerable<int> Filter(IEnumerable<int> array) 
{
  var result = new List<int>();

  foreach(var item in array)
  {
    if (item != 0) 
    {
      result.Add(item);
    }
  }

  return result;
}

Next the customer requires an additional requirement, it should also be possible to filter out items less than zero.

public class FilterOptions
{
  public boolean FilterZero { get; set; } = true;
  public boolean FilterLessThanZero { get; set; } = false;
}

public IEnumerable<int> Filter(IEnumerable<int> array, FilterOptions options) 
{
  var result = new List<int>();

  foreach(var item in array)
  {
    if (
      (item != 0 && options.FilterZero) || (item < 0 && options.FilterLessThanZero)
    ) 
    {
      result.Add(item);
    }
  }

  return result;
}

As you may noticed the implementation is getting more and more complicated based on the new requirements. To simplify this, one can just invert the control of filtering to higher order functions:

public IEnumerable<int> FilterWithOptions(IEnumerable<int> array, FilterOptions options)
{
    var filtersToApply = new List<Func<IEnumerable<int>, IEnumerable<int>>>();

    if (options.FilterGreaterThanTen)
    {
        filtersToApply.Add(FilterGreaterThanTen);
    }

    if (options.FilterLessThanZero)
    {
        filtersToApply.Add(FilterLessThanZero);
    }

    if (options.FilterZero)
    {
        filtersToApply.Add(FilterZero);
    }

    return FilterPipe(filtersToApply);
}

public IEnumerable<int> FilterGreaterThanTen(IEnumerable<int> array)
{
    return Filter(array, num => num > 10);
}

public IEnumerable<int> FilterLessThanZero(IEnumerable<int> array)
{
    return Filter(array, num => num < 0);
}

public IEnumerable<int> FilterZero(IEnumerable<int> array)
{
    return Filter(array, num => num == 0);
}

private IEnumerable<int> Filter(IEnumerable<int> array, Func<int, bool> predicate)
{
    var result = new List<int>();

    foreach (var item in array)
    {
        if (predicate(item))
        {
            result.Add(item);
        }
    }

    return result;
}

private IEnumerable<int> FilterPipe(IEnumerable<Func<IEnumerable<int>, IEnumerable<int>>> functions)
{
    return functions.Aggregate(new List<int>(), (ints, func) => func(ints).ToList());
}

Liskov substitution principle

"Objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program."

public interface IPerson
{
  bool IsPartying { get; }
  void Party();
}

public class Woman : IPerson
{
  public bool IsPartying { get; private set; }

  public void Party()
  {
    IsPartying = true;
  }
}

public class Man : IPerson
{
  public bool IsPartying { get; private set; }
  public bool IsDrunk { get; set; }

  public void Party()
  {
    if (IsDrunk) 
    {
      IsPartying = true;
    }
  }
}

The above breaks the Liskov rule, because:

public class Party
{
  public void Start(IEnumerable<Person> people)
  {
    foreach(var person in people)
    {
      // what if Person = Man?
      // Will he party? --> nope, he has to be drunk first!
      person.Party();
    }
  }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment