Нарушение принципов SOLID в множественной реализации интерфейса

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

    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 класс ломаетсяОткрытый закрытый принцип так и ломаетсяПринцип обращения зависимостей

А потом

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 класс как ниже

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.

 Callum Linington20 июл. 2016 г., 10:24
Мне кажется, что ты не хочешь фабрику. На самом деле, вы хотите шаблон стратегии. Тем не менее, если вы делаете[Dependency("DB")] Вы связываете себя с вашей структурой DI, которая тоже не идеальна.
 plalx21 июл. 2016 г., 06:14
ЗачемSaveDataFlow нужно отработать строковые типы ввода? Разве вы не можете просто ввести правильного писателя? Что касается фабрики, если вы хотите сделать ее расширяемой, вы можете использовать отражение для сканирования сборок и найти всех разработчиковIWriter для того, чтобы зарегистрировать их автоматически.

Ответы на вопрос(3)

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.

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

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: Вы можете ввести их оба, а затем использовать тот, который вы хотите, используя enum.

ConsЯ думаю, это как бы нарушает принцип OCP так же, как в вашем первом примере.

Подробнее о шаблоне стратегии вэтот отличный ответ от Марка Симанна.

3. Постройте фабрику, которая создает предметы на основе функции.

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

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: Перемещает всю зависимость к регистрации.

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

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

Похожий вопрос здесь:Внедрить требуемый объект зависит от условия в инжекторе конструктора

Решение Вопроса

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

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>();
 Callum Linington20 июл. 2016 г., 10:52
Я установил версию 4. Этот код был взят из работающей консольной программы
 Riki20 июл. 2016 г., 12:58
@CallumLinington Прежде всего, спасибо за решение. У меня есть маленький вопрос, реализацииIWriterинтерфейс (экв.DbWriter а такжеFileWriter) имеет 2 обязанности. Я просто пытался сохранить единственную ответственность.
 Callum Linington20 июл. 2016 г., 10:53
Ну, я запустил его снова и получил исключение, поэтому изменится
 smoksnes20 июл. 2016 г., 10:58
Да, я знаю. Это странно. Но почему-то Unity изначально понимает массивы, но не IEnumerable. Теперь ваш пример очень похож на мой вариант 2, но со строкой вместо enum.
 Callum Linington20 июл. 2016 г., 10:56
Почему это будет работать сType[] скорее, чемIEnumerable<Type> странно, видя, как массив наследуетIEnumerable
 Callum Linington20 июл. 2016 г., 11:03
Я обновил ответ с расширением, которое показывает, как обойти вас, явно набрав именованную часть!
 smoksnes20 июл. 2016 г., 10:53
Здорово. Я никогда не заставлял это работать. Вот почему я предложил массив в моей альтернативе 2 выше. Отличное решение!
 Callum Linington20 июл. 2016 г., 13:01
У них нет двух обязанностей ... У него есть только 1, он напишет в нужное место. Просто потому, что есть метод, который заявляет, может ли он обрабатывать определенный тип, на самом деле не другая ответственность. Вы должны помнить, что SOLID - это набор принципов. Не установлены строгие правила
 Callum Linington20 июл. 2016 г., 10:56
Лично я держусь подальше от Unity. Это работает в Autofac, StructureMap, Ninject без именованных зависимостей иIEnumerable<Type>
 smoksnes20 июл. 2016 г., 10:47
Unity понимает, что делать, когда вы используете IEnumerable <IWriter> писатели? В предыдущих версиях вам нужно было использовать массив.

Выберите до создания экземпляра и используйте области

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];
}
 Callum Linington20 июл. 2016 г., 10:22
У вас не было бы словаря шаблонов состояний, вы просто связали бы их все в структуре зависимостей и прошли бы через конструкторIEnumerable<IWriter>
 Aron20 июл. 2016 г., 10:25
@CallumLinington Вы бы. Но затем вы используете словарь для сопоставления метаданных с реальной стратегией. В качестве альтернативы вы можете использовать положение вList... Но да. Я буду участвовать вChooser учебный класс.

Ваш ответ на вопрос