什么是一个接一个地使用多种策略且可扩展的好模式

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