Search code examples
c#oopdesign-patternssolid-principlessingle-responsibility-principle

Filtering inside a processing method vs. filtering outside


In my application I am processing a List of IMyInterface instances. Not all, but some of them in addition also implement IAnotherInterface. Note that IAnotherInterface not derives from IMyInterface. Following the Single Responsibility Principle, I have a separate class that processes the IMyInterfaces instances via a Process method. Now I am struggling with the design choice whether

  1. the signature should be Process(IEnumerable<IMyInterface> items) and I filter for IAnotherInterface inside this method
  2. or the signature should be Process(IEnumerable<IAnotherInterface> items), meaning the filtering has to be done outside the processing method by the "client".

To make it more clear, these are the two code options I am struggling between:

// alternative 1:
List<MyInterface> items = GetItems(); // code not shown here
foreach(var item in items)
{
    // do some other processing before, not shown here

    // pass items to Process(IEnumerable<IMyInterface> items)
    myProcessor.Process(items);                
            
    // do some other processing afterwards, not shown here
}

// or alternative 2:
List<MyInterface> items = GetItems(); // code not shown here
foreach (var item in items)
{
    // do some other processing before, not shown here

    // pass items to Process(IEnumerable<IAnotherInterface> items)
    // -> need to filter first
    var filteredItems = filterForIAnotherInterface(items);
    myProcessor.Process(filteredItems);

    // do some other processing afterwards, not shown here
}

Is there any good reasoning for choosing one over the other? My own thoughts are that alternative 1 is easier to use for the client, but then the Process method has to do filtering which adds some kind of an additional responsibility besides its main responsibility. On the other hand, alternative 2 in some way makes the processing pipeline less readable I think.


Solution

  • There are no absolute rules to guide an API design decision like that. On the one hand, you may want to be as explicit as possible.

    Explicit is better than implicit.

    - The Zen of Python

    Another way to put this is to apply the Principle of Least Surprise. If you had a method with the signature void Process(IEnumerable<IMyInterface> items), client code would expect such a method to process IMyInterface objects. Such a client might be surprised, then, if it passes a collection of IMyInterface objects that do not also implement IAnotherInterface and then nothing happens. Surprising.

    On the other hand, the Robustness Principle (Postel's law) might argue that if Process can handle IMyInterface objects, then it should also accept them.


    Since I don't know more than what's in the OP, it doesn't sound as though Postel's law really applies here, since the Process method actually doesn't handle any IMyInterface objects - it just ignores them.

    Thus, without knowing more than that, it sounds as thought the API should be void Process(IEnumerable<IAnotherInterface> items).

    On the other hand, alternative 2 in some way makes the processing pipeline less readable I think.

    Just use OfType:

    myProcessor.Process(items.OfType<IAnotherInterface>());
    

    If you really want to make the pipeline explicit, you could invert the arguments by introducing an (internal) extension method:

    public static void ProcessWith(
        this IEnumerable<IAnotherInterface> items,
        SomeProcessor processor)
    {
        processor.Process(items);
    }
    

    which would enable you to write the pipeline like this:

    items.OfType<IAnotherInterface>().ProcessWith(myProcessor);