autofac / c #: универсальная фабрика для определенного базового класса?

#c# #dependency-injection #factory #autofac

#c# #внедрение зависимостей #фабрика #autofac

Вопрос:

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

В настоящее время я вижу, насколько возможно модифицировать проект c # «brownfield» для использования autofac. Проект требует создания дерева компонентов разных типов, но одного и того же базового класса. Эти компоненты требуют аутентификации и служб базы данных (среди прочего), поэтому сложно просто использовать пустые конструкторы.

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

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

Что-то вроде (где? является ли загадочная фабрика)

 public MyBase { 
    public Add(MyBase x) {..}
}

public DerivedA: MyBase {}

public DerivedB : MyBase {}

public DerivedC : DerivedA {}

public SomethingElse
{
     private ? _myBaseFact;

     public SomethingElse(? myBaseFact) { 
            _myBaseFact = myBaseFact;
     }

     public BuildComponentTree() {
        DerivedA a = _myBaseFact<DerivedA>();
        DerivedB b = _myBaseFact<DerivedB>();
        DerivedC c = _myBaseFact<DerivedC>();
        a.Add(b);
        b.Add(c);
     }
}
  

редактировать: меня попросили привести более конкретный пример, который справедлив 🙂 Существуют компоненты фреймворка, и я хотел разрешить разработчикам создавать свои собственные компоненты без необходимости регистрации в autofac. Наличие заводского класса означало бы, что разработчику пришлось бы добавлять свои пользовательские компоненты к этой фабрике, верно? Пожалуйста, извините за пример, это похоже на то, как работает фреймворк, когда я с ним столкнулся.

 FwComponent {
    public FwComponent (DataProvider, AuthManager, OtherModule)
    public Add(FwComponent x);
    public Setup();
    public Process();
    public Render();
}

Page : FwComponent {
    public Page(DataProvider, AuthManager, OtherModule): base(..)
    public string Title;
    ...
}

Input: FwComponent {
    public Input(DataProvider, AuthManager, OtherModule): base(..)
    public string Name;
    public int length;
    ...
}

Button: FwComponent {
    public Button(DataProvider, AuthManager, OtherModule): base(..)
    public string Name;
    ...
}

----

MyCustomButton : Button {
     public MyCustomButton(DataProvider, AuthManager, OtherModule): base(..)
}

MyPersonalPage : FwComponent {

    IContext _container;

    public MyPersonalPage(DataProvider, AuthManager, OtherModule, IContext container): base(..)

    public Setup() {
         var input = _container.Resolve<Input>();
         input.Name = 'xxx';
         input.length = 10;
         Add(input);

         var butt = _container.Resolve<MyCustomButton>();
         butt.name = "xxx";
         Add(butt);    
    }

    public Process() {
        DataProvider.DoStuff();
    }
}
  

Ответ №1:

Как я упоминал в своем комментарии к ответу Стивена, ваш вопрос, похоже, основан на непонимании назначения шаблона factory и полиморфизма. Я оставлю это для его ответа, но давайте перейдем к вашему непониманию внедрения зависимостей…

1) Внедрение зависимостей предназначено для служб, а не для сущностей

Неясно, что представляют собой ваши компоненты и дерево, но я предполагаю, что это объекты в вашем домене, а не службы, которые выполняют обработку на них. Внедрение зависимостей предназначено для внедрения сервисов (в самом общем смысле) в другие сервисы или связующие слои. Примеры включают внедрение репозитория в контроллер MVC, или веб-службы публикации Twitter в viewmodel, или фабрики продуктов в службу управления продуктами.

2) Внедрение зависимостей и его фреймворки не имеют ничего общего с шаблоном factory

Хотя она называется AutoFac, это не какой-то автоматический генератор фактов, точно так же, как Unity — это способ объединения всех ваших классов в один или Castle Windsor — это … ну… Понятия не имею. Единственная связь, которую могут иметь эти две концепции, заключается в том, что фабрика является одной из вышеупомянутых служб, которые вы внедряете.

3) Внедрение зависимостей зависит от хорошего дизайна

То есть вы должны использовать надлежащие объектно-ориентированные соображения, такие как использование конструктора для, ну, создания класса. Хотя я подозреваю, что компоненты в вашем дереве являются сущностями (как обсуждалось выше), если бы они были сервисами, тогда Add метод противоречил бы этим принципам проектирования: если классу нужен экземпляр MyBase , он должен принять это как параметр конструктора с соответствующим защитным предложением, чтобы генерировать исключение, если этот инвариант не выполняется. Аналогично, SomethingElse очевидно, требуется дерево компонентов, которое может быть указано корневым узлом: ну, тогда он должен принять этот корневой узел в качестве параметра конструктора.


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

 interface INotificationService { }
class TwitterNotificationService : INotificationService { }
class FacebookNotificationService : INotificationService { }
class CompositeNotificationService : INotificationService
{
    public CompositeNotificationService(IEnumerable<NotificationService> services) { }
    // implement composite pattern
}

class NotificationController : Controller
{
    public NotificationController(INotificationService service)
    {
        if (service === null)
        {
            throw new ArgumentNullException("service");
        }

        // ...
    }
}
  

Позже, если бы вы использовали DI для бедных, вы бы сделали что-то вроде

 var controller = new NotificationController(
        new CompositeNotificationService(new []
        {
            new TwitterNotificationService(),
            new FacebookNotificationService()
        }));
  

Цель фреймворка DI — просто переместить шаблонный код построения, подобный этому, в одно центральное местоположение вашего приложения (корень композиции), эффективно устраняя из вашего приложения проблему построения сервиса и превращая ее в деталь конфигурации. Но ваше приложение должно быть дружественным к DI, что на самом деле означает соответствие базовым принципам OO, таким как использование конструкторов, для работы DI.

Комментарии:

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

2. Отличный ответ! Один жирный плюс за это.

3. 1) Не пытаюсь внедрить объекты / компоненты пользовательского интерфейса, но сервисы в эти объекты. В настоящее время я работаю с проектом, в котором эти классы извлекают зависимости из синглтона. Это будет сложно переписать. 2) Я понимаю, что внедрение зависимостей и шаблон factory не связаны. (спасибо за покровительственное объяснение названия autofac) Я надеялся использовать функцию фабрики делегирования autofac для создания фабрики для меня. 3) Я не имел права голоса при первоначальном проектировании проекта. Существуют компоненты пользовательского интерфейса со сложным деревом наследования.

4. Опять же, я повторяю, что AutoFac в конкретных фреймворках и dependency injection framework в целом не являются подходящим инструментом для модернизации плохо спроектированного устаревшего приложения, которое содержит такие антипаттерны, как, например, службы, внедренные в объекты, или отсутствие гарантированных инвариантов.

Ответ №2:

В приведенном вами примере кода вы, похоже, запрашиваете определенный тип, используя _myBaseFact<DerivedA>() и _myBaseFact<DerivedB>() . Трудно понять, чего вы пытаетесь достичь, но мне кажется, что это пахнет кодом, потому что у вас все еще есть зависимость от конкретных типов.

Как я вижу, у вас есть два варианта: либо вы вводите фабрику и абстрагируетесь от производных типов, либо вы непосредственно вводите производные типы в конструктор. Использование фабрики полезно в двух сценариях:

1. У вас есть какой-то аргумент (не основанный на типе), который позволяет фабрике идентифицировать возвращаемый тип, как показано в следующем примере:

 public class MyBaseFactory : IMyBaseFactory
{
    public MyBase CreateMyBase(IUserContext user)
    {
        // Create a MyBase based on a user object.
    }
}
  

2. Ваша фабрика имеет несколько заводских методов:

 public class MyBaseFactory : IMyBaseFactory
{
    private Autofac.IContainer container;

    public MyBase CreateMyBaseForScenarioA()
    {
        return container.Resolve<DerivedA>();
    }

    public MyBase CreateMyBaseForScenarioB()
    {
        return container.Resolve<DerivedB>();
    }
}
  

Обратите внимание, что оба заводских метода возвращают MyBase и никогда конкретный тип. Вся идея полиморфизма заключается в том, что вы не должны заботиться о конкретном типе.

ИМО, не имеет значения, может ли Autofac создать фабрику для вас или нет. Вы не должны полагаться на подобные конструкции, специфичные для фреймворка, но зависеть от хорошего дизайна приложения. Если вы выберете фабричный подход, определите интерфейс для этой фабрики и внедрите экземпляр на основе этого интерфейса. Это делает дизайн очень чистым и очень хорошо передает его предназначение.

Комментарии:

1. Думаю, я хочу позволить людям создавать и создавать экземпляры своих собственных подклассов без необходимости добавлять их в специальный MyBaseFactory. Я надеялся использовать универсальный фабричный метод и сканирование сборки autofac. Вероятно, это будет тот случай, когда они захотят получить доступ к подклассу напрямую и не должны приводить базу.

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

3. @Nick Sonneveld: Похоже, вы очень смущены тем, как работает полиморфизм, какова цель шаблона factory и в чем смысл внедрения зависимостей. По сути, вы злоупотребляете AutoFac … это не то, для чего он предназначен.

4. @Nick: Возможно, если вы попытаетесь объяснить, что вы на самом деле пытаетесь здесь сделать, мы сможем дать вам несколько советов. Если возможно, дополните свой ответ более конкретным примером. Что MyBase на самом деле представляет? Что делают производные типы? Какова функция SomethingElse и что она делает с MyBase производными типами?

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