base/descendant class 分辨率的策略和工厂模式
Strategy & factory pattern for base/descendant class resolution
我正在重构代码库,偶然发现了一个工厂 class,它根据传入方法的子类型创建对象。
class 基本上有一个 public 方法和一个参数,它是基础 class 的后代。在这个方法中是一个 switch 语句,它确定传递哪个子类型并有条件地调用不同的方法来产生结果。
我正在尝试整理一下,认为策略模式可能适合要求,因为代码违反了开闭原则。
由于使用了 Autofac,我认为过渡会很简单,但是我在路上遇到了障碍。
问题与 Autofac 无关,而与设计选择有关。
下面的代码说明了 class 的组成,但是缺少。
public abstract class Parent { }
public class ChildA : Parent { }
public class ChildB : Parent { }
public interface IChildStrategy<T> where T:Parent
{
IEnumerable<object> CreateObjects(Parent child);
}
public class ChildAStrategy : IChildStrategy<ChildA>
{
private IEnumerable<object> CreateObjects(ChildA child)
{
yield return "child A";
}
public IEnumerable<object> CreateObjects(Parent child) =>
CreateObjects(child as ChildA);
}
public class ChildBStrategy : IChildStrategy<ChildB>
{
private IEnumerable<object> CreateObjects(ChildB child)
{
yield return "child b";
yield return "child b's pet";
}
public IEnumerable<object> CreateObjects(Parent child) =>
CreateObjects(child as ChildB);
}
[TestMethod]
public void TestStrategyPattern()
{
var container = builder.Build();
Parent child = new ChildA();
var type = child.GetType();
var strategy = container.Resolve(typeof(IChildStrategy<>)
.MakeGenericType(type));
// strategy.CreateObjects(child);
// Assert.AreEqual("child A", fromDict);
var dict = new Dictionary<Type, Func<Parent, IEnumerable<object>>>();
dict.Add(typeof(ChildA), x => new ChildAStrategy().CreateObjects(x));
dict.Add(typeof(ChildB), x => new ChildBStrategy().CreateObjects(x));
var fromDict = dict[type](child);
Assert.AreEqual("child A", fromDict);
}
我已经尝试过使用通用类型本身注册接口,如下所示:
public interface IChildStrategy<T> where T:Parent
{
IEnumerable<object> CreateObjects(T child);
}
但这并没有真正改变难度。
对于子 classes 的设计模式是否有任何好的替代方案?
已更新
这是我最终得到的结果。这些更改基本上是从 CreateObjects 方法中删除参数,而是将其作为依赖项注入构造函数并将策略注册为 Keyed<T>
注册。
public abstract class Parent { }
public class ChildA : Parent { }
public class ChildB : Parent { }
public interface IChildStrategy
{
IEnumerable<object> CreateObjects();
}
public class ChildAStrategy : IChildStrategy
{
private readonly ChildA childA;
public ChildAStrategy(ChildA childA)
{
this.childA = childA;
}
public IEnumerable<object> CreateObjects()
{
yield return childA;
}
}
public class ChildBStrategy : IChildStrategy
{
private readonly ChildB childB;
public ChildBStrategy(ChildB childB)
{
this.childB = childB;
}
public IEnumerable<object> CreateObjects()
{
yield return childB;
yield return "child b's pet";
}
}
[TestMethod]
public void TestStrategyPattern()
{
var builder = new ContainerBuilder();
builder.RegisterType<ChildAStrategy>().Keyed<IChildStrategy>(typeof(ChildA));
builder.RegisterType<ChildBStrategy>().Keyed<IChildStrategy>(typeof(ChildB));
var container = builder.Build();
IChildStrategy resolve(Parent x) => container.ResolveKeyed<IChildStrategy>(x.GetType(), new TypedParameter(x.GetType(), x));
Parent root;
IChildStrategy strategy;
root = new ChildA();
strategy = resolve(root);
Assert.IsInstanceOfType(strategy, typeof(ChildAStrategy));
Assert.AreSame(root, strategy.CreateObjects().Single());
root = new ChildB();
strategy = resolve(root);
Assert.IsInstanceOfType(strategy, typeof(ChildBStrategy));
Assert.AreSame(root, strategy.CreateObjects().First());
Assert.AreEqual("child b's pet", strategy.CreateObjects().Last());
}
更新答案
原答案已包含在下方
我认为您正在寻找的模式是 Mediator。
据我了解,这是满足您需求的示例实现。此实现加强了处理程序的类型,但在中介本身中更多地使用了 dynamic
。如果性能是一个问题,您可能需要 运行 一些测试——这个实现可能比您现有的代码慢(参见:How does having a dynamic variable affect performance?)
using System.Collections.Generic;
using System.Linq;
using Autofac;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace _54542354.MediatorExample
{
/**
* Example Input/Output types
**/
abstract class ActionBase { }
class ExampleAction : ActionBase { public string Name { get; set; } }
class ReturnType { public string Id { get; set; } }
/**
* Interfaces
**/
interface IActionHandler<TAction> where TAction : ActionBase
{
IEnumerable<ReturnType> Handle(TAction action);
}
interface IActionHandlerMediator
{
IEnumerable<ReturnType> Handle(ActionBase action);
}
/**
* Example implementations
**/
class ExampleHandler : IActionHandler<ExampleAction>
{
public IEnumerable<ReturnType> Handle(ExampleAction action)
{
yield return new ReturnType{ Id = $"{action.Name}_First" };
yield return new ReturnType{ Id = $"{action.Name}_Second" };
}
}
class ActionHandlerMediator : IActionHandlerMediator
{
readonly ILifetimeScope container;
public ActionHandlerMediator(ILifetimeScope container)
=> this.container = container;
public IEnumerable<ReturnType> Handle(ActionBase action)
{
// TODO: Error handling. What if no strategy is registered for the provided type?
dynamic handler = container.Resolve(typeof(IActionHandler<>)
.MakeGenericType(action.GetType()));
return (IEnumerable<ReturnType>)handler.Handle((dynamic)action);
}
}
/**
* Usage example
**/
[TestClass]
public class Tests
{
[TestMethod]
public void TestMediator()
{
var builder = new ContainerBuilder();
builder.RegisterType<ExampleHandler>().As<IActionHandler<ExampleAction>>();
builder.RegisterType<ActionHandlerMediator>().As<IActionHandlerMediator>();
var container = builder.Build();
var handler = container.Resolve<IActionHandlerMediator>();
var result = handler.Handle(new ExampleAction() { Name = "MyName" });
Assert.AreEqual("MyName_First", result.First().Id);
Assert.AreEqual("MyName_Second", result.Last().Id);
}
}
}
原答案
我试了一下 运行你的示例代码。我不得不调整一些开箱即用的东西,但我认为它确实按照你想要的方式工作(在我调整之后)。
这是我最终得到的结果:
[TestMethod]
public void TestStrategyPattern_Dict()
{
// Define the available strategies
var dict = new Dictionary<Type, Func<Parent, IEnumerable<object>>>();
dict.Add(typeof(ChildA), x => new ChildAStrategy().CreateObjects(x));
dict.Add(typeof(ChildB), x => new ChildBStrategy().CreateObjects(x));
// Create the input object
Parent child = new ChildA();
// Invoke the strategy
IEnumerable<object> enumerable = dict[child.GetType()](child);
// Verify the results
Assert.AreEqual("child A", enumerable.Single());
}
[TestMethod]
public void TestStrategyPattern_AutoFac()
{
// Define the available strategies
var builder = new ContainerBuilder();
builder.RegisterType<ChildAStrategy>().As<IChildStrategy<ChildA>>();
builder.RegisterType<ChildBStrategy>().As<IChildStrategy<ChildB>>();
var container = builder.Build();
// Create the input object
Parent child = new ChildA();
// Resolve the strategy
// Because we don't know exactly what type the container will return,
// we need to use `dynamic`
dynamic strategy = container.Resolve(typeof(IChildStrategy<>)
.MakeGenericType(child.GetType()));
// Invoke the strategy
IEnumerable<object> enumerable = strategy.CreateObjects(child);
// Verify the results
Assert.AreEqual("child A", enumerable.Single());
}
这些测试都通过了。我没有更改测试之外的任何代码。
我介绍的两个主要变化是:
- 在断言之前使用
.Single()
。这是必要的,因为策略 return 是一个 IEnumerable
,但断言期望来自该可枚举对象的第一个对象。
- 从 AutoFac 解析策略时使用
dynamic
类型。这是必要的,因为编译器无法判断 AutoFac 将 return 是什么类型。在原始代码中,returned 类型是 object
,它没有 CreateObjects(Parent)
方法。 dynamic
让我们可以调用我们想要的任何方法——编译器会假设它存在。如果该方法不存在,我们将得到一个 运行time 异常,但因为我们知道我们刚刚创建了一个 IChildStrategy<>
,我们可以确信该方法将存在。
我正在重构代码库,偶然发现了一个工厂 class,它根据传入方法的子类型创建对象。
class 基本上有一个 public 方法和一个参数,它是基础 class 的后代。在这个方法中是一个 switch 语句,它确定传递哪个子类型并有条件地调用不同的方法来产生结果。
我正在尝试整理一下,认为策略模式可能适合要求,因为代码违反了开闭原则。
由于使用了 Autofac,我认为过渡会很简单,但是我在路上遇到了障碍。
问题与 Autofac 无关,而与设计选择有关。
下面的代码说明了 class 的组成,但是缺少。
public abstract class Parent { }
public class ChildA : Parent { }
public class ChildB : Parent { }
public interface IChildStrategy<T> where T:Parent
{
IEnumerable<object> CreateObjects(Parent child);
}
public class ChildAStrategy : IChildStrategy<ChildA>
{
private IEnumerable<object> CreateObjects(ChildA child)
{
yield return "child A";
}
public IEnumerable<object> CreateObjects(Parent child) =>
CreateObjects(child as ChildA);
}
public class ChildBStrategy : IChildStrategy<ChildB>
{
private IEnumerable<object> CreateObjects(ChildB child)
{
yield return "child b";
yield return "child b's pet";
}
public IEnumerable<object> CreateObjects(Parent child) =>
CreateObjects(child as ChildB);
}
[TestMethod]
public void TestStrategyPattern()
{
var container = builder.Build();
Parent child = new ChildA();
var type = child.GetType();
var strategy = container.Resolve(typeof(IChildStrategy<>)
.MakeGenericType(type));
// strategy.CreateObjects(child);
// Assert.AreEqual("child A", fromDict);
var dict = new Dictionary<Type, Func<Parent, IEnumerable<object>>>();
dict.Add(typeof(ChildA), x => new ChildAStrategy().CreateObjects(x));
dict.Add(typeof(ChildB), x => new ChildBStrategy().CreateObjects(x));
var fromDict = dict[type](child);
Assert.AreEqual("child A", fromDict);
}
我已经尝试过使用通用类型本身注册接口,如下所示:
public interface IChildStrategy<T> where T:Parent
{
IEnumerable<object> CreateObjects(T child);
}
但这并没有真正改变难度。
对于子 classes 的设计模式是否有任何好的替代方案?
已更新
这是我最终得到的结果。这些更改基本上是从 CreateObjects 方法中删除参数,而是将其作为依赖项注入构造函数并将策略注册为 Keyed<T>
注册。
public abstract class Parent { }
public class ChildA : Parent { }
public class ChildB : Parent { }
public interface IChildStrategy
{
IEnumerable<object> CreateObjects();
}
public class ChildAStrategy : IChildStrategy
{
private readonly ChildA childA;
public ChildAStrategy(ChildA childA)
{
this.childA = childA;
}
public IEnumerable<object> CreateObjects()
{
yield return childA;
}
}
public class ChildBStrategy : IChildStrategy
{
private readonly ChildB childB;
public ChildBStrategy(ChildB childB)
{
this.childB = childB;
}
public IEnumerable<object> CreateObjects()
{
yield return childB;
yield return "child b's pet";
}
}
[TestMethod]
public void TestStrategyPattern()
{
var builder = new ContainerBuilder();
builder.RegisterType<ChildAStrategy>().Keyed<IChildStrategy>(typeof(ChildA));
builder.RegisterType<ChildBStrategy>().Keyed<IChildStrategy>(typeof(ChildB));
var container = builder.Build();
IChildStrategy resolve(Parent x) => container.ResolveKeyed<IChildStrategy>(x.GetType(), new TypedParameter(x.GetType(), x));
Parent root;
IChildStrategy strategy;
root = new ChildA();
strategy = resolve(root);
Assert.IsInstanceOfType(strategy, typeof(ChildAStrategy));
Assert.AreSame(root, strategy.CreateObjects().Single());
root = new ChildB();
strategy = resolve(root);
Assert.IsInstanceOfType(strategy, typeof(ChildBStrategy));
Assert.AreSame(root, strategy.CreateObjects().First());
Assert.AreEqual("child b's pet", strategy.CreateObjects().Last());
}
更新答案
原答案已包含在下方
我认为您正在寻找的模式是 Mediator。
据我了解,这是满足您需求的示例实现。此实现加强了处理程序的类型,但在中介本身中更多地使用了 dynamic
。如果性能是一个问题,您可能需要 运行 一些测试——这个实现可能比您现有的代码慢(参见:How does having a dynamic variable affect performance?)
using System.Collections.Generic;
using System.Linq;
using Autofac;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace _54542354.MediatorExample
{
/**
* Example Input/Output types
**/
abstract class ActionBase { }
class ExampleAction : ActionBase { public string Name { get; set; } }
class ReturnType { public string Id { get; set; } }
/**
* Interfaces
**/
interface IActionHandler<TAction> where TAction : ActionBase
{
IEnumerable<ReturnType> Handle(TAction action);
}
interface IActionHandlerMediator
{
IEnumerable<ReturnType> Handle(ActionBase action);
}
/**
* Example implementations
**/
class ExampleHandler : IActionHandler<ExampleAction>
{
public IEnumerable<ReturnType> Handle(ExampleAction action)
{
yield return new ReturnType{ Id = $"{action.Name}_First" };
yield return new ReturnType{ Id = $"{action.Name}_Second" };
}
}
class ActionHandlerMediator : IActionHandlerMediator
{
readonly ILifetimeScope container;
public ActionHandlerMediator(ILifetimeScope container)
=> this.container = container;
public IEnumerable<ReturnType> Handle(ActionBase action)
{
// TODO: Error handling. What if no strategy is registered for the provided type?
dynamic handler = container.Resolve(typeof(IActionHandler<>)
.MakeGenericType(action.GetType()));
return (IEnumerable<ReturnType>)handler.Handle((dynamic)action);
}
}
/**
* Usage example
**/
[TestClass]
public class Tests
{
[TestMethod]
public void TestMediator()
{
var builder = new ContainerBuilder();
builder.RegisterType<ExampleHandler>().As<IActionHandler<ExampleAction>>();
builder.RegisterType<ActionHandlerMediator>().As<IActionHandlerMediator>();
var container = builder.Build();
var handler = container.Resolve<IActionHandlerMediator>();
var result = handler.Handle(new ExampleAction() { Name = "MyName" });
Assert.AreEqual("MyName_First", result.First().Id);
Assert.AreEqual("MyName_Second", result.Last().Id);
}
}
}
原答案
我试了一下 运行你的示例代码。我不得不调整一些开箱即用的东西,但我认为它确实按照你想要的方式工作(在我调整之后)。
这是我最终得到的结果:
[TestMethod]
public void TestStrategyPattern_Dict()
{
// Define the available strategies
var dict = new Dictionary<Type, Func<Parent, IEnumerable<object>>>();
dict.Add(typeof(ChildA), x => new ChildAStrategy().CreateObjects(x));
dict.Add(typeof(ChildB), x => new ChildBStrategy().CreateObjects(x));
// Create the input object
Parent child = new ChildA();
// Invoke the strategy
IEnumerable<object> enumerable = dict[child.GetType()](child);
// Verify the results
Assert.AreEqual("child A", enumerable.Single());
}
[TestMethod]
public void TestStrategyPattern_AutoFac()
{
// Define the available strategies
var builder = new ContainerBuilder();
builder.RegisterType<ChildAStrategy>().As<IChildStrategy<ChildA>>();
builder.RegisterType<ChildBStrategy>().As<IChildStrategy<ChildB>>();
var container = builder.Build();
// Create the input object
Parent child = new ChildA();
// Resolve the strategy
// Because we don't know exactly what type the container will return,
// we need to use `dynamic`
dynamic strategy = container.Resolve(typeof(IChildStrategy<>)
.MakeGenericType(child.GetType()));
// Invoke the strategy
IEnumerable<object> enumerable = strategy.CreateObjects(child);
// Verify the results
Assert.AreEqual("child A", enumerable.Single());
}
这些测试都通过了。我没有更改测试之外的任何代码。
我介绍的两个主要变化是:
- 在断言之前使用
.Single()
。这是必要的,因为策略 return 是一个IEnumerable
,但断言期望来自该可枚举对象的第一个对象。 - 从 AutoFac 解析策略时使用
dynamic
类型。这是必要的,因为编译器无法判断 AutoFac 将 return 是什么类型。在原始代码中,returned 类型是object
,它没有CreateObjects(Parent)
方法。dynamic
让我们可以调用我们想要的任何方法——编译器会假设它存在。如果该方法不存在,我们将得到一个 运行time 异常,但因为我们知道我们刚刚创建了一个IChildStrategy<>
,我们可以确信该方法将存在。