在多个接口实现中打破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
作为如何选择你的作家的实现,这应该只关心得到一个作家! 这里的throw
exception非常有用,如果没有适合您需求的实现,它将很快失败!
如果您遇到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对象取决于构造函数注入中的条件