我有类B
和C
,它们是从类SuperA
继承的。如果我有一个SuperA
的列表,其中包含SuperA
的各种实现,那么如何根据列表中每个元素的实际实现来调用采用B
和C
参数的方法,而不必测试每个元素的类型(出于开放/封闭的原则,我宁愿避免使用if(item is B)
东西)。
public class Test
{
public void TestMethod()
{
var list = new List<SuperA> {new B(), new C()};
var factory = new OutputFactory();
foreach (SuperA item in list)
{
DoSomething(factory.GenerateOutput(item)); // doesn't compile as there is no GenerateOutput(SuperA foo) signature in OutputFactory.
}
}
private static void DoSomething(OutputB b)
{
Console.WriteLine(b.ToString());
}
private static void DoSomething(OutputC c)
{
Console.WriteLine(c.ToString());
}
public class SuperA
{
}
public class B : SuperA
{
}
public class C : SuperA
{
}
public class OutputB
{
public override string ToString()
{
return "B";
}
}
public class OutputC
{
public override string ToString()
{
return "C";
}
}
public class OutputFactory
{
public OutputB GenerateOutput(B foo)
{
return new OutputB();
}
public OutputC GenerateOutput(C foo)
{
return new OutputC();
}
}
}
在上面的代码中,我希望打印:
B
C
编辑:
我发现一个有效的解决方案可能是将项目类型更改为dynamic
foreach (dynamic item in list)
{
DoSomething(factory.GenerateOutput(item));
}
但是我愿意接受任何更好的主意。正如在回答中指出的那样,演化后运行时错误的风险很大。
答案 0 :(得分:1)
编译器抱怨您的代码,因为如您所指出的,GenerateOutput(SuperA)
类中没有OutputFactory
,并且方法调用解析发生在编译类型而不是运行时,因此基于引用的类型(item
是类型为SuperA
的引用),而不是运行时实例的类型。
您可以尝试不同的方法:
SuperA
类层次结构中,向SuperA
添加抽象方法或属性,并在其中进行不同的实现SuperA
的子类class SuperA {
public abstract string Content { get; }
}
class B : SuperA {
public string Content => "B";
}
class C : SuperA {
public string Content => "C";
}
class Test {
public void TestMethod() {
// ...
foreach (SuperA item in list) {
Console.WriteLine(item.Content);
}
}
非常简单,但是在SuperA
,B, and
C classes are out of your control or when the different desired behaviours you should provide for
A and
B classes does not belong to
B and
C时效果不佳课程。
TestMethod
:public void TestMethod() {
var list = new List<SuperA> {new B(), new C()};
var compositeHandler = new CompositeHandler(new Handler[] {
new BHandler(),
new CHandler()
});
foreach (SuperA item in list) {
compositeHandler.Handle(item);
}
}
因此,您需要定义一个Handler
接口及其实现,如下所示:
interface Handler {
bool CanHandle(SuperA item);
void Handle(SuperA item);
}
class BHandler : Handler {
bool CanHandle(SuperA item) => item is B;
void Handle(SuperA item) {
var b = (B)item; // cast here is safe due to previous check in `CanHandle()`
DoSomethingUsingB(b);
}
}
class CHandler : Handler {
bool CanHandle(SuperA item) => item is C;
void Handle(SuperA item) {
var c = (C)item; // cast here is safe due to previous check in `CanHandle()`
DoSomethingUsingC(c);
}
}
class CompositeHandler {
private readonly IEnumerable<handler> handlers;
public CompositeHandler(IEnumerable<handler> handlers) {
this.handlers = handlers;
}
public void Handle(SuperA item) {
handlers.FirstOrDefault(h => h.CanHandle(item))?.Handle(item);
}
}
此方法使用类型检查(item is B
),但将它们隐藏在接口后面(具体来说,该接口的每个实现都应提供类型检查,以便选择它可以处理的实例):如果需要添加您的层次结构根类的第三个D extends SuperA
子类,您只需要添加DHandler : Handler
接口的第三个Handler
实现,而无需修改已经提供的实现和CompositeHelper
类;您应该对现有代码进行的唯一更改是提供给handler
的构造函数的列表中新CompositeHelper
的实现的 registration ,但这很容易已移至您的IoC container
配置或外部配置文件。
我喜欢这种方法,因为它可以将基于类型检查的算法转换为多态算法。
我在我的技术博客https://javapeanuts.blogspot.com/2018/10/set-of-responsibility.html上最近的一篇文章中谈到了这个主题。
dynamic
的方法希望这对您有帮助!
答案 1 :(得分:0)
您可以这样称呼它:
DoSomething((dynamic)factory.GenerateOutput((dynamic)item));
这样,使用dynamic
,您的对象将在运行时绑定到正确的方法。
使用此实现,您将不得不考虑自己面临发送C
对象的风险,该对象未实现任何方法,并且您的代码仍会编译,但是会生成运行时错误