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

5
我刚刚完成了Mark Seemann的书Dependency Injection in .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替换了一个。

重构的最佳方式是什么?


1
我认为你的情景可能更适合使用责任链模式。请查看这个例子 ,了解在特定 DI 容器(Unity)中使用该模式的用法。 - Paolo Falabella
我不会以那种方式实现链的注册和组装,但这绝对是一种有效的方法。 - Sebastian Weber
除了使用“枚举”(多余?)之外,您当前的实现有什么特别困扰您的地方? - Mark Seemann
嗯,你可以把工厂实现放在组合根中,然后……或者你可以采用你概述的第二个选项。如果你有20个不同的实现,那么考虑注入一个列表以及一个规范或谓词,使你能够从该列表中选择一个实例……或将它们全部包装成一个复合体。 - Mark Seemann
@MarkSeeman:我想避免在只使用一个ArchiveRestorer子类时实例化所有子类。除非我漏掉了什么,如果我注入它们或者使用组合模式,所有三个子类都会被实例化。 - shamp00
显示剩余3条评论
5个回答

2

鉴于您已经拥有的代码,我会这样做:为每个对象创建一个工厂,并在其中添加一个Create()方法。

我还会为这些工厂创建一个接口,并让它们继承自一个通用的工厂接口。

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

调用方式类似于:

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

或者,最好拥有一个单一工厂来创建给定类型的实例。由于所有这些对象都是还原器,因此您可以根据 enum 而不是 switch 来创建实例。

_restorerFactory.Create(ArchiveType.CurrentData);

2
为什么不让ArchiveReader负责创建适当的ArchiveRestorer呢?这样代码的第一次迭代将如下所示:
public class ArchiveRestorerFactory : IArchiveRestorerFactory
{
    public ArchiveRestorer Create(ArchiveReader archiveReader)
    {
        ArchiveRestorer restorer = archiveReader.GetArchiveRestorer();
        return restorer;
    }
}

到那时,工厂已经变得多余了,因此在代码的第二次迭代中,您可以将其丢弃并让消费者直接调用ArchiveReader。


听起来这是一个很好的重构,我会实现它,但它并没有解决依赖问题。它只是将依赖项移动到了“ArchiveReader”类中。“ArchiveReader.GetArchiveRestorer()”仍然需要“new”正确的“ArchiveRestorer”子类。 - shamp00
1
将它们注入到ArchiveReader中。关键在于它如何选择适当的子类型。由于您没有分享这个信息,回答有点困难。然而,如果您决定分享这个信息,应该在这里提出一个新问题。 - Mark Seemann
这两种方法都无法解决依赖性问题(如上面的评论所提到的),同时也违反了SRP原则吗?我正在阅读你的书,但在尝试将理论应用于实际代码时,遇到了这些问题。 - Steve
1
@Steve 如此所述,我的答案已经重构了问题,使其成为根据运行时值选择适当策略的问题。有很多方法来解决这个问题:元数据角色提示角色接口角色提示,或者(我最喜欢的)部分类型名称角色提示 - Mark Seemann

1
创建一个接口,其中包含一个返回该接口类型的方法,并让三个归档类实现该接口。然后,在创建方法中,参数类型将仅为该接口,通过调用刚刚创建的接口的方法,将返回所需的对象。因此,在创建方法中不需要具体类型。

0
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
{
    .
    .
    .
}

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接