如何将依赖项注入应用于抽象工厂

本文关键字:应用于 抽象 工厂 注入 依赖 | 更新日期: 2023-09-27 18:22:21

我刚刚完成了Mark Seemann的《.NET依赖注入》一书,现在我正在尝试重构一些遗留代码。(在这个阶段,我不依赖任何特定的DI容器,而只是试图将所有的依赖关系转移到一个地方)。

我看到的是下面的工厂类,它通过用archiveReader.GetArchiveType()读取存档的前几个字节来确定ArchiveType,然后返回基于ArchiveType枚举的ArchiveRestorer的实例。

public class ArchiveRestorerFactory : IArchiveRestorerFactory
{
    public ArchiveRestorer Create(ArchiveReader archiveReader)
    {
        ArchiveType type = archiveReader.GetArchiveType();
        switch (type)
        {
            case ArchiveType.CurrentData:
                return new CurrentDataArchiveRestorer(archiveReader);
                break;
            case ArchiveType.HistoricalData:
                return new HistoricalDataArchiveRestorer(archiveReader);
                break;
            case ArchiveType.AuditTrail:
                return new AuditTrailArchiveRestorer(archiveReader);
                break;
            default:
                throw new Exception("ArchiveRestorerFactory error: Unknown value for ArchiveType.");
        }
    }
}

如何重构它,使类不依赖于具体类型CurrentDataArchiveRestorerHistoricalDataArchiveRestorerAuditTrailArchiveRestorer

我应该把三个混凝土修复师调到工厂的建造师那里吗?

public ArchiveRestorer Create(ArchiveReader archiveReader, 
    ArchiveRestorer currentDataArchiveRestorer, 
    ArchiveRestorer historicalDataArchiveRestorer, 
    ArchiveRestorer auditTrailDataArchiveRestorer)
{
    // guard clauses...
    // assign to readonly fields
}

这似乎是这里建议的方法,但当只需要一个恢复程序时,它会实例化所有三个恢复程序吗?如果我有20种不同的可能的具体实现呢?

我觉得我应该为每种类型的修复程序实现一个混凝土工厂,然后返回,但我只会用另一个new替换一个。

重构它的最佳方法是什么?

如何将依赖项注入应用于抽象工厂

考虑到您已经获得的代码,我这样做的方法是为这些对象中的每一个创建一个具有Create()方法的工厂。

我也会为这些工厂提供一个接口,并让它们继承通用工厂接口。

然后,您可以使用这些接口作为注入构造函数的点。

这将被称为类似于此:

case ArchiveType.CurrentData:
                return _currentDateArchiveRestorerFactory.Create(archiveReader);
                break;

或者,最好有一个单独的工厂来创建给定类型的实例。由于所有这些对象都是恢复器,您可以仅基于enum而不是switch创建实例。

_restorerFactory.Create(ArchiveType.CurrentData);

为什么不让ArchiveReader负责创建适当的ArchiveRestorer?然后代码的第一次迭代看起来是这样的:

public class ArchiveRestorerFactory : IArchiveRestorerFactory
{
    public ArchiveRestorer Create(ArchiveReader archiveReader)
    {
        ArchiveRestorer restorer = archiveReader.GetArchiveRestorer();
        return restorer;
    }
}

到那时,很明显工厂是多余的,所以在代码的第二次迭代中,你可以把它扔掉,让消费者直接调用ArchiveReader。

用一个方法创建一个接口,并让三个archiver类实现该接口,然后在create方法中,参数类型将只是接口,它将通过调用您刚刚创建的接口的方法来返回所需的对象。所以在create方法中不需要具体的类型。

interface ILogger
{
    void Log(string data);
}
class Logger : ILogger
{
    .
    .
    .
}

此时,您使用一个中间工厂对象来返回要在组件中使用的记录器:

class MyComponent
{
  void DoSomeWork()
  {
    // Get an instance of the logger
    ILogger logger = Helpers.GetLogger();
    // Get data to log
    string data = GetData();
    // Log
    logger.Log(data);
  }
}
class Helpers
{
  public static ILogger GetLogger()
  {
    // Here, use any sophisticated logic you like
    // to determine the right logger to instantiate.
    ILogger logger = null;
    if (UseDatabaseLogger)
    {
        logger = new DatabaseLogger();
    }
    else
    {
        logger = new FileLogger();
    }
    return logger;
  }
}
class FileLogger : ILogger
{
    .
    .
    .
}
class DatabaseLogger : ILogger
{
    .
    .
    .
}

我将通过商定命名约定并利用Unity命名注册的能力来解决这个问题。此处的示例:https://dannyvanderkraan.wordpress.com/2015/06/29/real-world-example-of-dependency-injection-based-on-run-time-values/