我有一种情况,我有一个IEnumerable,我需要迭代并对每个项目执行一些代码。
这个要执行的代码取决于项目的实际类型,我正在寻找的是一个很好的干净方式,没有任何条件,所以如果我需要处理的派生类型的数量增加我只是需要编写一个新的处理程序,而不是更改我现有的任何代码。
为了说明这一点,我举例说明第三方库包含以下代码:
public abstract class BaseObject{ }
public class Derived1: BaseObject { }
public class Derived2 : BaseObject { }
我的本地代码就像:
void Execute(IEnumerable<BaseObject> list)
{
foreach (var item in list)
item.DoWork();
}
我尝试创建扩展方法来执行此操作:
public static class Extensions
{
public static int DoWork(this BaseObject obj) { return 0; }
public static int DoWork(this Derived1 obj) { return 1; }
public static int DoWork(this Derived2 obj) { return 2; }
}
这显然不起作用,因为枚举中的每个项都是BaseObject类型,因此始终调用返回0的扩展方法。
我可以选择为每个派生类型创建一个新的派生类型实现一个接口:
public class MyDerived1: Derived1, IMyInterface
{
public int DoWork(){return 1;}
}
public class MyDerived2: Derived2, IMyInterface
{
public int DoWork(){return 2;}
}
public interface IMyInterface
{
int DoWork();
}
然后我可以迭代并转换为新界面:
void Execute(IEnumerable<BaseObject> list)
{
foreach (var item in list)
{
var local = item as IMyInterface;
local?.DoWork();
}
}
但是这需要构造我的派生类型,并且关于构造哪种类型的决定仍然需要条件语句,该条件语句需要针对添加的任何新类型进行更改。
在这里清楚地使用工厂模式,这是我迄今为止最干净的解决方案。
命令模式提供了一个解决方案,但由于要执行的命令取决于派生类型的类型,因此仍然需要条件来构造此命令...
任何人都可以提出一种方法,可以完全消除在将新派生类型添加到混合中时需要更改的任何条件吗?
答案 0 :(得分:4)
您可以使用dynamic
。通常我不是它的粉丝,但在这种情况下它似乎是一个不错的选择。
public static class Worker
{
public static int DoWork(BaseObject obj) { return 0; }
public static int DoWork(Derived1 obj) { return 1; }
public static int DoWork(Derived2 obj) { return 2; }
}
void Execute(IEnumerable<BaseObject> list) {
foreach (dynamic item in list) {
Worker.DoWork(item); // Method resolution done at run-time
}
}
答案 1 :(得分:0)
一个潜在的解决方案是使用合成框架,我选择了MEF作为我的例子。
您需要做的是创建实现公共接口的处理程序,每个接口都包含在合成容器中,合同名称与派生类型的名称相匹配:
public interface IBaseObjectWorker
{
int DoWork(BaseObject obj);
}
[Export(contractType: typeof(IBaseObjectWorker), contractName: "UnitTestProject1.Derived1")]
public class DerivedObject1Worker : IBaseObjectWorker
{
public int DoWork(BaseObject obj)
{
return 1;
}
}
[Export(contractType: typeof(IBaseObjectWorker), contractName: "UnitTestProject1.Derived2")]
public class DerivedObject2Worker : IBaseObjectWorker
{
public int DoWork(BaseObject obj)
{
return 2;
}
}
然后你可以从合成容器中获取处理程序并使用它:
void Execute(IEnumerable<BaseObject> list)
{
foreach (var item in list)
{
var worker = DIContainer.Instance.GetObject<IBaseObjectWorker>(item.GetType().ToString());
worker?.DoWork(item);
}
}
所以要扩展它以处理新的派生类型简单意味着创建一个新的处理程序类型。如果使用程序集中的所有类型填充组合容器,则新处理程序将在创建后立即存在,并将开始处理新的派生类型而不修改当前代码。因此遵守开放封闭的原则。
这是一种工厂模式的实现,但不必担心工厂的实施。
作为不是DIContainer的一方是我个人实现MEF容器周围的单个模式包装器,这是在app启动时填充的,GetObject(字符串名称)只是调用GetExportedValue(string contractName)。