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

时间:2015-05-27 08:27:08

标签: java c# dependency-injection

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

public abstract class MyClassBase
{
    MyClassBase(int settingId)
    {
        _settingId = settingId;
    }

    MyClassBase(Setting setting)
    {
        _setting = setting;
    }
    ...
}

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

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;
        }
    }  
}

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

2 个答案:

答案 0 :(得分:4)

您的injectables应该只有一个构造函数。有multiple constructors is an anti-pattern。类需要运行的所有内容都必须通过构造函数传递;使用多个步骤构造对象会导致temporal coupling, which is a design smell

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

尽管注入Settings对象可能没问题,但这种对象经常被滥用。你不应该将更多内容传递给构造函数,而不是直接需要它自己。如果Settings对象包含整个应用程序的值,则将不清楚此类所需的值。每次配置更改时,它也会导致您更改Settings对象,并且会有许多消费者依赖于此不断增长的Settings类。

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

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

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

开发人员经常将跨领域的问题转移到基类中。这将导致这些基类增长并变得难以维护,并且它导致基类的复杂性与派生类交织在一起,使得它们复杂且难以测试。相反,应该使用装饰或拦截来增加跨领域的关注。装饰设计模式在应用横切关注方面非常有效。它允许服务根本没有基类,并且在添加新的跨领域问题时提供了极大的灵活性。例如,请查看this article以了解相关信息。

如果基类不处理横切关注点,但许多派生类重用的中央应用程序逻辑,将此基类逻辑抽象为单独的服务通常是一种更好的方法。这样做时,这个新服务可以注入到您的组件中,这使他们可以忘记这个新服务本身具有的依赖关系。它可以防止您必须传入基类的依赖项并调用基础构造函数。其中一种方法是使用Aggregate Services

答案 1 :(得分:3)

没有。您应该从依赖接口获取所有需要的数据。像:

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