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
- the signature should be
Process(IEnumerable<IMyInterface> items)and I filter forIAnotherInterfaceinside this method - 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.
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.
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 processIMyInterfaceobjects. Such a client might be surprised, then, if it passes a collection ofIMyInterfaceobjects that do not also implementIAnotherInterfaceand then nothing happens. Surprising.On the other hand, the Robustness Principle (Postel's law) might argue that if
Processcan handleIMyInterfaceobjects, 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
Processmethod actually doesn't handle anyIMyInterfaceobjects - it just ignores them.Thus, without knowing more than that, it sounds as thought the API should be
void Process(IEnumerable<IAnotherInterface> items).Just use OfType:
If you really want to make the pipeline explicit, you could invert the arguments by introducing an (internal) extension method:
which would enable you to write the pipeline like this: