简体   繁体   English

基类/后代类解析的策略和工厂模式

[英]Strategy & factory pattern for base/descendant class resolution

I'm refactoring a codebase and stumbled upon a factory class that created objects based on the subtype passed into the method. 我正在重构代码库,偶然发现了一个工厂类,该工厂类根据传递给方法的子类型创建对象。

The class basically has one public method with one parameter of which it is a descendant from a base class. 该类基本上具有一个带有一个参数的公共方法,该方法是基类的后代。 Within this method is a switch statement that determines which subtype is passed and conditionally calls different methods to produce the result. 这个方法中有一个switch语句,该语句确定要传递的子类型,并有条件地调用不同的方法来产生结果。

I'm trying to tidy up a bit and figured a strategy pattern might suit the requirements since the code violates the open-closed principle. 我正在尝试整理一下,并发现一种策略模式可能符合要求,因为代码违反了开闭原则。

Since Autofac is being used, I figured that the transition would be straight forward, however I've hit a bump in the road. 自从使用Autofac以来,我认为过渡会很直接,但是我遇到了麻烦。

The problem isn't related to Autofac, but rather to the choice of design. 问题与Autofac无关,而与设计选择有关。

The following code illustrates the class composition, but it is lacking. 以下代码说明了类的组成,但是缺少它。

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);
}

I've tried both registering the interface with the generic type itself, like so: 我已经尝试过用通用类型本身注册接口,就像这样:

public interface IChildStrategy<T> where T:Parent
{
    IEnumerable<object> CreateObjects(T child);
}

But it doesn't really change the difficulties. 但这并没有真正改变困难。

Are there any good alternatives to a design pattern for sub-classes? 子类的设计模式是否有好的替代方案?

Updated 更新

Here's what I ended up with. 这就是我最后得到的。 The changes are basically removing the parameter from the CreateObjects method and rather inject it into the constructor as a dependency and registering the strategies as Keyed<T> registrations. 这些更改基本上是从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());
}

Updated Answer 更新的答案

Original answer is included, below 原始答案包括在下面

I think the pattern you're looking for is a Mediator . 我认为您正在寻找的模式是调解员

Here's an example implementation that fits your needs, as I understand them. 据我了解,这是一个适合您需求的示例实现。 This implementation strengthens the typing of the handlers, but makes even heavier use of dynamic in the mediator itself. 此实现增强了处理程序的类型,但在调解器本身中甚至更多地使用了dynamic If performance is a concern, you might want to run some tests--this implementation will probably be slower than your existing code (see: 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);
        }
    }
}

Original Answer 原始答案

I took a stab at running your sample code. 我在运行您的示例代码时遇到了麻烦。 I had to tweak some things out of the box, but I think it actually worked as you want it to (after my tweaks). 我必须先调整一些东西,但我认为它确实可以按您的意愿(在我调整之后)工作。

Here's what I ended up with: 我最终得到的是:

[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());
}

These tests both pass. 这些测试都通过了。 I did not change any code outside of the tests. 我没有在测试之外更改任何代码。

The two main changes I introduced are: 我介绍的两个主要更改是:

  • Use of .Single() before asserting. 在断言之前使用.Single() This is necessary because the strategy returns an IEnumerable , but the assertion is expecting the first object from that enumerable. 这是必需的,因为该策略返回IEnumerable ,但是断言期望该枚举中的第一个对象。
  • Use of the dynamic type when resolving the strategy from AutoFac. 从AutoFac解决策略时使用dynamic类型。 This is necessary because the compiler can't tell what type AutoFac will return. 这是必需的,因为编译器无法确定AutoFac将返回哪种类型。 In the original code, the returned type was object , which doesn't have a CreateObjects(Parent) method. 在原始代码中,返回的类型为object ,它没有CreateObjects(Parent)方法。 dynamic lets us call any method we want--the compiler will just assume it exists. dynamic让我们可以调用所需的任何方法-编译器将假定它存在。 We'll get a runtime exception if the method doesn't exist, but because we know we just created an IChildStrategy<> , we can be confident that the method will exist. 如果该方法不存在,我们将获得运行时异常,但是由于我们知道我们刚刚创建了IChildStrategy<> ,因此可以确信该方法将存在。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM