如何避免投射到特定界面

How to avoid casting to a specific interface

背景资料

我有一组interfaces/classes如下。为了简单起见,想象更多的属性、集合等。

interface IMaster
{
//Some properties
}

interface IB : IMaster
{
    string PropOnA { get; set }
}
interface IC : IMaster
{
    string PropOnB { get; set }
}

class B : IB
class C : IC
...

这些合约旨在存储数据(每种情况下数据的保存格式略有不同)。有很多代码使用这些契约来获取数据、格式化、处理、写入等。 我们开发了一个完整的库,它通过反转控制看不到任何这些合约的具体实现(B,C),并允许用户对每个合约使用我们的 'default implementations' 或只是加载他们自己的合约。我们有注册表,用户可以在其中注册不同的实现。

为此,我实施了一种策略模式,其中存在基于手头任务的每种合同类型的策略。为了简单起见,假设任务是写作,实际上它要复杂得多。

interface IWriteStrategy
{
    public Write(IMaster thing);
}

class WriterA : IWriteStrategy

class WriterB : IWriteStrategy

etc

以上具体策略也从未在我们的库中'seen',客户必须注册他们自己的实现或我们的默认版本。

设计缺陷??

我不喜欢现在需要的每个策略中的演员表。

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
    }
}

我们想要做的是能够遍历 IMaster 个对象的列表和 运行 一些操作。

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

上面的设计允许这样做,但似乎有一个缺陷,我终其一生都找不到解决方案。我们必须转换到每个策略实现中的特定接口。

我尝试过仿制药,但似乎无法解决问题。

问题

有什么更好的设计方法可以避免强制转换,但仍然能够循环遍历 IMaster 事物的列表并一视同仁?还是这里绝对需要强制转换?

我正在尝试遵循 SOLID 设计,但感觉转换与此混淆,因为实施策略的客户必须进行转换才能使任何东西在 Write 方法中工作。

[编辑] 我已经更新了实施 IWriteStrategy.

的 类

也许使用策略模式是合适的,只需给出一个实现并执行它。让我举个例子。

interface IMaster
{
    void ExecuteMaster();
}


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


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

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();
    }
}

和要执行的代码:

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]);
    }
}

怎么样,您将在一个策略工厂方法中进行所有转换:

 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();
        }
    }
}

如果您很少添加新的 IMaster 专业化,但经常添加新的操作或需要确保操作提供者(例如编写器)需要支持所有专业化,那么 Visitor Pattern 是一个完美的选择。

否则,您基本上需要某种服务定位器和注册协议来将操作 providers/strategies 映射到 IMaster 专业化。

您可以这样做的一种方法是定义通用接口,例如 IMasterWriter<T> where T:IMaster,然后可以像定义映射的 IBWriter : IMasterWriter<IB> 那样实现。

从那时起,您只需要一种机制,该机制使用反射为给定类型的 IMaster 查找特定的 IMasterWriter 实现程序,并决定在它丢失时要做什么。您可以及早扫描程序集以检测启动时缺少的实现,而不是稍后也失败。