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
Process(IEnumerable<IMyInterface> items)
and I filter for IAnotherInterface
inside this methodProcess(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.
Explicit is better than implicit.
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);