Является ли это правильной или жизнеспособной реализацией шаблона абстрактной фабрики?

На основе сообщения Марка Симанна: Распознавание образов: абстрактная фабрика или локатор сервисов?

Я хочу написать абстрактную фабрику так:

public interface IAbstractFactory { 
    T Create<T>();
}

Затем свяжите его с помощью Ninject следующим образом:

IKernel kernel = new StandardKernel();
kernel.Bind<IAbstractFactory>().ToFactory();

Затем можно использовать его следующим образом:

public class CustomerServiceIndicatorsModel {
    public CustomerServiceIndicatorsModel(IAbstractFactory factory) {
        this.emailIndicatorA = factory.Create<EmailIndicatorA>();
        this.emailIndicatorB = factory.Create<EmailIndicatorB>();
        this.emailIndicatorC = factory.Create<EmailIndicatorC>();
    }
}

Опять же, я нигде не ссылаюсь на ядро ​​Ninject, и это работает. Ядро упоминается только в файле Global.asax.cs для привязок.

Можно ли считать его приемлемой реализацией шаблона абстрактной фабрики?

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

public interface IEmailIndicatorAFactory {
    EmailIndicatorA Create();
}

Затем, когда я свяжу его ToFactory() с помощью Ninject, он создаст экземпляры EmailIndicatorA.

Как связать IAbstractFactory<T> с помощью Ninject, так как каждый тип:

IAbstractFactory<EmailIndicatorA>
IAbstractFactory<EmailIndicatorB>
IAbstractFactory<EmailIndicatorC>

считается вполне конкретным типом. И я не могу найти способ связать его с помощью Ninject.

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

public interface EmailIndicatorAFactory : IAbstractFactory<EmailIndicatorA> { }
public interface EmailIndicatorBFactory : IAbstractFactory<EmailIndicatorB> { }
public interface EmailIndicatorCFactory : IAbstractFactory<EmailIndicatorC> { }

После комментария @PrestonGuillot я попал в последнюю реализацию ServiceLocator вместо AbstractFactory, поскольку я использую общий метод Create<T>(), а Abstract Factory использует не универсальный метод Create().

Спасибо, что указали на это, @PrestonGuillot! знак равно

Возможно, я слишком усложняю... Итак, вот моя модель:

Индикатор электронной почты

public abstract EmailIndicator { 
    int EmailCount { get; set; }
    DateTime OldestDateTimeReceived { get; set; }
}

Индикатор электронной почтыA

public class EmailIndicatorA : EmailIndicator { }

Индикатор электронной почтыB

public class EmailIndicatorB : EmailIndicator { }

Индикатор электронной почтыC

public class EmailIndicatorC : EmailIndicator { }

Репозиторий IEmailIndicatorRepository

public interface IEmailIndicatorRepository<T> where T : EmailIndicator {
    T GetIndicator();
}

public class EmailIndicatorARepository : IEmailIndicatorRepository<EmailIndicatorA> {
    public EmailIndicatorARepository(IExchangeService service
        , IExchangeConfiguration configuration
        , INetworkCredentialFactory credentialFactory) {
        exchangeService = service;
        exchangeService.Url = configuration.ExchangeUri;
        exchangeService = credentialFactory.Create(configuration.Login, configuration.Password);       
    }

    EmailIndicatorA GetIndicator() {
        // Code to query Exchange through its Web services here...
    }
}

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

Я считаю, что есть место для использования Abstract Factory, и, поскольку я все еще изучаю шаблон, я просто еще не понимаю, как реализовать его в моей ситуации.

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

Насколько я понимаю, вот что я бы попробовал в качестве первого шага к решению:

IRepositoryFactory

public interface IRepositoryFactory<T> where T : class, new() {
    T Create();
}

IEmailIndicatorARepositoryFactory

public interface IEmailIndicatorARepositoryFactory 
    : IRepositoryFactory<EmailIndicatorARepository> {
    EmailIndicatorARepository CreateEmailIndicatorARepository();
}

IEmailIndicatorBRepositoryFactory

public interface IEmailIndicatorBRepositoryFactory 
    : IRepositoryFactory<EmailIndicatorBRepository> {
    EmailIndicatorBRepository CreateEmailIndicatorBRepository();
}

А Абстрактная фабрика?

public abstract IEmailIndicatorRepositoryFactory 
    : IEmailIndicatorARepositoryFactory
    , IEmailIndicatorBRepositoryFactory
    , IEmailIndicatorCRepositoryFactory {
    EmailIndicatorARepository CreateEmailIndicatorARepository() { // create instance here... }
    EmailIndicatorBRepository CreateEmailIndicatorBRepository() { // create instance here... }
    EmailIndicatorCRepository CreateEmailIndicatorCRepository() { // create instance here... }
}

Это лучший подход?

Я немного потерян и сбит с толку здесь.


person Will Marcouiller    schedule 10.11.2014    source источник
comment
Если вам нужны экземпляры EmailIndicatorA, EmailIndicatorB и EmailIndicatorC для создания экземпляра CustomerServiceIndicatorsModel, почему бы вам просто не добавить их в конструктор? Похоже, вы не делаете ничего, что требует абстрактной фабрики из вашего фрагмента.   -  person Preston Guillot    schedule 10.11.2014
comment
Ты прав. Возможно, мой пример выбран не очень удачно, и я не мог ничего другого придумать в голову. Кроме того, как насчет реализации шаблона Abstract Factory?   -  person Will Marcouiller    schedule 10.11.2014
comment
Несмотря на это, сообщение в блоге, на которое вы ссылаетесь, отвечает на ваш вопрос напрямую. Service Locator — это неуниверсальный тип с универсальным методом Create, вы попадаете во второй вариант.   -  person Preston Guillot    schedule 10.11.2014
comment
Если подумать, вы правы. Должно быть, я запутался в пути. Мне трудно получить представление о шаблоне Abstract Factory и его преимуществах.   -  person Will Marcouiller    schedule 10.11.2014
comment
@PrestonGuillot: Пожалуйста, ознакомьтесь с моим вопросом. знак равно   -  person Will Marcouiller    schedule 10.11.2014


Ответы (1)


Я нахожу диаграммы из статьи в Википедии весьма полезными.

Я думаю, что название «Абстрактная фабрика» немного вводит в заблуждение в мире .net/c#. Вам нужен abstract class только в том случае, если язык не поддерживает интерфейсы или вы хотите поделиться реализацией (но помните одобрение композиция вместо наследования).

Поскольку вы упомянули Марка Симана, я хотел бы выразить свою интерпретацию нескольких его сообщений, касающихся IoC и Factory:

  • don't use service locator
  • create a composition root. Ideally it should instantiate all objects in one go at the beginning.
    • as such you should design your objects to be stateless and only process data - whenever possible (separation of data and processing logic). That means you only have to instantiate more or less data classes / data structs, which don't have any dependencies. All the objects with dependencies are created in one go upon application start up.
  • late creation is not failing fast
    • if you use late creation, whether you call it service locator or factory, this decreases testability, since issues will not be visible upon application startup but rather only when you're using/creating the object at a later time.
    • чтобы несколько исправить это, создайте все зависимости объекта, который будет создан фабрикой, уже с корнем @composition. Только позднее создавайте фактический объект.
    • это делается с использованием шаблона абстрактной фабрики.

Итак, учитывая ваш пример, это будет:

public interface IEmailIndicatorFactory
{
    IEmailIndicator Create();
}

с тремя реализациями, по одной для EmailIndicatorA, EmailIndicatorB и EmailIndicatorC каждая. Теперь, чтобы аргумент имел значение, EmailIndicator нужна зависимость, специфичная для EmailIndicatorA (или B, C). Допустим, для этого требуется IServiceA:

internal class EmailIndicatorA : IEmailIndicator
{
    private readonly IServiceA serviceA;

    public EmailIndicatorA(IServiceA serviceA)
    {
        this.serviceA = serviceA;
    }

    (...)
}

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

internal class EmailIndicatorAFactory : IEmailIndicatorFactory
{
    private readonly IServiceA serviceA;

    public EmailIndicatorAFactory(IServiceA serviceA)
    {
        this.serviceA = serviceA;
    }

    public IEmailIndicator Create()
    {
        return new EmailIndicatorA(this.serviceA);
    }
}

Вот и все. Если у EmailIndicatorA есть дополнительные зависимости, их тоже нужно внедрить в EmailIndicatorAFactory.

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

Я думаю, вам следует тщательно взвесить все «за» и «против».

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

Я думаю, что Марк очень хорошо указывает на плюсы и минусы, и чтение его постов очень поучительно. Я не согласен со всеми его выводами, но TBH, я хотел бы поработать с ним над проектом какое-то время (не просто несколько недель, а дольше) и затем посмотреть, согласен я или нет.

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

И последнее замечание: я не знаю ни одного контейнера IoC, в котором есть Func<>, или интерфейсных фабрик, таких как .ToFactory() Ninject, которые рано создают экземпляры зависимостей. Это делается только после того, как вы выполните Func<> или вызовете метод IFooFactory.CreateFoo(). Таким образом, проблема отказоустойчивости еще не решена автоматически генерируемыми фабриками. Однако что было сделано, так это то, что некоторые контейнеры имеют методы проверки, которые проверяют, что типы могут быть созданы и что нет Закрепленные зависимости. Например, Простой инжектор

person BatteryBackupUnit    schedule 11.11.2014
comment
Вдохновленный вашим подходом, я, наконец, решил продолжить использование провайдеров, которые, как мне кажется, являются более простым способом, хотя на самом деле они служат той же цели, то есть созданию экземпляров на основе какого-либо контекста или зависимостей. Кстати, я всегда ценю ваши ответы. Они всегда освещали мой путь к более правильному использованию DI и IoC. Спасибо. - person Will Marcouiller; 13.11.2014