一个接一个地使用多个可扩展策略的好模式是什么?
本文关键字:一个 策略 是什么 模式 可扩展 | 更新日期: 2023-09-27 17:50:55
我有一个类,需要获得一些数据来执行分析。假设获取数据的接口如下:
public interface IDataFetcher
{
List<someobject> GetData();
}
在一个非常简单的例子中,我的类将在它的一个方法中使用如下接口:
void PerformAnalysis(List<IDataFetcher> fetchers)
{
...
foreach(IDataFetcher fetcher in fetchers)
{
List<someobject> myList = fetcher.GetData();
//We will try fetching one by one using different fetchers until we get the data
if(myList.Count > 0)
break;
}
...
}
现在,不同的抓取实现,如从文件抓取,从机器抓取或从数据库抓取,为他们的数据源采取不同的输入,例如,文件抓取将需要文件路径,机器抓取将需要机器名称和数据库抓取将需要数据库字符串。
在我的情况下,这个源信息只能在运行时(从用户输入或其他来源)在上面的PerformAnalysis方法中获知。所以,现在我不能传递IDataFetchers,因为源是未知的。
我修改的方法是不从外部执行实例化,而是通过创建一个抽象工厂来延迟它,如下所示:
public interface IDataFetcherAbstractFactory
{
IDataFetcher CreateFetcher(string source);
}
public interface FileDataFetcherFactory : IDataFetcherAbstractFactory
{
IDataFetcher CreateFetcher(string source)
{
return new FileDataFetcher(source);
}
}
类似地,不同的获取器也会做同样的事情,比如MachineDataFetcherFactory等。FileDataFetcher的一种实现可以通过更新Unity Container XML配置中的几个标签而与另一种实现进行交换,而无需修改源代码。这很好。现在,我更新我的方法如下:
void PerformAnalysis (List<IDataFetcherAbstractFactory> fetcherFactories)
{
...
string source = GetSource(); //source known dynamically
foreach(IDataFetcherAbstractFactory factory in fetcherFactories)
{
IDataFetcher fetcher = factory.Create(source);
List<someobject> myList = fetcher.GetData();
//We will try fetching one by one using different fetchers until we get the data
if(myList.Count > 0)
break;
}
...
}
a)这种使用工厂的方法是正确的还是有更好的方法?
b)我观察到的第二个问题是,对于每个工厂的产品,源字符串可能不同。例如,对于数据库工厂源字符串是连接字符串,对于机器是机器名称等。这意味着我的类必须知道它正在处理的是哪个工厂。可以让它知道吗?
c)在不更新源的情况下,可以使用unity将新工厂以某种方式传递/注入到effeccherfactories列表中吗?例如,有人实现了一个新的WebServiceFetcher: IDataFetcher和一个相应的工厂。现在,为了让我的框架利用它,我必须修改源代码,将它添加到fetcherFactories列表中。这听起来不可扩展。
谢谢
也许我误解了你的问题,但我会尽量提供一个答案:
声明用户输入值的运行时数据:
public interface IRuntimeData
{
string filePath { get; set; }
string connectionString { get; set; }
string machineName { get; set; }
}
class RuntimeData : IRuntimeData
{
public string filePath { get; set; }
public string connectionString { get; set; }
public string machineName { get; set; }
}
声明数据获取器和实现的接口。这些类需要IRuntimeData才能工作。
interface IDataFetcher
{
object getData();
}
class FileFetcher : IDataFetcher
{
private string _filePath;
public FileFetcher(IRuntimeData userInputData)
{
_filePath = userInputData.filePath;
}
public object getData()
{
return "Hello from FileFetcher. File path is " + _filePath;
}
}
class DBFetcher : IDataFetcher
{
private string _connStr;
public DBFetcher(IRuntimeData userInputData)
{
_connStr = userInputData.connectionString;
}
public object getData()
{
return "Hello from DBFetcher. Connection string is " + _connStr;
}
}
class MachineFetcher : IDataFetcher
{
private string _machineName;
public MachineFetcher(IRuntimeData userInputData)
{
_machineName = userInputData.machineName;
}
public object getData()
{
return "Hello from MachineFetcher. Machine name is " + _machineName;
}
}
声明分析器类。这个类需要一个IDataFetcher列表。
class Analyzer
{
private List<IDataFetcher> _fetcherList;
public Analyzer(IDataFetcher[] fetcherList)
{
_fetcherList = new List<IDataFetcher>(fetcherList);
}
public void PerformAnalysis()
{
foreach (IDataFetcher dtFetcher in _fetcherList)
{
Console.WriteLine(dtFetcher.getData());
}
}
}
现在,在容器中注册Datafetchers。
IUnityContainer container = new UnityContainer();
container.RegisterType<IDataFetcher, FileFetcher>("file");
container.RegisterType<IDataFetcher, DBFetcher>("db");
container.RegisterType<IDataFetcher, MachineFetcher>("machine");
当用户插入运行时数据时,创建一个实例并注册到容器中:
IRuntimeData rtData = new RuntimeData();
rtData.connectionString = "Persist Security Info=False;Integrated Security=true;Initial Catalog=Northwind;server=(local)";
rtData.filePath = @"C:'foo.txt";
rtData.machineName = "jlvaqueroMachine";
container.RegisterInstance<IRuntimeData>(rtData);
最后一部分是通过容器解析Analyzer:
Analyzer myAnalyzer = container.Resolve<Analyzer>();
myAnalyzer.PerformAnalysis();
Console.Read();
你可以看到在容器中注册的所有DataFetcher是如何被创建并注入到Analyzer中的。
完整的例子在这里。
PD:如果runTimeData的RegisterInstance
看起来像服务定位器反模式;可以解决Analyzer
覆盖rununtimedata依赖:
IRuntimeData rtData = new RuntimeData();
rtData.connectionString = "Persist Security Info=False;Integrated Security=true;Initial Catalog=Northwind;server=(local)";
rtData.filePath = @"C:'foo.txt";
rtData.machineName = "jlvaqueroMachine";
Analyzer myAnalyzer = container.Resolve<Analyzer>(new DependencyOverride<IRuntimeData>(rtData));