根据子类分派协变列表元素
Dispatch co-variant list elements according to subclass
我有 classes B
和 C
,继承自 class SuperA
。如果我有一个包含 SuperA
的各种实现的 SuperA
列表,我如何根据列表中每个元素的实际实现来调用采用 B
和 C
参数的方法,而不必测试每个元素的类型(出于 open/closed 原则原因,我宁愿避免 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));
}
不过,我愿意接受任何更好的想法。正如回答中指出的那样,进化后运行时错误的风险很大。
你可以这样称呼它:
DoSomething((dynamic)factory.GenerateOutput((dynamic)item));
这样,使用 dynamic
您的对象将在运行时绑定到正确的方法。
使用此实现,您将不得不考虑到发送 C
未实现任何方法的对象的风险,您的代码仍可编译,但会出现运行时错误生成。
编译器抱怨您的代码,因为正如您所指出的,OutputFactory
class 中没有 GenerateOutput(SuperA)
并且方法调用解析发生在编译类型,而不是运行时,因此基于引用的类型(item
是类型为 SuperA
的引用)而不是基于运行时实例的类型。
您可以尝试不同的方法:
- 如果你觉得有意义,你可以尝试将多态行为(要生成的输出文本)移动到
SuperA
class 层次结构中,添加一个抽象方法或 属性 到SuperA
并在 SuperA
的 subclasses 中以不同方式实现
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
Cclasses are out of your control or when the different desired behaviours you should provide for
Aand
Bclasses does not belong to
Band
时效果不是很好C` classes.
- 你可以使用我喜欢称之为责任集的方法:它类似于 GoF 的模式责任链,但没有链;-);你可以重写你的
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
),但将它们隐藏在接口后面(具体来说,接口的每个实现都应提供类型检查,以便 select 它可以处理的实例) : 如果你需要添加第三个 D extends SuperA
subclass 你的层次根 class 你只需要添加第三个 DHandler : Handler
实现 Handler
接口,无需修改既没有提供实现也没有 CompositeHelper
class;您应该对现有代码应用的唯一更改是在您提供给 CompositeHelper
的构造函数的列表中新 handler
实现的 注册 ,但是这可以很容易地移动到您 IoC container
配置或外部配置文件。
我喜欢这种方法,因为它可以将基于 类型检查的 算法转换为多态算法。
我最近在我的技术博客 post 中写过这个主题:https://javapeanuts.blogspot.com/2018/10/set-of-responsibility.html。
- 您可以通过 GoF 的 visitor 模式来解决这个问题,这比我建议的方法稍微复杂一点,但正是为这种情况设计的
- 您可以采用基于
dynamic
的方法,如另一个回复中所建议的那样
希望对您有所帮助!
我有 classes B
和 C
,继承自 class SuperA
。如果我有一个包含 SuperA
的各种实现的 SuperA
列表,我如何根据列表中每个元素的实际实现来调用采用 B
和 C
参数的方法,而不必测试每个元素的类型(出于 open/closed 原则原因,我宁愿避免 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));
}
不过,我愿意接受任何更好的想法。正如回答中指出的那样,进化后运行时错误的风险很大。
你可以这样称呼它:
DoSomething((dynamic)factory.GenerateOutput((dynamic)item));
这样,使用 dynamic
您的对象将在运行时绑定到正确的方法。
使用此实现,您将不得不考虑到发送 C
未实现任何方法的对象的风险,您的代码仍可编译,但会出现运行时错误生成。
编译器抱怨您的代码,因为正如您所指出的,OutputFactory
class 中没有 GenerateOutput(SuperA)
并且方法调用解析发生在编译类型,而不是运行时,因此基于引用的类型(item
是类型为 SuperA
的引用)而不是基于运行时实例的类型。
您可以尝试不同的方法:
- 如果你觉得有意义,你可以尝试将多态行为(要生成的输出文本)移动到
SuperA
class 层次结构中,添加一个抽象方法或 属性 到SuperA
并在SuperA
的 subclasses 中以不同方式实现
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
Cclasses are out of your control or when the different desired behaviours you should provide for
Aand
Bclasses does not belong to
Band
时效果不是很好C` classes.
- 你可以使用我喜欢称之为责任集的方法:它类似于 GoF 的模式责任链,但没有链;-);你可以重写你的
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
),但将它们隐藏在接口后面(具体来说,接口的每个实现都应提供类型检查,以便 select 它可以处理的实例) : 如果你需要添加第三个 D extends SuperA
subclass 你的层次根 class 你只需要添加第三个 DHandler : Handler
实现 Handler
接口,无需修改既没有提供实现也没有 CompositeHelper
class;您应该对现有代码应用的唯一更改是在您提供给 CompositeHelper
的构造函数的列表中新 handler
实现的 注册 ,但是这可以很容易地移动到您 IoC container
配置或外部配置文件。
我喜欢这种方法,因为它可以将基于 类型检查的 算法转换为多态算法。
我最近在我的技术博客 post 中写过这个主题:https://javapeanuts.blogspot.com/2018/10/set-of-responsibility.html。
- 您可以通过 GoF 的 visitor 模式来解决这个问题,这比我建议的方法稍微复杂一点,但正是为这种情况设计的
- 您可以采用基于
dynamic
的方法,如另一个回复中所建议的那样
希望对您有所帮助!