IoC, фабрики и аргументы конструктора - PullRequest
6 голосов
/ 02 марта 2011

Я новичок в борьбе с IoC и DI. Я хотел бы иметь возможность динамически разрешать фабрику соединений и соединений, используя автофак (или любой другой подходящий инструмент .NET IoC).

Сценарий мог бы изменить реализацию соединения на другую с более широкими возможностями для трассировки и т. Д.

Когда я применяю DI и IoC к приведенному ниже коду, я получаю путаницу namedParameter в конструкторах и т. Д. Фабрика соединений возвращает новое соединение с уникальным портом (глупый пример, просто чтобы показать, что мне нужно сохранять какое-то состояние). на заводе)

Я полагаю, что я мог бы использовать внедрение свойств для IP-адреса и диапазона портов, но в этом случае я не был бы гарантирован, что соединения будут иметь IP-адрес или порт, который является точкой конструктора. Кроме того, названные параметры делают меня зависимым от имен аргументов.

Идеи, шаблоны, указатели IoC очень ценятся!

Обновление:

Более конкретно: как я могу изменить класс соединения для инъекций? Должен ли я пойти с инъекцией собственности? Или какие-то хитрости, которые я мог бы сделать, чтобы получить более безопасное для типов разрешение с помощью аргументов конструктора?

public interface IConnection {
     void Open();
     void Close();
     string Execute(string command);
}

public interface IConnectionFactory {
     IConnection CreateConnection();
}

public class Connection : IConnection {
   ...
   public Connection(String ip, int port) {
     _ip = ip;
     _port = port;
   }

   public string Execute() {}   
   public void Open() {}
   public void Close() {}
}


public class ConnectionFactory : IConnectionFactory {
    //How would I resolve this?
    public ConnectionFactory(string ip, int fromPort) {
        ...
    }
    public IConnection CreateConnection()  {
        //How would I resolve this? 
        return new Connection(ip, fromPort++);
    }
}

Теперь, использование:

//Register
builder.RegisterType<Connection>().As<IConnection>();
builder.RegisterType<ConnectionFactory>().As<IConnectionFactory>().SingleInstance();
...

var connection = container.Resolve<IConnectionFactory>(
      new NamedParameter("ip", "127.0.0.1"), 
      new NamedParameter("fromPort", 80).CreateConnection());

Ответы [ 3 ]

6 голосов
/ 02 марта 2011

Альтернативой передаче аргументов конструктора во время разрешения является кодирование этих аргументов в функции регистрации:

builder
.Register(c => new ConnectionFactory("127.0.0.1", 80))
.As<IConnectionFactory>()
.SingleInstance();

Autofac будет использовать эту функцию всякий раз, когда потребуется создать экземпляр фабрики соединений.

Поскольку мы настроили ConnectionFactory как SingleInstance, он будет использоваться всеми компонентами, которые зависят от IConnectionFactory.Это означает, что ConnectionFactory необходимо сохранять свое собственное состояние между вызовами на CreateConnection:

public class ConnectionFactory : IConnectionFactory
{
    private int _fromPort;

    public ConnectionFactory(string ip, int fromPort)
    {
        ...
        _fromPort = fromPort;
    }

    public IConnection CreateConnection()
    {
        return new Connection(ip, _fromPort++);
    }
}

Если у вас есть одноразовый ConnectionFactory, который, скажем, использует другой IP, вы можете использоватьименная регистрация:

builder
.Register(c => new ConnectionFactory("192.168.0.1", 80))
.Named<IConnectionFactory>("AlernateConnectionFactory")
.SingleInstance();

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

builder.Register(c => new Foo(c.ResolvedNamed<IConnectionFactory>("AlernateConnectionFactory")));

Это удобный метод для настройки типа несколькими способами и использования их в определенных местах.

2 голосов
/ 02 марта 2011

У меня нет опыта работы с Autofac, но я решил очень похожую проблему в Unity.Вот фрагмент кода:

Когда я настраиваю свой контейнер, я делаю что-то вроде этого (это можно сделать даже в конфигурационном файле):

string connectionString = ConfigurationManager.ConnectionStrings["XYZ"].ConnectionString;
Container.RegisterType<IConnection, Connection>(new PerThreadLifetimeManager(), 
                                                new InjectionConstructor(connectionString));

Позже я могу создать соединения с помощьюпросто сказав:

IConnection myConnection = Container.Resolve<IConnection>();

или указав свойство зависимости:

[Dependency]
IConnection Connection {get;set;}

или в качестве параметра ввода для инъекции в конструктор:

[InjectionConstructor]
public SomeClassThatUsesConnections(IConnection connection)
{ ... }

Я оставил PerThreadLifetimeManager дляотметим, что я позволю IoC позаботиться о том, чтобы никакие два потока не разделяли соединение.

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

Надеюсь, это поможет.

1 голос
/ 02 марта 2011

Гибким подходом может быть создание «AppConfigConnectionFactory» (или WebConfig, DBConfig и т. Д.) И экстернализация этих свойств с помощью конфигурации. Я никогда не чувствовал правильной загрузки данных конфигурации прямо из структуры DI.

...