我有一个类需要获取一些数据来执行分析。假设获取数据的界面如下:
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;
}
...
}
现在,不同的提取实现,例如从文件提取,从机器提取或从dB提取,对其数据源采用不同的输入,例如文件提取器需要文件路径,机器提取器需要机器名称,而dB提取器需要数据库字符串。
在我的情况下,此源信息只能在运行时(来自用户输入或其他来源)在上面的PerformAnalysis Method中知道。所以,现在我无法通过IDataFetchers,因为源不明。
我修改它的方法是不从外部执行实例化,但是通过创建一个Abstract工厂来推迟它,如下所示:
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)这种使用Factory的方法是正确的还是有更好的方法来做到这一点?
b)我观察到的第二个问题是每个工厂产品的源字符串可能不同。即对于数据库工厂源字符串是连接字符串,对于机器它的机器名等。这意味着我的类必须知道它正在处理什么工厂。是否可以让它知道?
c)如果不更新源代码,可以使用unity将新工厂以某种方式传递/注入fectcherFactories列表吗? 例如,有人为它实现了一个新的WebServiceFetcher:IDataFetcher和相应的工厂。现在,为了让我的框架使用它,我将不得不修改源代码以将其添加到fetcherFactories列表中。这听起来不太可扩展。
由于
答案 0 :(得分:2)
也许我对你问题中的某些事情有所了解,但我会尝试提供答案:
声明用户输入值的运行时数据:
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类需要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;
}
}
声明Analyzer类。这个类需要一个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());
}
}
}
现在,在app bootstrap的容器中注册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 myAnalyzer = container.Resolve<Analyzer>();
myAnalyzer.PerformAnalysis();
Console.Read();
您可以看到如何在容器中注册的所有DataFetcher都被注入到Analyzer中。
完整示例here。
PD:如果runTimeData的RegisterInstance
看起来像服务定位器反模式;可以解析Analyzer
覆盖runtimeData pependency:
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));