Подтвердить что ты не робот

IoC Factory: за и против для интерфейса с делегатами

В любом месте, где вам нужно значение времени выполнения для конструирования определенной зависимости, абстрактным Factory является решение.

Мой вопрос: почему многие источники предпочитают FactoryInterface над FactoryDelegate для реализации этого шаблона? Каковы преимущества и недостатки обоих решений?

Вот пример, чтобы понять, что я имею в виду

Если у вас есть Служба, которая нуждается в репозитории с определенным Контекстом, то конструктор службы требуется создать Factory для создания или доступа к его репозиторию.

Общим решением для этого является создание RepositoryFactoryInterface, как это.

public IRepositoryFactory {
    IRepository Create(ContextInformation context);
}

public class MyService {
    private IRepositoryFactory repositoryFactory;
    public MyService(IRepositoryFactory repositoryFactory)
    {
        this.repositoryFactory = repositoryFactory:
    }

    public void DoSomeService()
    {
        ContextInformation context = ....;

        IRepository repository = this.repositoryFactory.Create(context);

        repository.Load(...);
        ...
        repository.Save(...);
    }
}

Вам также необходимо реализовать интерфейс IRepositoryFactory.

public MyEf4RepositoryFactory : IRepositoryFactory
{
    IRepository Create(ContextInformation context)
    {
        return new MyEf4Repository(context);
    }
}

... и использовать его в приложении

public void main()
{
    IRepositoryFactory repoFactory = new MyEf4RepositoryFactory();
    IService service = new MyService(repoFactory); 

    service.DoSomeService();
}

----- Конец основного решения ------

Вместо RepositoryFactoryInterface вы можете сделать то же самое с factorydelegate, для которого требуется меньше кодирования.

public class MyService {
    private Func<ContextInformation, IRepository> repositoryFactory;
    public MyService(Func<ContextInformation, IRepository> repositoryFactory)
    {
        this.repositoryFactory = repositoryFactory:
    }

    public void DoSomeService()
    {
        ContextInformation context = ....;

        IRepository repository = this.repositoryFactory(context);

        repository.Load(...);
        ...
        repository.Save(...);
    }
}

... и использовать его в приложении

public void main()
{
    IService service = new MyService(context => new MyEf4Repository(context)); 

    service.DoSomeService();
}

По моему мнению, factorydelegate context => new MyEf4Repository(context) намного компактнее, чем объявление и реализация интерфейса IRepositoryFactory и MyEf4RepositoryFactory.

Для этого должна быть причина, и я хочу знать почему.

Вот один пример источника, который использует интерфейс aproach: ответ на is-there-a-pattern-for-initialization-objects-created-via-a-di-container

[Обновление] Спустя 15 месяцев после того, как я задал этот вопрос и получил больше опыта работы с java-универсалами, я передумал: теперь я предпочитаю интерфейсы над делегатами. Но я не могу сказать, почему. Это просто чувство. Может быть, потому, что я больше привык к этому?

4b9b3361

Ответ 1

Лично я всегда использовал основное решение, просто потому, что не думал об использовании делегата.

После того, как я подумал об этом, я столкнулся с проблемой разделения проблем. Я использую Ninject, и я не хотел, чтобы мой модуль привязки выглядел так (представьте себе, что репозитома имеет некоторые зависимости от себя):

class IoCModule : NinjectModule
{
    public override Load()
    {
        Bind<Func<Context, IRepository>>()
            .ToConstant( context => new MyEf4Repository(context, Kernel.Get<IRepositoryDependency1>, Kernel.Get<IRepositoryDependency2>) );
    }
}

Это совсем не читаемо. Поэтому я все еще использовал полностью типизированные абстрактные фабрики для Разделения Концерна и читаемости.

Теперь я использую FuncModule, описанный в этом вопросе (a la AutoFac). Поэтому я могу это сделать:

class IoCModule : NinjectModule
{
    public override Load()
    {
        Bind<IRepository>().To<MyEf4Repository>();
        Bind<IRepositoryDependency1>().To<...>();
        Bind<IRepositoryDependency2>().To<...>();
    }
}

и пусть ninject рисует зависимости для меня. Как вы можете видеть, это более читаемо, чем использование описанного выше метода, и для привязки фабрик к каждой зависимости. Именно так я сделал переход от основного решения к решению делегата.

Итак, чтобы ответить на ваш вопрос. Причина, по которой я использовал основное решение, состояла в том, что сначала я не знал, как сделать это по-другому (это частично связано с тем, что большинство блогов полностью печатают абстрактные фабрики, видите ли вы круг?).

Ответ 2

В любом месте, где вам нужно значение времени выполнения для конструирования определенной зависимости, абстрактным Factory является решение.

Я бы возразил против этого. Зависимости не должны строиться с использованием данных времени выполнения, так как объясняется здесь. Таким образом, в статье говорится:

Не вводить данные во время выполнения во время строительства; он вызывает неоднозначность, усложняет корневой состав с дополнительной ответственностью и делает чрезвычайно сложным проверить правильность конфигурации DI. Вместо этого пусть данные времени выполнения проходят через вызовы методов построенных графиков объектов.

Когда мы позволяем данным во время выполнения "протекать через вызовы методов построенных графов объектов", вы увидите, что эффективность абстрактных заводов уменьшается. Они могут по-прежнему использоваться, когда данные времени выполнения используются для выбора из нескольких зависимостей (по сравнению с введением данных времени выполнения в зависимость), но даже тогда абстрактные заводы обычно не являются лучшим решением, поскольку объясняется здесь. Таким образом, в статье говорится:

Как правило, использование абстракции Factory не является дизайном, учитывающим его потребителей. В соответствии с принципом внедрения зависимостей (DIP) абстракции должны определяться их клиентами, а поскольку Factory увеличивает количество зависимостей, от которых клиент вынужден зависеть, абстракция явно не создается в пользу клиента, и мы может поэтому считать это нарушением DIP.

Вместо этого шаблоны, такие как Facade, Composite, Mediator и Proxy, как правило, являются лучшим решением.

Это не значит, что у вас не может быть кода в приложении, который создает зависимости, но он не должен определяться как абстракция, используемая другими компонентами приложения. Вместо этого поведение factory -подобие должно быть инкапсулировано в адаптеры, которые определены как часть вашего Root of Composition.

Если у вас есть только такая логика и зависимости типа factory как часть вашего корня композиции, на самом деле не важно, определяете ли вы IRepositoryFactory или просто используете Func<IRepository> для построения такой зависимости, поскольку IRepositoryFactory также будет определен в корне композиции, так как приложение не имеет никакого бизнеса при использовании такого factory).

Тем не менее, в редком случае абстрактная Factory является правильной абстракцией (которая обычно возникает при создании многоразовой рамки), я нахожу, что использование интерфейсов Factory гораздо более наглядно показывает, чем использование делегатов. Это немного более многословно, но гораздо яснее, что такое смысл. IControllerFactory более интенсивно отображается, чем Func<IController>.

Я бы сказал, что это еще более справедливо для заводов, которые не производят зависимостей, а не значений данных. Возьмем, например, пример ввода a Func<DateTime> в конструктор. Что это значит, и какое значение оно возвращает? Является ли интуитивно понятным, что он возвращает DateTime.Now, или возвращает DateTime.Today, или что-то еще? В этом случае было бы гораздо яснее определить интерфейс ITimeProvider с помощью метода GetCurrentTime().

ПРИМЕЧАНИЕ. Этот ответ был обновлен в июле 2017 года, чтобы отразить мои последние мнения.

Ответ 3

Я считаю, что вызов "factory" делегат не должен быть правильным.

Factory несет ответственность за создание экземпляра некоторого поддерживаемого типа, например, в какой-то реализации репозитория.

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

Вопрос для меня заключается в том, почему вы должны использовать делегат, если вы можете реализовать общий параметр TRepository с ограничениями "new" и "class" и во время построения создать экземпляр репозитория внутри вашей службы?

Это просто мнение, но, похоже, вам нужен ярлык вместо лучшего, хорошо спроектированного и оптимального решения.

Резюмируя:

  • Factory над делегатом позволяет инвертировать элемент управления даже для самого factory.

  • Делегировать через factory имеет нулевое преимущество, поскольку вы даже можете исключить необходимость самого делегата - возможно, тогда это бесполезно или избыточно -.

  • Делегат "factory" не будет factory, потому что N способов создания экземпляра репозитория будут реализованы, что приведет к необходимости преодоления необходимости и преимуществ factory.

  • Нет необходимости вручную создавать экземпляры некоторого репозитория в коде потребителя. Просто используйте общий параметр, чтобы тип репозитория можно было предоставить, чтобы создать подходящий экземпляр, благодаря инверсии элемента управления.

Ответ 4

Мне нравится и использовать решение делегата, поскольку оно более кратким. Чтобы избежать проблемы с читабельностью контейнеров IoC, упомянутых г-ном Бэдом, не используйте Func. Вместо этого создайте свой именованный делегат.

delegate IRepository RepositoryFactory(ContextInformation context);

Теперь у вас есть лучшее из обоих миров: краткость делегатов и читаемость для контейнеров IoC.

Ответ 5

Весь смысл использования абстрактного factory над простым factory состоит в объединении отдельных заводов.

В вашем случае, если вам когда-либо понадобился ваш делегат для создания чего-либо другого, кроме IRepository, у вас возникнут проблемы.