问题
I have a situation where I have an IEnumerable which I need to iterate through and execute some code against each item.
This code to be executed is dependent on the actual type of the item, and what I am looking for is a good clean way of doing this without any conditionals, so if the number of derived types I need to handle increases I simply need to write a new handler and not change any of my existing code.
To illustrate this I have the example where the 3rd party library contains the following code:
public abstract class BaseObject{ }
public class Derived1: BaseObject { }
public class Derived2 : BaseObject { }
and my local code does something like:
void Execute(IEnumerable<BaseObject> list)
{
foreach (var item in list)
item.DoWork();
}
I have tried creating extension methods to do this:
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; }
}
which obviously doesn't work as each item in the enumeration is of type BaseObject, so the extension method that returns 0 is always called.
I have the option of creating a new derived type for each derived type all implementing an interface:
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();
}
then I could iterate and cast to the new interface:
void Execute(IEnumerable<BaseObject> list)
{
foreach (var item in list)
{
var local = item as IMyInterface;
local?.DoWork();
}
}
but this requires construction of my derived type, and the decision as to which type to construct still requires a conditional statement which would need to change for any new type added.
Clearly use of the factory pattern here, and its the cleanest solution I have so far.
The command pattern offers a solution, but as the command to execute is dependent on the type of the derived type, a conditional is still needed to construct this command...
Can anyone suggest an approach that would completely eliminate any conditional that would need changing when new derived types are added to the mix?
回答1:
You could use dynamic
. Normally I'm not a fan of it, but in this case it seems to be a good option.
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
}
}
回答2:
One potential solution uses a composition framework, I have chosen MEF for my example.
What you need to do is create handlers that implement a common interface, each one being contained in the composition container with a contract name matching the name of the derived type:
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;
}
}
then you can get the handler from the composition container and use that:
void Execute(IEnumerable<BaseObject> list)
{
foreach (var item in list)
{
var worker = DIContainer.Instance.GetObject<IBaseObjectWorker>(item.GetType().ToString());
worker?.DoWork(item);
}
}
so to extend this to handle a new derived type would simple mean creating a new handler type. If the composition container is populated with all types from the assembly, the new handler would be there as soon as its created and would start handling the new derived type with no modification to current code. Thus obeying the open-closed principle.
This is a kind of an implementation of the factory pattern, but without having to worry about the implementation of the factory.
as a side not the DIContainer is my personal implementation of a single pattern wrapper around a MEF container, this is populated on app startup and the GetObject(string name) simply calls the GetExportedValue(string contractName).
来源:https://stackoverflow.com/questions/36354522/polymorphic-extension-to-3rd-party-classes