构造函数注入,避免非依赖参数
本文关键字:依赖 参数 注入 构造函数 | 更新日期: 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;
....
}