什么是一个接一个地使用多种策略且可扩展的好模式
What is a good pattern for using multiple strategies one after another that is also extensible
我有一个 class 需要获取一些数据来执行分析。假设获取数据的接口如下:
public interface IDataFetcher
{
List<someobject> GetData();
}
在一个非常简单的情况下,我的 class 将在其方法之一中使用此接口,如下所示:
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;
}
...
}
现在,不同的抓取实现(例如从文件抓取、从机器抓取或从 dB 抓取)对其数据源采用不同的输入,例如File Fetcher 需要文件路径,machine fetcher 需要机器名,而 dB fetcher 需要数据库字符串。
在我的例子中,此源信息只能在运行时(从用户输入或其他来源)稍后在上面的 PerformAnalysis 方法中获知。所以,现在我无法通过 IDataFetcher,因为来源未知。
我修改它的方法是 不是 从外部执行实例化,而是通过创建一个抽象工厂来推迟它,如下所示:
public interface IDataFetcherAbstractFactory
{
IDataFetcher CreateFetcher(string source);
}
public interface FileDataFetcherFactory : IDataFetcherAbstractFactory
{
IDataFetcher CreateFetcher(string source)
{
return new FileDataFetcher(source);
}
}
同样,不同的 fetcher 会像 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) 这种使用 Factory 的方法是否正确,或者有更好的方法吗?
b) 我观察到的第二个问题是每个工厂产品的源字符串可能不同。即对于数据库工厂源字符串是连接字符串,对于机器它的机器名称等。这意味着我的 class 必须知道它正在处理的工厂。让它知道可以吗?
c) 在不更新源代码的情况下,新工厂能否以某种方式 passed/injected 使用 unity 进入 fectcherFactories 列表?
例如,有人实现了一个新的 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; }
}
声明数据获取器和实现的接口。 Theese classes 需要 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;
}
}
声明分析器class。 class 需要一个 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());
}
}
}
现在,在应用 bootstrap 的容器中注册 Datafetcher。
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 中的。
完整示例 here.
PD:如果 runTimeData 的 RegisterInstance
对您来说看起来像是服务定位器反模式;可以解决 Analyzer
覆盖 runtimeData 的依赖性:
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));
我有一个 class 需要获取一些数据来执行分析。假设获取数据的接口如下:
public interface IDataFetcher
{
List<someobject> GetData();
}
在一个非常简单的情况下,我的 class 将在其方法之一中使用此接口,如下所示:
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;
}
...
}
现在,不同的抓取实现(例如从文件抓取、从机器抓取或从 dB 抓取)对其数据源采用不同的输入,例如File Fetcher 需要文件路径,machine fetcher 需要机器名,而 dB fetcher 需要数据库字符串。
在我的例子中,此源信息只能在运行时(从用户输入或其他来源)稍后在上面的 PerformAnalysis 方法中获知。所以,现在我无法通过 IDataFetcher,因为来源未知。
我修改它的方法是 不是 从外部执行实例化,而是通过创建一个抽象工厂来推迟它,如下所示:
public interface IDataFetcherAbstractFactory
{
IDataFetcher CreateFetcher(string source);
}
public interface FileDataFetcherFactory : IDataFetcherAbstractFactory
{
IDataFetcher CreateFetcher(string source)
{
return new FileDataFetcher(source);
}
}
同样,不同的 fetcher 会像 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) 这种使用 Factory 的方法是否正确,或者有更好的方法吗?
b) 我观察到的第二个问题是每个工厂产品的源字符串可能不同。即对于数据库工厂源字符串是连接字符串,对于机器它的机器名称等。这意味着我的 class 必须知道它正在处理的工厂。让它知道可以吗?
c) 在不更新源代码的情况下,新工厂能否以某种方式 passed/injected 使用 unity 进入 fectcherFactories 列表? 例如,有人实现了一个新的 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; }
}
声明数据获取器和实现的接口。 Theese classes 需要 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;
}
}
声明分析器class。 class 需要一个 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());
}
}
}
现在,在应用 bootstrap 的容器中注册 Datafetcher。
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 中的。
完整示例 here.
PD:如果 runTimeData 的 RegisterInstance
对您来说看起来像是服务定位器反模式;可以解决 Analyzer
覆盖 runtimeData 的依赖性:
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));