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

本文关键字:一个 策略 是什么 模式 可扩展 | 更新日期: 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));