Абстрактные фабрики при использовании структур внедрения зависимостей - PullRequest
4 голосов
/ 13 ноября 2011

Мне интересно, как правильно использовать абстрактные фабрики при использовании инфраструктуры DI, и один из параметров в этой фабрике - это зависимость, которая должна обрабатываться инфраструктурой DI.

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

Например, у меня есть TcpServer, и он использует Session.Factory для создания сокетов.Объект Session фактически принимает Processor в своем конструкторе.Должен ли я передать процессор процессору TcpServer, а затем передать его на Session.Factory или сделать так, чтобы мой контейнер DI выполнил монтаж?например:

class Session : ISession
{
    public delegate ISession Factory(string name);

    ...
    public Session(string name, Processor processor)
    {
      ...
    }
}

class TcpServer : ITcpServer
{
    private readonly Session.Factory _sessionFactory;

    public TcpServer(Session.Factory sessionFactory)
    {
        this._sessionFactory = socketFactory;
    }

    ...

    public void OnConnectionReceived()
    {
       ...
       var session= _sessionFactory(ip.LocalEndPoint());
       ...

    }
}

Затем, используя DI-контейнер, такой как Ninject, я смогу сделать это при настройке контейнера:

Bind<Session.Factory>().ToMethod(c =>
{
    var processor = Kernel.Get<Processor>();
    return (name) => new Session(name, processor);
}).InSingletonScope();

Моя главная проблема с этим подходомв том, что он предполагает, что тот, кто создает Session.Factory, знает о процессоре.В моем случае, поскольку я использую DI-контейнер, это на самом деле очень удобно, но кажется странным иметь фабрику со своими собственными зависимостями.Я всегда представлял себе, что на фабрике вообще нет членов.

Если бы мне пришлось передать зависимость через

class Session : ISession
{
    public delegate ISession Factory(string name, Processor processor);

    ...
    public Session(string name, Processor processor)
    {
      ...
    }
}

class TcpServer : ITcpServer
{
    private readonly Session.Factory _sessionFactory;
    private readonly Processor _processor;

    public TcpServer(Session.Factory sessionFactory, Processor processor)
    {
        this._processor = processor;
    }

    ...

    public void OnConnectionReceived()
    {
       ...
       var session = _sessionFactory(ip.LocalEndPoint(), _processor);
       ...

    }
}     

У меня есть две проблемы со вторым подходом:

  1. TcpServer фактически ничего не делает с процессором.Это просто передает это.Кажется, это почти что DI-код бедняков на работе.
  2. В реальной программе, стоящей за этим кодом, у Processor есть ссылка на TcpServer.Поэтому при использовании этого подхода я получаю круговую ссылку.Когда я разбиваю его на части с помощью первого сценария, это не проблема.

Какой, по вашему мнению, лучший подход?Я открыт для новых идей.

Спасибо!

Ответы [ 2 ]

4 голосов
/ 13 ноября 2011

Многие контейнеры поддерживают фабрики тем или иным способом, и именно так вы и должны идти.

Например, на вашем примере определите интерфейс ISessionFactory, подобный этому

public interface ISessionFactory
{
    ISession CreateSession(string name);
}

Для Ninject 2.3 см.https://github.com/ninject/ninject.extensions.factory и пусть это будет реализовано Ninject

Bind<ISessionFactory>().AsFactory();

Для 2.2 сделайте реализацию самостоятельно

public class SessionFactory : ISessionFactory
{
    private IKernel kernel;
    public SessionFactory(IKernel kernel)
    {
        this.kernel = kernel;
    }

    public ISession CreateSession(string name)
    {
        return this.kernel.Get<ISession>(new ConstructorArgument("name", name));
    }
}
1 голос
/ 13 ноября 2011

Шаблон, который я использую для абстрактного фабричного шаблона, немного отличается от вашего. Я использую что-то наподобие внедрения сеттера в общий синглтон, но оборачиваю настраиваемое «свойство» делегата в более интуитивно понятный интерфейс.

Я бы предпочел не регистрировать каждую реализацию по отдельности, поэтому я бы предпочел использовать некоторые соглашения, которые можно протестировать при запуске приложения. Я не уверен в синтаксисе Ninject для автоматической регистрации пользовательских соглашений, но логика сводится к сканированию соответствующих сборок на наличие ссылочных типов T, которые имеют статические поля только для чтения типа AbstractFactory<T>, а затем вызывают Configure(Func<T>) для этой статической член, использующий отражение.

Ниже приведен пример универсального абстрактного фабричного синглтона и способа его объявления в Session.

 public class Session { 
      public static readonly AbstractFactory<Session> Factory = AbstractFactory<Session>.GetInstance();

 }

 public sealed class AbstractFactory<T> 
     where T: class{

     static AbstractFactory(){
          Bolt = new object();
     }
     private static readonly object Bolt;
     private static AbstractFactory<T> Instance;
     public static AbstractFactory<T> GetInstance(){
          if(Instance == null){
              lock(Bolt){
                  if(Instance == null)
                      Instance = new AbstractFactory<T>();
              }
          }
          return Instance;
     }

     private AbstractFactory(){}

     private Func<T> m_FactoryMethod;

     public void Configure(Func<T> factoryMethod){
              m_FactoryMethod = factoryMethod;
     }

     public T Create() { 
              if(m_FactoryMethod == null) {
                       throw new NotImplementedException();
              }
              return m_FactoryMethod.Invoke();
     } 

 } 

Обновление

Если вам нужно передать параметры в метод фабрики, вы можете изменить класс, например:

 public sealed class AbstractFactory<TDataContract,T> 
      where T: class{
     static AbstractFactory(){
          Bolt = new object();
     }
     private static readonly object Bolt;
     private static AbstractFactory<TDataContract,T> Instance;
     public static AbstractFactory<TDataContract,T> GetInstance(){
          if(Instance == null){
              lock(Bolt){
                  if(Instance == null)
                      Instance = new AbstractFactory<T>();
              }
          }
          return Instance;
     }

     private AbstractFactory(){}

     private Func<TDataContract,T> m_FactoryMethod;

     public void Configure(Func<TDataContract,T> factoryMethod){
              m_FactoryMethod = factoryMethod;
     }

     public T Create(TDataContract data) { 
              if(m_FactoryMethod == null) {
                       throw new NotImplementedException();
              }
              return m_FactoryMethod.Invoke(data);
     } 

 } 

Ваши SessionData, Session и TcpServer могут выглядеть как

 public class SessionData{
      public DateTime Start { get; set; }
      public string IpAddress { get; set; }
 }

 public class Session { 
      public static readonly AbstractFactory<SessionData,Session> Factory = AbstractFactory<Session>.GetInstance();

      private readonly string _ip;
      private readonly DateTime _start;

      public Session(SessionData data) { 
           _ip = data.IpAddress;
           _start = DateTime.Now;

      }
      public event EventHandler<RequestReceivedEventEventArgs> RequestAdded;

 }

 public class RequestReceivedEventArgs:  EventArgs { 

     public SessionRequest Request { get; set; }
 }

public class TcpServer : ITcpServer
{
    private readonly Processor _processor;

    public TcpServer(Processor processor)
    {
        this._processor = processor;
    }

    public void OnConnectionReceived()
    {
       var sessionData = new SessionData { 
                                            IpAddress = ip.LocalEndPoint(),
                                            Start = DateTime.Now
                                          };
       var session = Session.Factory.Create(sessionData);

       //...do other stuff
    }

    public void ServeResponse(SessionRequest request){
            _processor.Process(request);
    }
}  

При настройке DI-контейнера вы можете настроить заводские настройки, например:

Session.Factory.Configure(sessionData => { 
       // instead of injecting the processor into the Session, configure events 
       // that allow the TcpServer to process the data.  
       // (After all, it is more logical for a servers to serve a request than 
       // it is for a Session to do the Processing.  Session's tend to store data
       // and state, not invoke processes
       session.RequestAdded += (sender,e) => { 
              Kernel.Get<ITcpServer>.ServeResponse(e.Request);
       };
});
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...