2

Я много читал об этой теме, но не мог понять ее полностью.C# - использование инъекции зависимостей (ninject) вместо заводского шаблона

Я пытаюсь использовать Ninject.Extensions.Factory вместо моей фабрики для создания новых объектов в зависимости от ввода пользователем. Я хочу полностью использовать функциональность Ninject и концепцию IoC.

Теперь код выглядит следующим образом:

interface IFeatureFactory 
{ 
    IFeature createFeature(int input); 
} 

и:

class BasicFeatureFactory : IFeatureFactory 
{ 
    public IFeature createFeature(int input) 
    { 
     switch (input) 
     { 
      case 1: 
       return new FirstFeature(); 
      case 2: 
       return new SecondFeature(); 
      default: 
       return null; 
     } 
    } 
} 

В будущем параметрических элементов будет иметь зависимости, так что я хочу сделать это IoC способ.

РЕДАКТИРОВАТЬ:

потребительского класса - The IFeatureFactory и IUILayer вводят в FeatureService конструктора и решены с помощью Ninject.

private IFeatureFactory featureFactory; 
    private IUILayer uiHandler; 

    public FeatureService(IFeatureFactory featureFactory, IUILayer uiHandler) 
    { 
     this.featureFactory = featureFactory; 
     this.uiHandler = uiHandler; 
    } 

    public void startService() 
    { 
     int userSelection = 0; 
     uiHandler.displayMenu(); 
     userSelection = uiHandler.getSelection(); 
     while (userSelection != 5) 
     { 
      IFeature feature = featureFactory.createFeature(userSelection); 
      if (feature != null) 
      { 
       IResult result = feature.execFeature(); 
       uiHandler.displayResult(result); 
      } 
      else 
      { 
       uiHandler.displayErrorMessage(); 
      } 
      uiHandler.displayMenu(); 
      userSelection = uiHandler.getSelection(); 
     } 
    } 

и параметрических элементов класса:

public interface IFeature 
{ 
    IResult execFeature(); 
} 

Наручники:

public override void Load() 
    { 
     Bind<IFeatureFactory>().To<BasicFeatureFactory>(); 
     Bind<IUILayer>().To<ConsoleUILayer>(); 
    } 

Как я могу преобразовать этот Factory Pattern в IoC с Ninject.Extensions.Factory? имейте в виду создание IFeature зависит от пользовательского ввода.

+0

Можете ли вы показать, как потребители «IFeatureFactory» и «IFeature» будут использовать эти абстракции? – Steven

+0

Итак, что является лучшим решением для этого? это хорошая идея использовать Ninject.Extensions.Factory? или оставить это так? как я могу использовать Ninject.Extensions.Factory для этого? Благодарю. –

+0

Я думаю, что этот вопрос слишком расплывчатый относительно того, какую проблему вы пытаетесь решить. Верно ли, что вы спрашиваете, чтобы помочь вам в будущем? Если это так, если на это ответят 10 человек, вы, вероятно, получите 12 разных мнений, основанных на том, что все думают, что это «правильный» способ справиться с будущей проверкой. Самое лучшее, что я могу сказать, - хорошо спроектировать ваши требования ** сначала **, а затем беспокоиться о том, как сделать поддержку ваших инструментов. –

ответ

2

Для меня это похоже, что у вас есть 2 варианта реорганизации кода, чтобы получить все преимущества ninject.
То, как вы работаете сейчас, ничем не отличается от pure di (что в этом нет ничего плохого, и это лучше в некоторых случаях), но, как вы сказали, вы хотите полностью использовать функциональность ninject.

Вариант один
Вместо введение IFeatureFactory в FeatureService впрыскивать интерфейс IFeatureProvider, который будет выглядеть следующим образом:

public interface IFeatureProvider 
{ 
    IFeature GetFeature(int featureType); 
} 

Теперь ваш FeatureService получит запрашиваемые характеристики этого поставщика вместо завода.
Вам нужно будет реализовать IFeatureProvider и для этого вам потребуется больше 2 интерфейса IFirstFeatureFactory и ISecondFeatureFactory:

public interface IFirstFeatureFactory 
{ 
    IFeature CreateFirstFeature(); 
} 

public interface ISecondFeatureFactory 
{ 
    IFeature CreateSecondFeature(); 
} 

И теперь IFeatureProvider impelementaion:

public class FeatureProvider: IFeatureProvider 
    { 
     private readonly IFirstFeatureFactory _firstFeatureFactory; 
     private readonly ISecondFeatureFactory _secondFeatureFactory; 

     public FeatureProvider(IFirstFeatureFactory firstFeatureFactory, ISecondFeatureFactory secondFeatureFactory) 
     { 
      _firstFeatureFactory=firstFeatureFactory; 
      _secondFeatureFactory=secondFeatureFactory; 
     } 

     public IFeautre GetFeature(int featureType) 
     { 
      switch(featureType) 
      { 
       case 1: 
        return _firstFeatureFactory.CreateFirstFeature(); 
       case 2: 
        return _secondFeatureFactory.CreateSecondFeature(); 
       default: 
        return null; 
      } 
     } 
    } 

вещь вы должны заметить, что я просто извлечь объект, который отвечает за «новый» в другой интерфейс.
Мы не будем реализовывать два заводских интерфейса, так как ninject сделает это для нас, если мы свяжем его должным образом.
Связывание:

Bind<IFeature>().ToFeature<FirstFeature>().NamedLikeFactoryMethod((IFirstFeatureFactory o) => o.CreateFirstFeature()); 
Bind<IFeature>().ToFeature<SecondFeature>().NamedLikeFactoryMethod((ISecondFeatureFactory o) => o.CreateSecondFeature()); 
Bind<IFirstFeatureFactory>().ToFactory(); 
Bind<ISecondFeatureFactory>().ToFactory(); 
Bind<IFeatureProvider>().To<FeatureProivder>(); 

Это «NameLikeFactoryMethod» Связывание эквивалент использование поименованые, как я сделал here и теперь the recommended way by ninject for factories.

Важно отметить, что вы не реализуете IFirstFeatureFactory и ISecondFeatureFactory самостоятельно, и для этого вы используете функциональность ninject.

Основным недостатком этой опции является то, что нам нужно добавить дополнительные функции, которые нам нужно будет создать, кроме самой функции, другой FeatureFactory, а также изменить FeatureProvider для ее обработки. Если функции не изменяются очень часто этот вариант может быть хорошим и простым, но если они делают это может стать поддержание кошмар, и именно поэтому я предлагаю вариант 2.

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

public interface IFeatureFactory 
{ 
    IFeature CreateFeature(string featureName); 
} 

Мы не будем реализовывать этот интерфейс от себя, и пусть Ninject сделать это для нас, однако мы должны сказать Ninject использовать первый параметр CearteFeature для обнаружения, реализация которых для создания экземпляра (FirstFeatue или SecondFeature).
Для этого нам понадобится поставщик специальных экземпляров с таким же поведением, как и StandardInstanceProvider, используя другое соглашение, чтобы выбрать, какую реализацию выполнить для создания экземпляра (соглашение по умолчанию in this article).
К счастью, ninject показывает, как именно мы можем реализовать его очень быстро на UseFirstArgumentAsNameInstanceProvider.
Теперь связывание:

Bind<IFeature>().To<FirstFeature>().Named("FirstFeature"); 
Bind<IFeature>().To<FirstFeature>().Named("SecondFeature"); 
Bind<IFeatureFactory>().ToFactory(() => new UseFirstArgumentAsNameInstanceProvider()); 

Вещи, чтобы заметить здесь:

  • Мы не будем реализовывать интерфейс фабрики сами.
  • Мы используем именованное связывание для каждой реализации, и мы получим реализацию с фабрики в соответствии с именем featureName, которое мы перейдем к заводскому методу (именно поэтому я предпочитаю, чтобы параметр был строкой).
    Обратите внимание, что если вы передадите имя функции, которое не является «FirstFeature» или «SecondFeature», будет выбрано исключение.
  • Мы используем UseFirstArgumentAsNameInstanceProvider как наш поставщик экземпляров для нашего завода, как упоминалось ранее.

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

Conslucsion
При выборе одного из этих вариантов выше по сравнению с чистым ди мы получим преимущество позволяя Ninject ядра создать наш объект на заводах, а не делать все «новые» самостоятельно.
Хотя нет ничего плохого в том, что я не использую контейнер IoC, это может реально помочь нам в случаях, когда в реализациях IFeature имеется большой график зависимостей, поскольку ninject будет вводить их нам. Выполняя одну из этих опций, мы полностью используем функцию ninject без использования «локатора сервисов», который рассматривается как анти-шаблон.

+0

спасибо за этот очень подробный ответ. Я закончил использовать второй вариант, как вы предложили, но одна из проблем, которые у меня есть, - позволяет сказать, что у меня есть реализации для FirstFeature и SecondFeature, основанные на входах «1» и «2». чем пользователь вводит «3» - как я могу обрабатывать возвращаемый null для этой опции? потому что теперь приложение аварийно говорит: «Нет подходящих привязок, и тип не является самопереключаемым». спасибо много снова –

+0

Я имею в виду, единственный способ справиться с этой проблемой - проверить входные данные перед тем, как получить IFeature? или, возможно, у ninject есть способ справиться с такими проблемами. спасибо –

+0

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

1

Вы можете вставлять функции в конструктор BasicFeatureFactory.

class BasicFeatureFactory : IFeatureFactory 
{ 
    FirstFeature feature1; 
    SecondFeature feature2; 

    public BasicFeatureFactory(FirstFeature feature1, SecondFeature feature2) { 
     this.feature1 = feature1; 
     this.feature2 = feature2; 
    } 

    public IFeature createFeature(int input) { 
     switch (input) { 
      case 1: return this.feature1; 
      case 2: return this.feature2; 
      default: return null; 
     } 
    } 
} 
+0

Единственное, что я могу предложить, это изменить параметры c'tor на «IFeature» и использовать контекстные объекты привязки Ninject для определения того, что «FirstFeature» и «SecondFeature»: https://github.com/ninject/Ninject/wiki/Contextual-Binding –

+0

@JoeAmenta: Я бы сказал «простейшая вещь, которая могла бы работать» :). Вы также можете ввести ядро ​​и разрешить его из контейнера. – Steven

Смежные вопросы