I have a set of interfaces/classes as follows. For the sake of simplicity imagine more properties, collections etc.
interface IMaster
{
//Some properties
}
interface IB : IMaster
{
string PropOnA { get; set }
}
interface IC : IMaster
{
string PropOnB { get; set }
}
class B : IB
class C : IC
...
These contracts were designed to store data(which is held in a slightly different format in each case). There is a lot of code that uses these contracts to get the data, format it, process it, write etc. We have developed an entire library that does not see the concrete implementations(B,C) of any of these contracts by inverting control and allow the user to use our 'default implementations' for each contract or just loading in their own. We have registry where the user can register a different implementation.
To this end I have implemented a kind of strategy pattern where there exists a strategy for each contract type based on the task at hand. For the sake of simplicity lets say the task is writing, in reality it is much more complicated.
interface IWriteStrategy
{
public Write(IMaster thing);
}
class WriterA : IWriteStrategy
class WriterB : IWriteStrategy
etc
The above concrete strategies are also never 'seen' in our library, the client must register their own implementation or our default version.
I am not liking the cast in every strategy that is now necessary.
public classWriterA : IWriteStrategy
{
public void Write(IMaster thing)
{
if(thing is IA thingA)
//do some work
}
}
public classWriterB : IWriteStrategy
{
public void Write(IMaster thing)
{
if(thing is IB thingB)
//do some work
}
}
What we want to do is be able to loop through a list of IMaster
objects and run some operations.
foreach(var thing in Things)
{
var strategy = GetStrategy(thing.GetType()); //this gets the strategy object from our `registry` if one exists
strategy.Execute(thing);
}
The above design allows this but there seems to be a flaw which I cant for the life of me spot a solution to. We have to cast to the specific interface within each strategy implementation.
I have tried with generics, but just cant seem to nail it.
What would be a better way of designing this to avoid the cast but still be able to loop through a list of IMaster
things and treat them the same? Or is the cast absolutely necessary here?
I am trying to follow a SOLID design but feel the cast is messing with this as the client implementing the strategies will have to do the cast in order to get anything to work within the Write
method.
[Edit]
I have updated the classes implementing the IWriteStrategy
.
If you rarely add new IMaster
specializations, but often add new operations OR need to make sure operation providers (e.g writer) needs to support ALL specializations then the Visitor Pattern is a perfect fit.
Otherwise you basically need some kind of service locator & registration protocol to map operation providers/strategies to IMaster
specializations.
One way you could do it is define generic interfaces such as IMasterWriter<T> where T:IMaster
which can then be implemented like IBWriter : IMasterWriter<IB>
which defines the mapping.
From that point you only need a mechanism that uses reflection to find a specific IMasterWriter
implementor for a given type of IMaster
and decide what to do if it's missing. You could scan assemblies early to detect missing implementations at boot rather than failing later too.