设计依赖项注入类的准则

时间:2020-03-06 14:39:58  来源:igfitidea点击:

关于单元测试最佳实践的问题提到了为依赖注入设计类。这让我开始思考这到底意味着什么。

刚开始使用反转控制容器时,我对这个问题有一些想法,所以让我把它们扔在墙上,看看有什么问题。

我的看法是,对象可以具有三种基本类型的依赖关系。

  • 对象相关性-所涉及的类将使用的实际对象。例如,LogInFormController中的LogInVerifier。这些应该通过构造函数注入。如果该类足够高,需要在构造函数中使用多个这些对象,请考虑将其分解或者至少使用工厂模式。我们还应该考虑为依赖项提供一个接口,并针对该接口进行编码。
  • 简单设置-例如阈值或者超时期限。这些通常应具有默认值,并通过工厂模式的构建器进行设置。我们还可以提供设置它们的构造函数重载。但是,在大多数情况下,我们可能不应该强迫客户端明确设置它。
  • 消息对象-从一个类传递到另一个类的对象,接收类可能将其用于业务逻辑。一个示例是LogInCompleRouter类的User对象。在这里,我发现通常最好不要在构造函数中指定消息,因为我们随后必须在IoC容器中注册User实例(使其成为全局实例),或者在拥有User实例之后才实例化LogInCompleteRouter (为此,我们不能使用DI或者至少需要对Container的显式依赖)。在这种情况下,最好仅在需要方法调用时才传递消息对象(即LoginInCompleteRouter.Route(User u);)。

另外,我要指出的是,并非所有内容都应该进行DI处理,如果我们有一个简单的功能,可以方便地将它扔掉,那么就可以当场实例化。显然,这是一个判断。如果我发现编写这样的类很方便

class PasswordEqualsVerifier {
  public bool Check(string input, string actual) { return input===actual;}
}

我可能不会打扰依赖注入它,而只是让一个对象直接在using块内实例化它。结果是,如果值得为其编写单元测试,那么可能值得注入。

那么你们怎么看?任何其他准则或者相反的意见都欢迎。

解决方案

重要的是尝试对接口进行编码,并让类接受这些接口的实例,而不是自己创建实例。我们显然可以为此感到疯狂,但是无论单元测试还是DI,这都是一种普遍的好习惯。

例如,如果我们有一个数据访问对象,则可能倾向于为所有DAO编写一个基础,如下所示:

public class BaseDAO
{
    public BaseDAO(String connectionURL, 
                   String driverName, 
                   String username, String password)
    {
        // use them to create a connection via JDBC, e.g.
    }

    protected Connection getConnection() { return connection; }
}

但是,最好从类中删除它,以使用接口

public interface DatabaseConnection
{
    Connection getConnection();
}

public class BaseDAO
{
    public BaseDAO(DatabaseConnection dbConnection)
    {
        this.dbConnection = dbConnection;
    }

    protected Connection getConnection() { return dbConnection.getConnection(); }
}

现在,我们可以提供DatabaseConnection的多个实现。即使忽略单元测试,如果我们假设我们使用的是JDBC,也有两种方法来获得Connection:从容器获取连接池,或者直接通过使用驱动程序。现在,DAO代码没有与这两种策略耦合。

为了进行测试,我们可以创建一个" MockDatabaseConnection",并通过罐装数据连接到某些嵌入式JDBC实现,以测试代码。