在接口的多重实现中打破 SOLID 原则
Breaking SOLID Principles in multiple implementation of an Interface
我在 factory
方法中遇到依赖倒置问题,它也违反了开闭原则。我的代码看起来像下面的代码
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
}
}
现在我使用工厂 class 来解决对象创建问题。它看起来像下面的代码
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
class 打破了 开闭原则 所以它也打破了 依赖倒置原则
然后
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);
}
}
由于上面的工厂 class 正在打破依赖倒置,我删除了 Factory
class 并更改 SaveDataFlow
class 如下所示
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");
然而最终我还是打破了开闭原则。
我需要更好的 design/solution 来解决这样的问题,但我必须遵循 SOLID 原则。
我倾向于使用其中一种方法。
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<IWriter> _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 this excellent answer 中有关策略模式的更多信息。
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);
}
}
优点:将整个依赖移动到注册。
缺点:服务定位器模式的包装器。可能有点难读。
None 上面的例子是完美的,因为每个例子都有其优缺点。
这里有类似的问题:
解决方案 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];
}
我只想把它变成一个策略模式:
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
作为如何 select 您的作者的实现,这应该只关心获得作者!这里的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>();
在.NET Core中(从问题中不清楚使用的是什么框架),您可以使用内置的DI以非常少的代码轻松实现策略模式。
在Startup.ConfigureServices
中:
services
.AddScoped<IWriter, FileWriter>()
.AddScoped<IWriter, DBWriter>()
.AddScoped<ISaveDataFlow, SaveDataFlow>();
为策略算法IWriter
添加一个方法:
public interface IWriter
{
bool CanWrite(string outputType);
void WriteToStorage(string data);
}
public class FileWriter : IWriter
{
bool CanWrite(string outputType) => outputType == "FILE";
public void WriteToStorage(string data) {}
}
public class DBWriter : IWriter
{
bool CanWrite(string outputType) => outputType == "DB";
public void WriteToStorage(string data) {}
}
然后将SaveDataFlow
的构造函数改为使用集合类型,将SaveData
改为调用所有已解析的IWriter
类型的算法方法
public class SaveDataFlow : ISaveDataFlow
{
private readonly IWriter _writers;
public SaveDataFlow(IEnumerable<IWriter> writers)
{
_writers= writers;
}
public void SaveData(string data, string outputType)
{
_writers.Single(w => w.CanWrite(outputType)).WriteToStorage(data);
}
}
这符合 Open/Closed 原则,因为具体选择仅在具体 类 本身内。
我在 factory
方法中遇到依赖倒置问题,它也违反了开闭原则。我的代码看起来像下面的代码
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
}
}
现在我使用工厂 class 来解决对象创建问题。它看起来像下面的代码
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
class 打破了 开闭原则 所以它也打破了 依赖倒置原则
然后
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);
}
}
由于上面的工厂 class 正在打破依赖倒置,我删除了 Factory
class 并更改 SaveDataFlow
class 如下所示
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");
然而最终我还是打破了开闭原则。 我需要更好的 design/solution 来解决这样的问题,但我必须遵循 SOLID 原则。
我倾向于使用其中一种方法。
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<IWriter> _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 this excellent answer 中有关策略模式的更多信息。
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);
}
}
优点:将整个依赖移动到注册。
缺点:服务定位器模式的包装器。可能有点难读。
None 上面的例子是完美的,因为每个例子都有其优缺点。
这里有类似的问题:
解决方案 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];
}
我只想把它变成一个策略模式:
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
作为如何 select 您的作者的实现,这应该只关心获得作者!这里的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>();
在.NET Core中(从问题中不清楚使用的是什么框架),您可以使用内置的DI以非常少的代码轻松实现策略模式。
在Startup.ConfigureServices
中:
services
.AddScoped<IWriter, FileWriter>()
.AddScoped<IWriter, DBWriter>()
.AddScoped<ISaveDataFlow, SaveDataFlow>();
为策略算法IWriter
添加一个方法:
public interface IWriter
{
bool CanWrite(string outputType);
void WriteToStorage(string data);
}
public class FileWriter : IWriter
{
bool CanWrite(string outputType) => outputType == "FILE";
public void WriteToStorage(string data) {}
}
public class DBWriter : IWriter
{
bool CanWrite(string outputType) => outputType == "DB";
public void WriteToStorage(string data) {}
}
然后将SaveDataFlow
的构造函数改为使用集合类型,将SaveData
改为调用所有已解析的IWriter
类型的算法方法
public class SaveDataFlow : ISaveDataFlow
{
private readonly IWriter _writers;
public SaveDataFlow(IEnumerable<IWriter> writers)
{
_writers= writers;
}
public void SaveData(string data, string outputType)
{
_writers.Single(w => w.CanWrite(outputType)).WriteToStorage(data);
}
}
这符合 Open/Closed 原则,因为具体选择仅在具体 类 本身内。