0

У меня возникла проблема с инверсией зависимостей в методе factory, а также нарушена принцип открытого закрывания. Мой код выглядит следующим образом:Нарушение принципов SOLID при множественном внедрении интерфейса

public interface IWriter 
    { 
     void WriteToStorage(string data); 
    } 

    public class FileWriter : IWriter 
    { 
     public void WriteToStorage(string data) 
     { 
      //write to file 
     } 
    } 

    public class DBWriter : IWriter 
    { 
     public void WriteToStorage(string data) 
     { 
      //write to DB 
     } 
    } 

Теперь я использую фабричный класс для решения задачи создания объекта. Он похож ниже код

public interface IFactory 
{ 
    IWriter GetType(string outputType); 
} 

public class Factory : IFactory 
{ 
    public IWriter GetType(string outputType) 
    { 
     IWriter writer = null; 
     if (outputType.Equels("db")) 
     { 
      writer = new FileWriter(); 
     } 
     else if (outputType.Equels("db")) 
     { 
      writer = new DBWriter(); 
     } 
    } 
} 

Теперь проблема заключается в Factory класс нарушает Открытый закрыт принцип так и ломает Dependency Inversion Принцип

А потом

public interface ISaveDataFlow 
{ 
    void SaveData(string data, string outputType); 
} 

public class SaveDataFlow : ISaveDataFlow 
{ 
    private IFactory _writerFactory = null; 
    public SaveDataFlow(IFactory writerFactory) 
    { 
     _writerFactory = writerFactory; 
    } 
    public void SaveData(string data, string outputType) 
    { 
     IWriter writer = _writerFactory.GetType(outputType); 
     writer.WriteToStorage(data); 
    } 
} 

Как над классом фабрики нарушает инверсию зависимостей. Я удаляю класс Factory и меняю класс SaveDataFlow как belo ж

public class SaveDataFlow : ISaveDataFlow 
{ 
    private IWriter _dbWriter = null; 
    private IWriter _fileWriter = null; 
    public SaveDataFlow([Dependency("DB")]IWriter dbWriter, 
         [Dependency("FILE")]IWriter fileWriter) 
    { 
     _dbWriter = dbWriter; 
     _fileWriter = fileWriter; 
    } 
    public void SaveData(string data, string outputType) 
    { 
     if (outputType.Equals("DB")) 
     { 
      _dbWriter.WriteToStorage(data); 
     } 
     else if (outputType.Equals("FILE")) 
     { 
      _fileWriter.WriteToStorage(data); 
     } 
    } 
} 

И решить эти зависимости с помощью Unity Framework

container.RegisterType<IWriter, DBWriter>("DB"); 
container.RegisterType<IWriter, FileWriter>("FILE"); 

Но в конце концов, я в конечном итоге нарушение Открытый Закрытый принцип. Мне нужно лучшее решение для решения этой проблемы, но я должен следовать принципам SOLID.

+2

Мне кажется, что вы не хотите завод. На самом деле вам нужен шаблон стратегии. Однако, если вы выполняете '[Dependency (« DB »)], вы связываете себя с картой DI, которая также не идеальна. –

+0

Почему 'SaveDataFlow' должен работать со строковыми типами ввода? Не можете ли вы просто вставить надлежащего писателя? Что касается фабрики, если вы хотите сделать ее расширяемой, вы можете использовать отражение для сканирования сборок и найти всех разработчиков «IWriter», чтобы зарегистрировать их автоматически. – plalx

ответ

2

Я бы просто превратить его в шаблон стратегии:

namespace UnityMutliTest 
{ 
    using System; 
    using System.Collections.Generic; 
    using System.Linq; 

    using Microsoft.Practices.Unity; 

    class Program 
    { 
     static void Main(string[] args) 
     { 
      IUnityContainer container = new UnityContainer(); 

      container.RegisterType<IWriter, FileWriter>("file"); 
      container.RegisterType<IWriter, DbWriter>("db"); 

      container.RegisterType<IWriterSelector, WriterSelector>(); 

      var writerSelector = container.Resolve<IWriterSelector>(); 

      var writer = writerSelector.SelectWriter("FILE"); 

      writer.Write("Write me data"); 

      Console.WriteLine("Success"); 

      Console.ReadKey(); 
     } 
    } 

    interface IWriterSelector 
    { 
     IWriter SelectWriter(string output); 
    } 

    class WriterSelector : IWriterSelector 
    { 
     private readonly IEnumerable<IWriter> writers; 

     public WriterSelector(IWriter[] writers) 
     { 
      this.writers = writers; 
     } 

     public IWriter SelectWriter(string output) 
     { 
      var writer = this.writers.FirstOrDefault(x => x.CanWrite(output)); 

      if (writer == null) 
      { 
       throw new NotImplementedException($"Couldn't find a writer for {output}"); 
      } 

      return writer; 
     } 
    } 

    interface IWriter 
    { 
     bool CanWrite(string output); 

     void Write(string data); 
    } 

    class FileWriter : IWriter 
    { 
     public bool CanWrite(string output) 
     { 
      return output == "FILE"; 
     } 

     public void Write(string data) 
     { 
     } 
    } 

    class DbWriter : IWriter 
    { 
     public bool CanWrite(string output) 
     { 
      return output == "DB"; 
     } 

     public void Write(string data) 
     { 
     } 
    } 
} 

Вы можете иметь столько IWriter сек, как вы хотите, просто зарегистрировать их:

container.RegisterType<IWriter, LogWriter>("log"); 

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

Вы используете (плохо названный) IWriterSelector как реализацию того, как выбрать своего автора, это должно касаться только получения писателя! Исключение составляет throw. Это действительно полезно, оно будет неудачно быстро, если нет реализации, которая соответствует вашим потребностям !!

Если у вас когда-либо были проблемы с Open Closed, используйте стратегии «Стратегия» или «Шаблон» для преодоления.

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

Я создал немного метод расширения, чтобы не приходилось называть экземпляры:

static class UnityExtensions 
{ 
    public static void RegisterMultipleType<TInterface, TConcrete>(this IUnityContainer container) 
    { 
     var typeToBind = typeof(TConcrete); 
     container.RegisterType(typeof(TInterface), typeToBind, typeToBind.Name); 
    } 
} 

container.RegisterMultipleType<IWriter, FileWriter>(); 
+0

Способствует ли Unity понять, что нужно вводить, когда вы используете IEnumerable авторов? В предыдущих версиях вам нужно было использовать массив. – smoksnes

+0

Я установил версию 4. Этот код был взят из управляемой консольной программы –

+0

Прохладный. Мне никогда не приходилось работать. Вот почему я предложил массив в моей альтернативе 2 выше. Отличное решение! – smoksnes

1

Я стараюсь использовать один из этих подходов.

1. Перерыв в различные интерфейсы

public interface IWriter 
{ 
    void WriteToStorage(string data); 
} 

public interface IFileWriter : IWriter 
{ 
} 

public interface IDBWriter: IWriter 
{ 
} 

public class FileWriter : IFileWriter 
{ 
    public void WriteToStorage(string data) 
    { 
     //write to file 
    } 
} 

public class DBWriter : IDBWriter 
{ 
    public void WriteToStorage(string data) 
    { 
     //write to DB 
    } 
} 

Pros: Вы можете придать правильную реализацию на основе интерфейса, который не нарушает OCP.

Против: У вас есть пустые интерфейсы.


2. Используйте перечисление для их разделения (стратегия шаблон)

public interface IWriter 
{ 
    void WriteToStorage(string data); 
    StorageType WritesTo { get; } 
} 

public enum StorageType 
{ 
    Db = 1, 
    File = 2 
} 

public class Factory : IFactory 
{ 
    public IEnumerable<IWriter> _writers; 

    public Factory(IWriter[] writers) 
    { 
     _writers = writers; 
    } 

    public IWriter GetType(StorageType outputType) 
    { 
     IWriter writer = _writers.FirstOrDefault(x => x.WritesTo == outputType); 
     return writer; 
    } 
} 

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

Против: Я предполагаю, что это нарушает принцип OCP так же, как в вашем первом примере.

Подробнее о стратегии шаблон в this excellent answer от Марка Seemann.


3. Построить завод, который создает элементы, основанные на FUNC.

В вашей регистрации:

container.RegisterType<IWriter, DBWriter>("DB"); 
container.RegisterType<IWriter, FileWriter>("FILE"); 
container.RegisterType<IFactory, Factory>(
    new ContainerControlledLifetimeManager(), 
    new InjectionConstructor(
     new Func<string, IWriter>(
      writesTo => container.Resolve<IWriter>(writesTo)); 

И ваша фабрика

public class Factory : IFactory 
{ 
    private readonly Func<string, IWriter> _createFunc; 

    public Factory(Func<string, IWriter> createFunc) 
    { 
     _createFunc = createFunc; 
    } 

    public IWriter CreateScope(string writesTo) 
    { 
     return _createFunc(writesTo); 
    } 
} 

Pros: перемещает всю зависимость к регистрации.

Против: Обертка для шаблона сервис-локатор. Может быть немного трудно читать.


Ни один из приведенных выше примеров не является совершенным, поскольку каждый из них имеет свои плюсы и минусы.

вопрос здесь похож: Inject require object depends on condition in constructor injection

1

Решение 1

Выберите Перед инстанцировании и использования областей

using(var scope = new Scope(unity)) 
{ 
    scope.register<IWriter, ConcreteWriter>(); 
    var flow = scope.Resolve<ISaveDataFlow>(); 

} 

Решение 2

Внедрить стратегию во время выполнения.

ISaveDataFlow flow = .... 
IWriter writer = GetWriterBasedOnSomeCondition(); 
flow.SaveData(data, writer); 

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

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

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

Например

public interface IWriter 
{ 
    void WriteData(data); 
    string Name {get;} 
} 

void GetWriterBasedOnSomeCondition() 
{ 
    Dictionary<string, IWriter> writers = ...ToDictionary(x => x.Name); 
    var choice = Console.ReadLine(); 
    return writers[choice]; 
} 
+0

У вас не было бы словаря шаблонов stategy, вы просто привязывали бы их все в структуре зависимостей и проходили через конструктор 'IEnumerable ' –

+0

@CallumLinington You would. Но затем вы используете словарь для отображения обратно метаданных в фактическую стратегию. В качестве альтернативы вы можете использовать позицию в 'List' ... Но да. DI будет участвовать в классе «Chooser». – Aron

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