什么是构造函数注入?


47

在浏览有关(服务定位器)设计模式的文章时,我一直在研究构造函数注入和依赖注入这两个术语。

当我搜索构造函数注入时,我得到的结果不清楚,这促使我在这里进行检查。

什么是构造函数注入?这是一种特定类型的依赖项注入吗?一个典型的例子将有很大的帮助!

编辑

一周的时间后,我又重新审视了这个问题,我看到自己迷失了……万一其他人突然出现在这里,我将通过一点我的学习来更新问题的正文。请随时发表评论/纠正。 构造函数注入和属性注入是两种类型的依赖注入。


5
谷歌的第一击清楚地解释了它... misko.hevery.com/2009/02/19/…– 2012
user281377

Answers:


95

我不是专家,但我想可以提供帮助。是的,这是一种特定类型的依赖注入。

免责声明:Ninject Wiki几乎所有这些都是“被盗”

让我们通过简单的示例来研究依赖注入的概念。假设您正在编写下一个轰动一时的游戏,其中高贵的战士为荣耀而战。首先,我们需要一种适合武装战士的武器。

class Sword 
{
    public void Hit(string target)
    {
        Console.WriteLine("Chopped {0} clean in half", target);
    }
}

然后,让我们创建一个代表战士自己的类。为了攻击敌人,战士需要一个Attack()方法。调用此方法时,应使用其剑来打击其对手。

class Samurai
{
    readonly Sword sword;
    public Samurai() 
    {
        this.sword = new Sword();
    }

    public void Attack(string target)
    {
        this.sword.Hit(target);
    }
}

现在,我们可以创建我们的武士并进行战斗!

class Program
{
    public static void Main() 
    {
        var warrior = new Samurai();
        warrior.Attack("the evildoers");
    }
}

就像您可能想象的那样,这将把切碎的邪恶者打印到控制台一半。这很好用,但是如果我们想用另一把武器武装武士怎么办?由于Sword是在Samurai类的构造函数中创建的,因此我们必须修改类的实现才能进行此更改。

当一个类依赖于具体的依赖时,据说它该类紧密相关。在此示例中,Samurai类与Sword类紧密耦合。当类紧密耦合时,在不更改其实现的情况下不能互换它们。为了避免紧密耦合类,我们可以使用接口来提供一个间接级别。让我们创建一个界面来表示我们游戏中的武器。

interface IWeapon
{
    void Hit(string target);
}

然后,我们的Sword类可以实现此接口:

class Sword : IWeapon
{
    public void Hit(string target) 
    {
        Console.WriteLine("Chopped {0} clean in half", target);
    }
}

我们可以更改武士职业:

class Samurai
{
    readonly IWeapon weapon;
    public Samurai() 
    {
        this.weapon = new Sword();
    }
    public void Attack(string target) 
    {
        this.weapon.Hit(target);
    }
}

现在我们的武士可以装备不同的武器。可是等等!剑仍在武士的构造函数中创建。由于我们仍然需要更改武士的实现方式来为我们的战士提供另一种武器,因此武士仍然与剑紧密相连。

幸运的是,有一个简单的解决方案。与其从Samurai的构造函数中创建Sword,不如将其公开为构造函数的参数。也称为构造函数注入。

class Samurai
{
    readonly IWeapon weapon;
    public Samurai(IWeapon weapon) 
    {
        this.weapon = weapon;
    }
    public void Attack(string target) 
    {
        this.weapon.Hit(target);
    }
}

正如Giorgio指出的那样,还有财产注入。就像这样:

class Samurai
{
    IWeapon weapon;

    public Samurai() { }


    public void SetWeapon(IWeapon weapon)
    {
        this.weapon = weapon;
    }

    public void Attack(string target) 
    {
        this.weapon.Hit(target);
    }

}

希望这可以帮助。


8
喜欢这个!:)它实际上读起来像一个故事!只是好奇。如果您想用多种武器(依次)武装同一名武士,那么财产注入将是最好的方法吧?
TheSilverBullet

是的,您可以这样做。实际上,我认为将IWeapon作为参数传递给Attack方法更好。就像“公共无效攻击(IWeapon with,字符串目标)”。为了避免重复的代码,我将使现有方法“ public void Attack(字符串目标)”完整无缺,并将其称为“ this.Attack(this.weapon,target)”。
路易·安吉洛

然后与工厂结合,可以获得诸如CreateSwordSamurai()之类的方法!很好的例子。
Geerten

2
很好的例子!很清楚...
Serge van den Oever 2015年

@路易斯·安吉洛(Luiz Angelo)。抱歉,我不确定我是否理解您的评论:“实际上,我认为将IWeapon作为参数传递给Attack方法更好。您的意思是在Samurai类中重载Attack方法吗?
巴氏

3

我将尽力使它变得非常基础。这样,您可以在概念的基础上创建自己的复杂解决方案或想法。

现在想象我们有一个名为禧年的教堂,该教堂在世界各地都有分支机构。我们的目标是简单地从每个分支中获取一个项目。这就是使用DI的解决方案;

1)创建一个接口IJubilee:

public interface IJubilee
{
    string GetItem(string userInput);
}

2)创建一个类JubileeDI,它将把IJubilee接口作为构造函数并返回一个项目:

public class JubileeDI
{
    readonly IJubilee jubilee;

    public JubileeDI(IJubilee jubilee)
    {
        this.jubilee = jubilee;
    }

    public string GetItem(string userInput)
    {
        return this.jubilee.GetItem(userInput);
    }
}

3)现在创建Jubilee的三个分支,即JubileeGT,JubileeHOG,JubileeCOV,它们都必须继承IJubilee接口。有趣的是,让其中一个将其方法实现为虚拟方法:

public class JubileeGT : IJubilee
{
    public virtual string GetItem(string userInput)
    {
        return string.Format("For JubileeGT, you entered {0}", userInput);
    }
}

public class JubileeHOG : IJubilee
{
    public string GetItem(string userInput)
    {
        return string.Format("For JubileeHOG, you entered {0}", userInput);
    }
}

public class JubileeCOV : IJubilee
{
    public string GetItem(string userInput)
    {
        return string.Format("For JubileCOV, you entered {0}", userInput);
    }
}

public class JubileeGTBranchA : JubileeGT
{
    public override string GetItem(string userInput)
    {
        return string.Format("For JubileeGT branch A, you entered {0}", userInput);
    }
}

4)就是这样!现在,让我们看看DI的作用:

        JubileeDI jCOV = new JubileeDI(new JubileeCOV());
        JubileeDI jHOG = new JubileeDI(new JubileeHOG());
        JubileeDI jGT = new JubileeDI(new JubileeGT());
        JubileeDI jGTA = new JubileeDI(new JubileeGTBranchA());

        var item = jCOV.GetItem("Give me some money!");
        var item2 = jHOG.GetItem("Give me a check!");
        var item3 = jGT.GetItem("I need to be fed!!!");
        var item4 = jGTA.GetItem("Thank you!");

对于容器类的每个实例,我们传递所需的类的新实例,从而实现松散耦合。

希望这可以概括地解释这个概念。


2

假设您有一个类,A每个类的实例都需要另一个类的实例B

class A
{
   B b;
}

依赖注入意味着对引用的设置B是由管理对象实例的对象设置的A(与让类直接A管理对引用的引用相反B)。

构造函数注入意味着对的引用B作为参数传递给的构造函数A并在构造函数中设置:

class A
{
    B b;

    A(B b)
    {
        this.b = b;
    }
}

一种替代方法是使用setter方法(或属性)将引用设置为B。在这种情况下,管理一个实例的对象aA必须先调用的构造A和以后调用设置器方法来设置成员变量A.b之前a被使用。后者注射方法是在必要时对象包含对于彼此,例如循环引用如果一个实例bB,在一个实例设置aA包含一个回参考a

**编辑**

这里有一些更多的细节来解决这个评论。

1. A类管理B实例

class A
{
    B b;

    A()
    {
        b = new B();
    }
 }

2.在构造函数中设置了B实例

class A
{
    B b;

    A(B b)
    {
        this.b = b;
    }
}

class M
{
    ...
    B b = new B();
    A a = new A(b);
}

3.使用setter方法设置B实例

class A
{
    B b;

    A()
    {
    }

    void setB(B b)
    {
        this.b = b;
    }
}

class M
{
    ...
    B b = new B();
    A a = new A();

    a.setB(b);
}

...而不是让A类直接管理对B的引用。这是什么意思?您将如何用代码来实现?(请原谅我的无知。)
TheSilverBullet

1
武士的例子好多了。让我们停止使用字母作为变量了……
stuartdotnet

@stuartdotnet:我不认为武士的例子更好或更糟,但是为什么要停止使用一个字母的变量呢?如果变量没有特殊含义,而只是占位符,则单字母变量要紧凑得多,而且要点。使用较长的名称(例如itemAobjectA仅仅是增加名称)并不总是更好。
Giorgio

1
然后,您就错过了我的观点-使用非描述性名称很难理解,理解和理解,也不是解释观点的好方法
stuartdotnet

@stuartdotnet:我想我不会错过您的意思:您声称描述性名称总是更好,并且使代码始终更具可读性和解释性。
Giorgio
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.