Стратегия и шаблон фабрики для разрешения базового / потомкового класса - PullRequest
0 голосов
/ 05 февраля 2019

Я выполняю рефакторинг кодовой базы и наткнулся на фабричный класс, который создал объекты на основе подтипа, переданного в метод.

У класса в основном есть один открытый метод с одним параметром, из которого он является потомкомбазовый класс.В этом методе есть оператор switch, который определяет, какой подтип передается, и условно вызывает различные методы для получения результата.

Я пытаюсь немного привести в порядок и подумал, что шаблон стратегии может соответствовать требованиям, поскольку коднарушает принцип открытого-закрытого.

Поскольку используется Autofac, я подумал, что переход будет прямым, однако я столкнулся с препятствием на дороге.

Проблема не в том,t относится к Autofac, а скорее к выбору дизайна.

Следующий код иллюстрирует композицию классов, но ее не хватает.

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

Но это не меняет сложностей.

Есть ли хорошие альтернативы шаблонам проектирования для подклассов?

Обновлено

Вот чем я закончил.Изменения в основном удаляют параметр из метода 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());
}

1 Ответ

0 голосов
/ 06 февраля 2019

Обновленный ответ

Оригинальный ответ включен ниже

Я думаю, что шаблон, который вы ищете, это Посредник .

Вот пример реализации, которая соответствует вашим потребностям, насколько я понимаю.Эта реализация усиливает типизацию обработчиков, но еще более интенсивно использует dynamic в самом посреднике.Если производительность является проблемой, вы можете запустить некоторые тесты - эта реализация, вероятно, будет медленнее, чем существующий код (см .: Как влияет динамическая переменная на производительность? )

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() перед подтверждением.Это необходимо, потому что стратегия возвращает IEnumerable, но утверждение ожидает первый объект из этого перечисляемого.
  • Использование типа dynamic при разрешении стратегии из AutoFac.Это необходимо, потому что компилятор не может сказать, какой тип AutoFac вернет.В исходном коде возвращенный тип был object, у которого нет метода CreateObjects(Parent).dynamic позволяет нам вызывать любой метод, который мы хотим - компилятор просто предположит, что он существует.Мы получим исключение времени выполнения, если метод не существует, но поскольку мы знаем, что только что создали IChildStrategy<>, мы можем быть уверены, что метод будет существовать.
...