什么是一个接一个地使用多个策略的良好模式,也是可扩展的

时间:2015-05-26 19:01:10

标签: c# dependency-injection unity-container ioc-container factory

我有一个类需要获取一些数据来执行分析。假设获取数据的界面如下:

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列表中。这听起来不太可扩展。

由于

1 个答案:

答案 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));