Как использовать DI-контейнер для разрешения зависимостей в шаблоне стратегии? - PullRequest
0 голосов
/ 09 ноября 2018

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

У меня есть бизнес-логика:

interface IBusinessLogic
{
   void DoBusinessLogic();
}

class TypeABusinessLogic : IBusinessLogic
{
   public void DoBusinessLogic()
   {
      Console.WriteLine("Do Business Logic for Type A");
   }
 } 

class TypeBBusinessLogic : IBusinessLogic
{
   public void DoBusinessLogic()
   {
      Console.WriteLine("Do Business Logic for Type B");
   }
}  

А также некоторая логика приложения:

interface IApplicationLogic
{
   void DoApplicationLogic();
}

class TypeAApplicationLogic : IApplicationLogic
{
   public void DoApplicationLogic()
   {
      Console.WriteLine("Do Application Logic for Type A");
   }
 } 

class TypeBApplicationLogic : IApplicationLogic
{
   public void DoApplicationLogic()
   {
      Console.WriteLine("Do Application Logic for Type B");
   }
}    

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

interface IStrategy
{
   void DoWork();
}

abstract class StrategyBase : IStrategy
{
   private IBusinessLogic _businessLogic;
   private IApplicationLogic _applicationLogic;

   protected StrategyBase(IBusinessLogic businessLogic, IApplicationLogic applicationLogic)
   {
      _businessLogic = businessLogic;
      _applicationLogic = applicationLogic;
   }

   public void DoWork()
   {
      _businessLogic.DoBusinessLogic();
      _applicationLogic.DoApplicationLogic();
   }
}

class TypeAStrategy : IStrategy
{
   public TypeAStrategy(TypeABussinessLogic businessLogic, TypeAApplicationLogic applicationLogic) : base(businessLogic, applicationLogic)
   {}
}

class TypeBStrategy : IStrategy
{
   public TypeBStrategy(TypeBBussinessLogic businessLogic, TypeBApplicationLogic applicationLogic) : base(businessLogic, applicationLogic)
   {}
}

Теперь мой класс Context

class Context
{
   private Func<string, IStrategy> _strategyFactory;
   public Context(Func<string, IStrategy> strategyFactory)
   {
      _strategyFactory = strategyFactory;
   } 
   public void Run()
   {
      string userInput = GetUserInput(); //"TypeA" or "TypeB"
      IStrategy strategy = _strategyFactory(userInput);
      strategy.DoWork();
   }
}

Вот мой код конструктора DI:

var builder = new ContainerBuilder();
builder.RegisterType<TypeAStrategy>().As<IStrategy>().Keyed<IStrategy>("TypeA");
var builder = new ContainerBuilder();
builder.RegisterType<TypeBStrategy>().As<IStrategy>().Keyed<IStrategy>("TypeB");
builder.Register<Func<string, IStrategy>>( c => 
{
   var componentContext = c.Resolve<IComponentContext>();
   return (key) =>
   {
       IStrategy stategy = componentContext.ResolveKeyed<IStrategy >(key);
       return stategy;
   };
});

Проблема, которую я вижу здесь, заключается в том, что мои стратегии (TypeAStrategy, TypeBStrategy) напрямую зависят от конкретного класса (TypeABusinessLogic, TypeAApplicationLogic, TypeBBusinessLogic, TypeBApplicationLogic), что не очень хорошо. Я не могу издеваться над этими зависимостями в модульном тесте.

Если я позволю моим стратегиям зависеть от интерфейсов, я не знаю, как реализовать DI-контейнер для разрешения зависимостей (Примечания: в настоящее время я использую Autofac, но я могу использовать любые другие DI-контейнеры)

Пожалуйста, сообщите.

1 Ответ

0 голосов
/ 09 ноября 2018

Итак, я подумал о нескольких способах подойти к этому, но я думаю, что самый простой способ - это просто ввести несколько интерфейсов токенов. Интерфейс токена - это интерфейс, который не добавляет никаких свойств или функциональности. Например:

interface IBusinessLogic
{
    void DoBusinessLogic();
}
interface ITypeABusinessLogic : IBusinessLogic { }
interface ITypeBBusinessLogic : IBusinessLogic { }

interface IApplicationLogic
{
    void DoApplicationLogic();
}
interface ITypeAApplicationLogic : IApplicationLogic { }
interface ITypeBApplicationLogic : IApplicationLogic { }

Затем мы настраиваем классы для реализации соответствующего интерфейса токена:

class TypeABusinessLogic : ITypeABusinessLogic
{
    public virtual void DoBusinessLogic()
    {
        Console.WriteLine("Do Business Logic for Type A");
    }
}

class TypeBBusinessLogic : ITypeBBusinessLogic
{
    public virtual void DoBusinessLogic()
    {
        Console.WriteLine("Do Business Logic for Type B");
    }
}

class TypeAApplicationLogic : ITypeAApplicationLogic
{
    public void DoApplicationLogic()
    {
        Console.WriteLine("Do Application Logic for Type A");
    }
}

class TypeBApplicationLogic : ITypeBApplicationLogic
{
    public void DoApplicationLogic()
    {
        Console.WriteLine("Do Application Logic for Type B");
    }
}

Аналогичным образом мы можем создавать фиктивные классы, реализуя соответствующий интерфейс токена:

class MockTypeABusinessLogic : ITypeABusinessLogic
{
    public void DoBusinessLogic()
    {
        Console.WriteLine("[Mock] Do Business Logic for Type A");
    }
}

class MockTypeBBusinessLogic : ITypeBBusinessLogic
{
    public void DoBusinessLogic()
    {
        Console.WriteLine("[Mock] Do Business Logic for Type B");
    }
}

class MockTypeAApplicationLogic : ITypeAApplicationLogic
{
    public void DoApplicationLogic()
    {
        Console.WriteLine("[Mock] Do Application Logic for Type A");
    }
}

class MockTypeBApplicationLogic : ITypeBApplicationLogic
{
    public void DoApplicationLogic()
    {
        Console.WriteLine("[Mock] Do Application Logic for Type B");
    }
}

Я также изменил интерфейс IStrategy, чтобы немного упростить внедрение с Unity, дав каждой стратегии свойство Name (вам не нужно это делать):

interface IStrategy
{
    string Name { get;  }
    void DoWork();
}

abstract class StrategyBase : IStrategy
{
    private IBusinessLogic _businessLogic;
    private IApplicationLogic _applicationLogic;

    public string Name { get; private set; }

    protected StrategyBase(String name, IBusinessLogic businessLogic, IApplicationLogic applicationLogic)
    {
        this.Name = name;
        _businessLogic = businessLogic;
        _applicationLogic = applicationLogic;
    }

    public void DoWork()
    {
        _businessLogic.DoBusinessLogic();
        _applicationLogic.DoApplicationLogic();
    }
}    

class TypeAStrategy : StrategyBase
{
    public TypeAStrategy(String name, ITypeABusinessLogic businessLogic, ITypeAApplicationLogic applicationLogic) : base(name, businessLogic, applicationLogic)
    { }
}

class TypeBStrategy : StrategyBase
{
    public TypeBStrategy(String name, ITypeBBusinessLogic businessLogic, ITypeBApplicationLogic applicationLogic) : base(name, businessLogic, applicationLogic)
    { }
}

Используя Unity, я написал следующую программу для проверки регистраций:

class Context
{
    private Dictionary<string, IStrategy> _strategyFactory = new Dictionary<string, IStrategy>();
    public Context(IStrategy[] strategies)
    {
        foreach (var s in strategies)
        {
            _strategyFactory.Add(s.Name, s);
        }
    }
    public void Run()
    {
        string userInput = "TypeA";
        IStrategy strategy = _strategyFactory[userInput];
        strategy.DoWork();

        userInput = "TypeB";
        strategy = _strategyFactory[userInput];
        strategy.DoWork();
    }
}

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Mock DI Example: ");
        UnityContainer ioc = new UnityContainer();

        ioc.RegisterType<ITypeABusinessLogic, MockTypeABusinessLogic>();
        ioc.RegisterType<ITypeAApplicationLogic, MockTypeAApplicationLogic>();
        ioc.RegisterType<ITypeBBusinessLogic, MockTypeBBusinessLogic>();
        ioc.RegisterType<ITypeBApplicationLogic, MockTypeBApplicationLogic>();
        ioc.RegisterType<IStrategy, TypeAStrategy>("TypeA", new InjectionConstructor("TypeA", typeof(ITypeABusinessLogic), typeof(ITypeAApplicationLogic)));
        ioc.RegisterType<IStrategy, TypeBStrategy>("TypeB", new InjectionConstructor("TypeB", typeof(ITypeBBusinessLogic), typeof(ITypeBApplicationLogic)));

        Context c = ioc.Resolve<Context>();
        c.Run();

        Console.WriteLine("\nUnmocked DI Example: ");

        ioc = new UnityContainer();

        ioc.RegisterType<ITypeABusinessLogic, TypeABusinessLogic>();
        ioc.RegisterType<ITypeAApplicationLogic, TypeAApplicationLogic>();
        ioc.RegisterType<ITypeBBusinessLogic, TypeBBusinessLogic>();
        ioc.RegisterType<ITypeBApplicationLogic, TypeBApplicationLogic>();
        ioc.RegisterType<IStrategy, TypeAStrategy>("TypeA", new InjectionConstructor("TypeA", typeof(ITypeABusinessLogic), typeof(ITypeAApplicationLogic)));
        ioc.RegisterType<IStrategy, TypeBStrategy>("TypeB", new InjectionConstructor("TypeB", typeof(ITypeBBusinessLogic), typeof(ITypeBApplicationLogic)));

        c = ioc.Resolve<Context>();
        c.Run();

        Console.WriteLine("\nPress enter to exit...");
        Console.ReadLine();
    }

И вот мой вывод:

Mock DI Пример:

[Mock] Логика Do Business для типа A

[Mock] Do Application Logic для типа A

[Макет] Do Business Logic для типа B

[Mock] Do Application Logic для типа B

Unmocked DI Пример:

Логика ведения бизнеса для типа А

Прикладная логика для типа A

Do Business Logic для типа B

Прикладная логика для типа B

Нажмите Enter, чтобы выйти ...

Это не единственный способ решить проблему, но я думаю, что это наиболее точно соответствует тому, как вы структурировали свой код в OP. Надеюсь, это поможет:)

РЕДАКТИРОВАТЬ: Вот одна из альтернатив, выше, я думаю, вы должны рассмотреть. Это значительно сократит иерархию вашего объекта и интерфейса. ПРИМЕЧАНИЕ: вам нужно сделать класс StrategyBase не абстрактным и предоставить конструктор открытым.

        Console.WriteLine("\nAlternative DI Example: ");

        ioc = new UnityContainer();

        ioc.RegisterType<IBusinessLogic, TypeABusinessLogic>("TypeA");
        ioc.RegisterType<IApplicationLogic, TypeAApplicationLogic>("TypeA");
        ioc.RegisterType<IStrategy, StrategyBase>("TypeA", new InjectionConstructor("TypeA", new ResolvedParameter<IBusinessLogic>("TypeA"), new ResolvedParameter<IApplicationLogic>("TypeA") ));
        ioc.RegisterType<IBusinessLogic, TypeBBusinessLogic>("TypeB");
        ioc.RegisterType<IApplicationLogic, TypeBApplicationLogic>("TypeB");
        ioc.RegisterType<IStrategy, StrategyBase>("TypeB", new InjectionConstructor("TypeB", new ResolvedParameter<IBusinessLogic>("TypeB"), new ResolvedParameter<IApplicationLogic>("TypeB")));
        c = ioc.Resolve<Context>();
        c.Run();

Поскольку ваши классы и интерфейсы токенов на самом деле не предоставляют вам никакой функциональности, они служат только для дифференциации конкретных реализаций. Но DI-контейнеры уже имеют простой способ сделать это: строки. В Unity вы можете использовать одну и ту же строку для разных типов, как указано выше. Вы можете использовать это, чтобы определить, какие конкретные реализации идут вместе. Это моя рекомендация:)

...