在多个接口实现中打破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("DB"); container.RegisterType("FILE"); 

但最终我最终打破了开放封闭原则 。 我需要一个更好的设计/解决方案来解决这个问题,但我必须遵循SOLID原则。

我只想把它变成一种策略模式:

 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("file"); container.RegisterType("db"); container.RegisterType(); var writerSelector = container.Resolve(); 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 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("log"); 

如果你愿意,你甚至可以在作者身上实现装饰器。

你使用(命名很差的) IWriterSelector作为如何选择你的作家的实现,这应该只关心得到一个作家! 这里的throwexception非常有用,如果没有适合您需求的实现,它将很快失败!

如果您遇到Open Closed问题,请使用策略或模板模式来克服。

我一直使用这种模式,效果很好。

我创建了一个小扩展方法来防止你必须为你的实例命名:

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

解决方案1

在实例化之前选择并使用范围

 using(var scope = new Scope(unity)) { scope.register(); var flow = scope.Resolve(); } 

解决方案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 writers = ...ToDictionary(x => x.Name); var choice = Console.ReadLine(); return writers[choice]; } 

我倾向于使用这些方法之一。

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 } } 

优点 :您可以根据接口注入正确的实现,这不会破坏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 _writers; public Factory(IWriter[] writers) { _writers = writers; } public IWriter GetType(StorageType outputType) { IWriter writer = _writers.FirstOrDefault(x => x.WritesTo == outputType); return writer; } } 

优点 :你可以注入它们,然后使用枚举使用你想要的那个。

缺点 :我想它有点像你的第一个例子那样打破了OCP原则。

更多关于Mark Seemann的优秀答案中的策略模式。


3.构建一个基于func创建项目的工厂。

在您的注册中:

 container.RegisterType("DB"); container.RegisterType("FILE"); container.RegisterType( new ContainerControlledLifetimeManager(), new InjectionConstructor( new Func( writesTo => container.Resolve(writesTo)); 

和你的工厂

 public class Factory : IFactory { private readonly Func _createFunc; public Factory(Func createFunc) { _createFunc = createFunc; } public IWriter CreateScope(string writesTo) { return _createFunc(writesTo); } } 

优点 :将整个依赖项移动到注册。

缺点 :服务定位器模式的包装器。 可能有点难以阅读。


上面的例子都不是完美的,因为它们各有利弊。

这里有类似的问题: Inject require对象取决于构造函数注入中的条件