在涉及多态的场景下,如何解决访问具体实现的需求?
How to address the need to access to specific implementations in a scenario involving polymorphism?
我偶然发现了这个我无法正确解决的问题。这里有一些解释。
代码
我有这些产品 类:
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 只能消费 ProductA,ConcreteConsumerB 只能消费 ProductB。这打破了我对消费者和产品的良好抽象。
解决方案 1:转换
可以做的第一件显而易见的事情是将产品实例转换为特定产品。它可以工作,但并不理想,因为如果类型有任何问题,我依赖运行时抛出任何错误。
解决方案二:打破乘积的继承关系类
另一种解决方案是 打破产品继承 成这样:
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;}
}
解决方案 3:泛型
我也可以像这样制作 IConsumer 接口通用:
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 */
}
}
然而,像癌症一样,这个通用接口现在正在蔓延到整个程序中,这也不是理想的选择。
我不确定这里出了什么问题,违反了哪条规则。也许这是一个需要更改的设计问题。是否有更好的解决方案来解决这个问题?
如果ConcreteConsumerA
需要一个SpecificConfigurationA
来完成它的工作,而不是任何Configuration
实例那么它应该接受 SpecificConfigurationA
,而不是 Configuration
。接受任何类型的配置,然后在调用者不知道你有你没有提供的要求时在运行时出错只是在寻求错误。
对于你的第二个解决方案,你制作了一个配置对象,它只包含任何消费者可能需要的所有信息,这样就不会为消费者提供缺少他们所需信息的配置对象。如果这对您来说完全可行,那就太好了。任何消费者都不可能拥有无效的对象;它会一直工作得很好。
如果你不能统一对象,需要不同类型的具体实现,不同的消费者只能处理某些类型的配置,那么最终的解决方案是唯一真正的选择。当然,它确保您永远不会提供不正确类型的配置值。虽然 code 可能比不让类型跟踪此信息更多,但这并不意味着 work。如果这些类型没有跟踪为了你关于这些消费者中的哪些需要哪些类型的配置那么你必须以某种方式跟踪它,如果你弄错了,而不是立即弄清楚,因为你的程序没有编译,你不会发现,直到在测试中实际出现不正确的情况并且你得到一个无效的强制转换异常。如果这种情况并不常见,那么问题就更大了,而不是在所有情况下都会发生的错误,导致您在测试中遗漏了它,并且后来才被客户发现。
如果您想避免泛型传播,您可以减轻选项 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;
}
}
我找到了解决我问题的方法:访问者模式。诀窍是在我的 IConsumer
和我的 Product
之间找到另一个抽象(这里称为 ICommonInterface
),让访问者处理细节。
public interface IProductVisitor
{
ICommonInterface Visit(SpecificProductA productA);
ICommonInterface Visit(SpecificProductB productB);
}
/* The purpose of this abstract class is to minimize the impact of the changes if I had to support another SpecificProductC. */
public abstract class ProductVisitor : IProductVisitor
{
public virtual ICommonInterface GetCommonInterface(SpecificProductA productA)
{
throw new NotImplementedException();
}
public virtual ICommonInterface GetCommonInterface(SpecificProductB productB)
{
throw new NotImplementedException();
}
}
public sealed class SpecificProductAVisitor : ProductVisitor
{
public override ICommonInterface GetCommonInterface(SpecificProductA productA)
{
/* This guy will deal with ParamA of SpecificProductA */
return new ImplACommonInterface(productA);
}
}
public sealed class SpecificProductBVisitor : ProductVisitor
{
public override ICommonInterface GetCommonInterface(SpecificProductB productB)
{
/* This guy will deal with ParamB of SpecificProductB */
return new ImplBCommonInterface(productB);
}
}
然后我必须在 Product
上允许新的 IProductVisitor
类:
public abstract class Product
{
public int BaseParam { get; set; }
public abstract ICommonInterface Visit(IProductVisitor productVisitor);
}
public class SpecificProductA : Product
{
public int ParamA {get;set;}
public override ICommonInterface Visit(IProductVisitor productVisitor)
{
/* Forwards the SpecificProductA to the Visitor */
return productVisitor.GetCommonInterface(this);
}
}
public class SpecificProductB : Product
{
public int ParamB {get;set;}
public override ICommonInterface Visit(IProductVisitor productVisitor)
{
/* Forwards the SpecificProductB to the Visitor */
return productVisitor.GetCommonInterface(this);
}
}
每个 IConsumer
实现现在都可以执行以下操作而无需转换任何内容:
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);
}
}
我偶然发现了这个我无法正确解决的问题。这里有一些解释。
代码
我有这些产品 类:
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 只能消费 ProductA,ConcreteConsumerB 只能消费 ProductB。这打破了我对消费者和产品的良好抽象。
解决方案 1:转换
可以做的第一件显而易见的事情是将产品实例转换为特定产品。它可以工作,但并不理想,因为如果类型有任何问题,我依赖运行时抛出任何错误。
解决方案二:打破乘积的继承关系类
另一种解决方案是 打破产品继承 成这样:
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;}
}
解决方案 3:泛型
我也可以像这样制作 IConsumer 接口通用:
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 */
}
}
然而,像癌症一样,这个通用接口现在正在蔓延到整个程序中,这也不是理想的选择。
我不确定这里出了什么问题,违反了哪条规则。也许这是一个需要更改的设计问题。是否有更好的解决方案来解决这个问题?
如果ConcreteConsumerA
需要一个SpecificConfigurationA
来完成它的工作,而不是任何Configuration
实例那么它应该接受 SpecificConfigurationA
,而不是 Configuration
。接受任何类型的配置,然后在调用者不知道你有你没有提供的要求时在运行时出错只是在寻求错误。
对于你的第二个解决方案,你制作了一个配置对象,它只包含任何消费者可能需要的所有信息,这样就不会为消费者提供缺少他们所需信息的配置对象。如果这对您来说完全可行,那就太好了。任何消费者都不可能拥有无效的对象;它会一直工作得很好。
如果你不能统一对象,需要不同类型的具体实现,不同的消费者只能处理某些类型的配置,那么最终的解决方案是唯一真正的选择。当然,它确保您永远不会提供不正确类型的配置值。虽然 code 可能比不让类型跟踪此信息更多,但这并不意味着 work。如果这些类型没有跟踪为了你关于这些消费者中的哪些需要哪些类型的配置那么你必须以某种方式跟踪它,如果你弄错了,而不是立即弄清楚,因为你的程序没有编译,你不会发现,直到在测试中实际出现不正确的情况并且你得到一个无效的强制转换异常。如果这种情况并不常见,那么问题就更大了,而不是在所有情况下都会发生的错误,导致您在测试中遗漏了它,并且后来才被客户发现。
如果您想避免泛型传播,您可以减轻选项 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;
}
}
我找到了解决我问题的方法:访问者模式。诀窍是在我的 IConsumer
和我的 Product
之间找到另一个抽象(这里称为 ICommonInterface
),让访问者处理细节。
public interface IProductVisitor
{
ICommonInterface Visit(SpecificProductA productA);
ICommonInterface Visit(SpecificProductB productB);
}
/* The purpose of this abstract class is to minimize the impact of the changes if I had to support another SpecificProductC. */
public abstract class ProductVisitor : IProductVisitor
{
public virtual ICommonInterface GetCommonInterface(SpecificProductA productA)
{
throw new NotImplementedException();
}
public virtual ICommonInterface GetCommonInterface(SpecificProductB productB)
{
throw new NotImplementedException();
}
}
public sealed class SpecificProductAVisitor : ProductVisitor
{
public override ICommonInterface GetCommonInterface(SpecificProductA productA)
{
/* This guy will deal with ParamA of SpecificProductA */
return new ImplACommonInterface(productA);
}
}
public sealed class SpecificProductBVisitor : ProductVisitor
{
public override ICommonInterface GetCommonInterface(SpecificProductB productB)
{
/* This guy will deal with ParamB of SpecificProductB */
return new ImplBCommonInterface(productB);
}
}
然后我必须在 Product
上允许新的 IProductVisitor
类:
public abstract class Product
{
public int BaseParam { get; set; }
public abstract ICommonInterface Visit(IProductVisitor productVisitor);
}
public class SpecificProductA : Product
{
public int ParamA {get;set;}
public override ICommonInterface Visit(IProductVisitor productVisitor)
{
/* Forwards the SpecificProductA to the Visitor */
return productVisitor.GetCommonInterface(this);
}
}
public class SpecificProductB : Product
{
public int ParamB {get;set;}
public override ICommonInterface Visit(IProductVisitor productVisitor)
{
/* Forwards the SpecificProductB to the Visitor */
return productVisitor.GetCommonInterface(this);
}
}
每个 IConsumer
实现现在都可以执行以下操作而无需转换任何内容:
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);
}
}