devtoka
devtoka

Reputation: 446

How to avoid casting to a specific interface

Background info

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.

Design flaw??

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.

Question

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.

Upvotes: 3

Views: 701

Answers (3)

plalx
plalx

Reputation: 43718

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.

Upvotes: 2

Ive
Ive

Reputation: 1331

what about this, you will have all your casts in one strategy factory method:

 public interface IWriterStrategy
{
    void Execute();
}

public class WriterA : IWriterStrategy
{
    private readonly IA _thing;

    public WriterA(IA thing)
    {
        _thing = thing;
    }
    public void Execute()
    {
        Console.WriteLine(_thing.PropOnA);
    }
}

public class WriterB : IWriterStrategy
{
    private readonly IB _thing;
    public WriterB(IB thing)
    {
        _thing = thing;
    }
    public void Execute()
    {
        Console.WriteLine(_thing.PropOnB);
    }
}



public static class WriterFactory
{
    public static List<(Type Master, Type Writer)> RegisteredWriters = new List<(Type Master, Type Writer)>
        {
           (typeof(IA),  typeof(WriterA)),
           (typeof(IB),  typeof(WriterB))
        };
    public static IWriterStrategy GetStrategy(IMaster thing)
    {
        (Type Master, Type Writer) writerTypeItem = RegisteredWriters.Find(x => x.Master.IsAssignableFrom(thing.GetType()));
        if (writerTypeItem.Master != null)
        {
            return (IWriterStrategy)Activator.CreateInstance(writerTypeItem.Writer, thing);
        }
        throw new Exception("Strategy not found!");
    }
}

public interface IMaster
{
    //Some properties
}

public interface IA : IMaster
{
    string PropOnA { get; set; }
}

public interface IB : IMaster
{
    string PropOnB { get; set; }
}

public interface IC : IMaster
{
    string PropOnC { get; set; }
}

public class ThingB : IB
{
    public string PropOnB { get => "IB"; set => throw new NotImplementedException(); }
}

public class ThingA : IA
{
    public string PropOnA { get => "IA"; set => throw new NotImplementedException(); }
}

public class ThingC : IC
{
    public string PropOnC { get => "IC"; set => throw new NotImplementedException(); }
}

internal static class Program
{
    private static void Main(string[] args)
    {
        var things = new List<IMaster> { 
            new ThingA(),
            new ThingB()//,
            //new ThingC()
        };



        foreach (var thing in things)
        { 
            var strategy = WriterFactory.GetStrategy(thing);  //this gets the strategy object from our `registry` if one exists
            strategy.Execute();
        }
    }
}

Upvotes: 1

StepUp
StepUp

Reputation: 38094

Maybe it is appropriate to use Strategy pattern and just give an implementation and execute it. Let me show an example.

interface IMaster
{
    void ExecuteMaster();
}


class MasterOne : IMaster
{
    public void ExecuteMaster()
    {
        Console.WriteLine("Master One");
    }
}


class MasterTwo : IMaster
{
    public void ExecuteMaster()
    {
        Console.WriteLine("Master Two");
    }
}

and

interface IWriteStrategy
{
    void Write(IMaster thing);
}

class WriterA : IWriteStrategy
{
    public void Write(IMaster thing)
    {
        Console.WriteLine("Writer A");
        thing.ExecuteMaster();
    }
}

class WriterB : IWriteStrategy
{
    public void Write(IMaster thing)
    {
        Console.WriteLine("Writer B");
        thing.ExecuteMaster();
    }
}

and code to execute:

static void Main(string[] args)
{
    List<IWriteStrategy> writeStrategies = new List<IWriteStrategy>()
    {
        new WriterA(),
        new WriterB()
    };
    List<IMaster> executes = new List<IMaster>()
    {
         new MasterOne(),
         new MasterTwo()
    };

    for (int i = 0; i < writeStrategies.Count(); i++)
    {
         writeStrategies[i].Write(executes[i]);
    }
}

Upvotes: 1

Related Questions