构造函数注入,避免非依赖参数

本文关键字:依赖 参数 注入 构造函数 | 更新日期: 2023-09-27 17:50:49

我需要重构现有的抽象类来实现依赖注入,但是这个类有两个构造函数,它们接受其他参数。

public abstract class MyClassBase
{
    MyClassBase(int settingId)
    {
        _settingId = settingId;
    }
    MyClassBase(Setting setting)
    {
        _setting = setting;
    }
    ...
}

我需要注入一些接口,并避免在构造函数中传递任何其他参数,如"settingId"answers"Setting"。所以我的想法是,一旦我们创建了这个类的实例,就创建两个方法来设置这些参数。

public abstract class MyClassBase
{
    private readonly IOneService _oneService;
    private readonly ITwoService _twoService;
    MyClassBase(IOneService oneService, ITwoService twoService)
    {
        _oneService = oneService;
        _twoService = twoService;
    }
    protected void SetupSetting(int settingId)
    {
        _settingId = settingId;
    }
    protected void SetupSetting(Setting setting)
    {
        _setting = setting;
    }
    protected Setting Setting
    {
        get 
        {
            if(_setting == null)
            {
                _setting = _oneService.getSettingById(_settingId);
            }
            return _setting;
        }
    }  
}

但是它看起来不是一个合适的解决方案,因为如果开发人员在实例创建后忘记运行这些方法之一,我们将来会得到一个异常(对象未设置为引用…)。我该怎么做才合适呢?

构造函数注入,避免非依赖参数

你的注入应该只有一个构造函数。拥有多个构造函数是一种反模式。类运行所需的一切都必须通过构造函数传入;使用多个步骤构造对象会导致时间耦合,这是一种设计气味。

所以总的来说,我同意@realnero的解决方案,尽管用接口抽象Settings对象可能没有用。接口意味着抽象行为,但这样的设置对象通常只包含数据,不包含行为。因此,添加抽象是行不通的。

虽然注入Settings对象是可以的,但是这种对象经常被误用。你不应该向构造函数传递比类本身直接需要的更多的东西。如果Settings对象包含整个应用程序的值,则该类需要的值将变得不清楚。它还会导致您在每次配置更改时更改Settings对象,并且会有许多消费者依赖于这个不断增长的Settings类。

这些设置类通常用于分派到配置文件。换句话说,当消费者从Settings类请求一个值时,它查询配置文件。这将导致惰性读取配置文件。这使得应用程序在运行时很容易因为配置文件中的错别字而失败。相反,你更希望应用程序在启动时直接失败(快速失败)。

所以,你应该注入这些组件需要的配置值,这些值应该在应用程序启动时从配置文件中读取。这允许应用程序在配置不正确时快速失败,使组件的需求非常清楚,并防止您拥有整个应用程序依赖的中心配置类。

但是有依赖的基类本身就是一种代码气味。如果没有具体的例子,就不可能说该如何重构,但我认为有两个设计错误会导致使用基类。

开发人员经常将横切关注点移到基类中。这将导致这些基类不断增长,变得难以维护,并导致基类的复杂性与派生类交织在一起,从而使它们变得复杂且难以测试。相反,应该使用装饰或拦截来添加横切关注点。装饰器设计模式在应用横切关注点方面非常有效。它允许服务根本没有基类,并且在添加新的横切关注点时提供了很大的灵活性。例如,看看这篇文章来获得一些关于这方面的想法。

如果基类不处理横切关注点,而是处理许多派生类重用的中心应用程序逻辑,则将此基类逻辑抽象为单独的服务通常是一种更好的方法。当这样做时,这个新服务可以被注入到你的组件中,这样就可以让组件忽略这个新服务本身的依赖关系。它使您不必传入基类的依赖项并调用基构造函数。其中一种方法是使用聚合服务。

No。您应该从依赖的接口获得所有需要的数据。如:

MyClassBase(IOneService oneService, ITwoService twoService, ISettings set)
{
    _setting = set;
    ....
}