简体   繁体   English

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

[英]Constructor injection, avoid non-dependency parameters

I need to refactor existing abstract class to implement Dependency Injection, but this class has two constructors that takes other parameters. 我需要重构现有的抽象类来实现依赖注入,但是这个类有两个带有其他参数的构造函数。

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

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

I need to inject some interfaces and avoid passing any other parameter like "settingId" and "Setting" in constructor. 我需要注入一些接口,并避免在构造函数中传递任何其他参数,如“settingId”和“Setting”。 So my idea is to create two methods to setup these parameters once we create the instance of this class. 所以我的想法是在创建这个类的实例后创建两个方法来设置这些参数。

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

But it does not look as a proper solution, because if developer forget to run one of these methods just after instance creation we can get an exception(object not set to reference...) in future. 但它看起来并不是一个合适的解决方案,因为如果开发人员在创建实例后忘记运行其中一个方法,我们将来会得到一个异常(对象未设置为引用...)。 How should I do it properly? 我该怎么做呢?

Your injectables should have just one constructor. 您的注射剂应该只有一个构造函数。 Having multiple constructors is an anti-pattern . 拥有多个构造函数是一种反模式 Everything that the class requires to run must be passed in through the constructor; 类需要运行的所有内容都必须通过构造函数传递; constructing the object using multiple steps leads to temporal coupling, which is a design smell . 使用多个步骤构造对象导致时间耦合,这是设计气味

So in general, I agree with @realnero's solution, although abstracting the Settings object with an interface might not be useful. 所以一般来说,我同意@ realnero的解决方案,尽管使用接口抽象Settings对象可能没用。 Interfaces are meant to abstract behaviour, but such settings object will typically only contain data, no behaviour. 接口用于抽象行为,但这样的设置对象通常只包含数据,没有行为。 Adding an abstraction will therefore not work. 因此,添加抽象将不起作用。

Although injecting a Settings object can be fine, such object is often misused. 尽管注入一个Settings对象可以很好,但这种对象经常被滥用。 You should not pass in more into the constructor than such class directly needs itself. 你不应该将更多内容传递给构造函数,而不是直接需要它自己。 If the Settings object contains values for the whole application, it will become unclear what values such class needs. 如果Settings对象包含整个应用程序的值,则将不清楚此类所需的值。 It will also cause you to change the Settings object every time the configuration changes and there will be many consumers depending on this ever growing Settings class. 每次配置更改时,它也会导致您更改Settings对象,并且根据不断增长的Settings类,将有许多使用者。

These settings classes are often used to dispatch to the configuration file. 这些设置类通常用于分派到配置文件。 In other words, when a consumer asks for a value from the Settings class, it queries the configuration file. 换句话说,当消费者从Settings类请求值时,它会查询配置文件。 This results in a configuration file that is read lazily. 这导致一个懒惰读取的配置文件。 This makes it really easy for an application to fail at runtime, because of typos in the configuration file. 这使得应用程序在运行时很容易失败,因为配置文件中存在拼写错误。 Instead you rather want the application to fail directly at start up (fail fast). 相反,您更希望应用程序在启动时直接失败(快速失败)。

So instead, you should just inject the configuration values that such component needs and those values should be read from the configuration file at application start up. 因此,您应该只注入此组件需要的配置值,并在应用程序启动时从配置文件中读取这些值。 This allows the application to fail fast when the config is incorrect, makes it very clear what the component requires and prevents you from having central configuration classes where the whole application depends upon. 这允许应用程序在配置不正确时快速失败,使组件需要的内容非常清楚,并防止您拥有整个应用程序所依赖的中央配置类。

But having base classes with dependencies is a code smell by itself. 但是拥有依赖的基类本身就是代码味道。 Without a concrete example it's impossible to say what how to refactor, but I think there are two design mistakes that cause the use of base classes. 没有一个具体的例子,就不可能说出如何重构,但我认为有两个设计错误会导致使用基类。

Developers often move cross-cutting concerns into base classes. 开发人员经常将跨领域的问题转移到基类中。 This will cause those base classes to grow and become hard to maintain, and it causes the complexity of the base class to be intertwined with the derived class, making them complex and hard to test as well. 这将导致这些基类增长并变得难以维护,并且它导致基类的复杂性与派生类交织在一起,使得它们复杂且难以测试。 Instead, cross-cutting concerns should be added using decoration or interception. 相反,应该使用装饰或拦截来增加跨领域的关注。 The decorator design pattern is very effective in applying cross-cutting concerns. 装饰设计模式在应用横切关注方面非常有效。 It allows the services to have no base class at all and provides great flexibility when it comes to adding new cross-cutting concerns. 它允许服务根本没有基类,并且在添加新的跨领域问题时提供了极大的灵活性。 Take a look at this article for instance to get some ideas about this. 例如,看看这篇文章就可以得到一些想法。

In case the base class doesn't handle cross-cutting concerns, but central application logic that many derived classes reuse, abstracting this base class logic into a separate service is often a much better approach. 如果基类不处理横切关注点,而是许多派生类重用的中央应用程序逻辑,则将此基类逻辑抽象为单独的服务通常是一种更好的方法。 When doing this, this new service can be injected into your components, which allows them to make them oblivious about the dependencies this new service itself has. 这样做时,这个新服务可以注入到您的组件中,这使他们可以忘记这个新服务本身具有的依赖关系。 It prevents you from having to pass in dependencies of the base class and calling the base constructor. 它可以防止您必须传入基类的依赖项并调用基础构造函数。 One of the ways to do this is using Aggregate Services . 其中一种方法是使用Aggregate Services

No. You should get all needed data from your dependent interface. 不。您应该从您的从属接口获取所有需要的数据。 Like: 喜欢:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM