IoC Factory: плюсы и минусы для интерфейса против делегатов - PullRequest
27 голосов
/ 23 марта 2011

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

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

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

Если у вас есть служба, которой нужен репозиторий с определенным контекстом, тогда конструктор службы нужна фабрика для создания или доступа к хранилищу.

Распространенным решением для этого является создание 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();
}

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

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

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

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

Ответы [ 5 ]

10 голосов
/ 23 марта 2011

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

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

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

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

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

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

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

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

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

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

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

4 голосов
/ 23 марта 2011

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

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

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

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

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

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

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

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

3 голосов
/ 14 августа 2012

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

delegate IRepository RepositoryFactory(ContextInformation context);

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

3 голосов
/ 23 марта 2011

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

Фабрика отвечает за создание экземпляров некоторых поддерживаемых типов, например, некоторых реализаций репозитория.

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

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

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

Подведение итогов:

  • Фабрика над делегатом позволяет инвертировать управление даже для самой фабрики.

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

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

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

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

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

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

...