C# 在涉及多态性的场景中,如何满足访问特定实现的需要?

C# 在涉及多态性的场景中,如何满足访问特定实现的需要?,c#,polymorphism,abstraction,visitor-pattern,C#,Polymorphism,Abstraction,Visitor Pattern,我偶然发现了这个我无法正确解决的问题。这里有一些解释 代码 我有以下产品类别: public abstract class Product { public int BaseParam {get;set;} } public class SpecificProductA : Product { public int ParamA {get;set;} } public class SpecificProductB : Product { public int Para

我偶然发现了这个我无法正确解决的问题。这里有一些解释

代码

我有以下产品类别:

public abstract class Product
{
    public int BaseParam {get;set;}
}

public class SpecificProductA : Product
{
    public int ParamA {get;set;}
}

public class SpecificProductB : Product
{
    public int ParamB {get;set;}
}
我有这些消费类:

public interface IConsumer
{
    void Consume(Product product);
}

public class ConcreteConsumerA : IConsumer
{
    public void Consume(Product product)
    {
        /* I need ParamA of SpecificProductA */
    }
}

public class ConcreteConsumerB : IConsumer
{
    public void Consume(Product product)
    {
        /* I need ParamB of SpecificProductB */
    }
}
问题

我需要IConsumer接口的具体实现来访问产品的特定部分。ConcreteConsumerA只能消费产品A,ConcreteConsumerB只能消费产品B。这打破了我对消费者和产品的良好抽象

解决方案1:铸造

可以做的第一件显而易见的事情是将产品实例转换为特定的产品。它可以工作,但并不理想,因为如果类型有任何错误,我依赖运行时抛出任何错误

解决方案2:打破产品类的继承

另一个解决方案是将产品继承性打破为如下内容:

public class Product
{
    public int BaseParam {get;set;}

    public SpecificProductA ProductA {get;set;}

    public SpecificProductB ProductB {get;set;}
}

public class SpecificProductA
{
    public int ParamA {get;set;}
}

public class SpecificProductB
{
    public int ParamB {get;set;}
}
    public interface IConsumer<TProduct> where TProduct: Product
    {
        void Consume(Product product);
    }

    public class ConcreteConsumerA : IConsumer<SpecificProductA>
    {
        public void Consume(SpecificProductA productA)
        {
            /* I now have access to ParamA of SpecificProductA */
        }
    }

    public class ConcreteConsumerB : IConsumer<SpecificProductB>
    {
        public void Consume(SpecificProductB productB)
        {
            /* I now have access to ParamA of SpecificProductB */
        }
    }
解决方案3:仿制药

我还可以使IConsumer界面通用如下:

public class Product
{
    public int BaseParam {get;set;}

    public SpecificProductA ProductA {get;set;}

    public SpecificProductB ProductB {get;set;}
}

public class SpecificProductA
{
    public int ParamA {get;set;}
}

public class SpecificProductB
{
    public int ParamB {get;set;}
}
    public interface IConsumer<TProduct> where TProduct: Product
    {
        void Consume(Product product);
    }

    public class ConcreteConsumerA : IConsumer<SpecificProductA>
    {
        public void Consume(SpecificProductA productA)
        {
            /* I now have access to ParamA of SpecificProductA */
        }
    }

    public class ConcreteConsumerB : IConsumer<SpecificProductB>
    {
        public void Consume(SpecificProductB productB)
        {
            /* I now have access to ParamA of SpecificProductB */
        }
    }
公共接口i消费者,其中TProduct:Product
{
无效消费(产品);
}
公共类消费者:IConsumer
{
公共无效消费(SpecificProductA productA)
{
/*我现在有权访问SpecificProductA的ParamA*/
}
}
公共类消费者B:IConsumer
{
公共无效消费(SpecificProductB productB)
{
/*我现在可以访问SpecificProductB的ParamA*/
}
}
然而,像癌症一样,这种通用接口现在正在扩散到整个程序中,这也不理想


我不确定这里出了什么问题,违反了哪条规则。也许这是一个需要改变的设计问题。是否有更好的解决方案可以解决此问题?

如果
具体消费者a
需要
特定配置a
来完成其工作,而不是任何
配置
实例,那么它应该接受
特定配置a
,而不是
配置
。接受任何类型的配置,然后在运行时调用方不知道您有未提供的需求时出错,这只是在询问bug

对于第二个解决方案,您可以创建一个配置对象,它只包含任何消费者都需要的所有信息,这样就不会给消费者提供缺少他们所需的配置对象。如果这对你来说完全可行,那就太好了。任何消费者都不可能拥有无效的对象;它总是很好用的


如果您不能统一对象,并且需要不同类型的特定实现,其中不同的使用者只能处理特定类型的配置,那么最终的解决方案是唯一的实际选择。当然,它可以确保您永远不会提供不正确类型的配置值。虽然可能需要更多的代码,而不仅仅是不让类型跟踪这些信息,但这并不意味着需要更多的工作。如果这些类型没有为您跟踪哪些消费者需要哪些类型的配置,那么您必须以某种方式跟踪它,如果您弄错了,而不是因为您的程序没有编译而立即弄清楚,直到测试中出现了不正确的情况,并且得到了无效的强制转换异常,您才会发现。如果这种情况不常见,而不是在所有情况下都会发生错误,导致您在测试中错过了它,并且只有在以后客户才能找到它,那么问题就更大了。

如果
ConcreteConsumerA
需要
特定配置a
来完成其工作,而不是任何
配置
实例,那么它应该接受
特定配置a
,而不是
配置
。接受任何类型的配置,然后在运行时调用方不知道您有未提供的需求时出错,这只是在询问bug

对于第二个解决方案,您可以创建一个配置对象,它只包含任何消费者都需要的所有信息,这样就不会给消费者提供缺少他们所需的配置对象。如果这对你来说完全可行,那就太好了。任何消费者都不可能拥有无效的对象;它总是很好用的


如果您不能统一对象,并且需要不同类型的特定实现,其中不同的使用者只能处理特定类型的配置,那么最终的解决方案是唯一的实际选择。当然,它可以确保您永远不会提供不正确类型的配置值。虽然可能需要更多的代码,而不仅仅是不让类型跟踪这些信息,但这并不意味着需要更多的工作。如果这些类型没有为您跟踪哪些消费者需要哪些类型的配置,那么您必须以某种方式跟踪它,如果您弄错了,而不是因为您的程序没有编译而立即弄清楚,直到测试中出现了不正确的情况,并且得到了无效的强制转换异常,您才会发现。如果这种情况不常见,而不是在所有情况下都会发生错误,导致您在测试中错过它,并且只有在以后客户才能发现它,那么问题就更大了。

如果您希望避免通用传播,您可以减轻选项1的运行时错误,让消费者知道他是否传递了正确的类型:

public interface IConsumer
{ 
    bool TryConsume(Product product);
}

public class ConcreteConsumerA : IConsumer
{
    public bool TryConsume(Product product)
    {
        if (product is SpecificProductA a)
        { 
            //consume a
            return true;
        }

        return false;
    }
}

如果您希望避免泛型传播,则可以减轻选项1的运行时错误
    public interface IConsumer
    {
        void Consume(Product product);

        ICommonObject Visit(IProductVisitor productVisitor);
    }

    public class ConcreteConsumerA : IConsumer
    {
        public void Consume(Product product)
        {
            /* The logic that needs for ParamA of SpecificProductA is now  
 pushed into the Visitor. */
            var productAVisitor = new SpecificProductAVisitor();
            ICommonInterface commonInterfaceWithParamA = product.GetCommonInterface(productAVisitor); 
        }
    }

    public class ConcreteConsumerB : IConsumer
    {
        public void Consume(Product product)
        {
        /* The logic that needs for ParamB of SpecificProductB is now  
 pushed into the Visitor. */
            var productBVisitor = new SpecificProductBVisitor();
            ICommonInterface commonInterfaceWithParamB = product.GetCommonInterface(productBVisitor); 
        }
    }