手动依赖注入

说明:本文是Ninject依赖注入说明文档的一部分,原文地址:https://github.com/ninject/ninject/wiki/Dependency-Injection-By-Hand。Ninject是一款开源的依赖注入容器,在VS中可通过Nuget安装。

所谓依赖注入(Dependency Injection),是指从类的外部把依赖关系“注入”到类的内部,这一“注入”的过程一般是通过用接口作为一个“占位”实现的,使得具体类的依赖转化为对接口的依赖,符合IOC(控制反转)原则。如此,获得注入的类只需要了解所要依赖的“契约”即可,而不必了解具体的实现类,从而实现解耦。本文讲述的是如何实现构造函数注入,它是实现依赖注入的一种重要方式。

 

首先通过一个简单案例研究一下依赖注入的思想。假设你正在开发一个游戏,场景是勇士们为荣誉而战。首先,我们需要设计一款武器来武装我们的勇士:

class Sword
{
    public void Hit(string target)
    {
        Console.WriteLine($"把 {target} 砍成了两半儿!");
    }
}

然后,我创建一个代表勇士的类。为了攻击敌人,勇士需要一个 Attack() 方法,当此方法被调用时,它会使用 Sword 来攻击敌人。

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

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

现在,我可以创建一个勇士来参加战斗了!

class Program
{
    public static void Main()
    {
        var warrior = new Warrior();
        warrior.Attack("邪恶的敌人");
    }
}

我们能猜得到,这会在控制台上输出“把 邪恶的敌人 砍成了两半儿!”。这样看起来运行的不错,但是,如果我们想给我们的勇士配备另一种武器呢?由于 Sword 是在 Warrior 类的构造函数里创建的,为了改变武器,我们不得不修改 Warrior 类的实现。

当一个类依赖于另一个具体类时,也称为与那个类紧密地耦合。在这个例子中,Warrior 类与 Sword 类紧密地耦合在一起了。当两个类紧密地耦合时,若不修改二者的实现方法,我们是无法替换二者的。为了避免类的紧密耦合,我们可以通过接口来构造一个中间层。下面来创建一个代表武器的接口。

interface IWeapon
{
    Void Hit(string target);
}

那么,我们的 Sword 类就可以实现这个接口了;

class Sword : IWeapon
{
    public void Hit(string target)
    {
        Console.WriteLine($"把 {target} 砍成了两半儿!");
    }
}

这样,我们就可以修改 Warrior 类了:

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

现在我们就可以为 Warrior 配置不同的武器了。但是等一下! Sword 类仍然是在 Warrior 类的构造函数里创建的,那么我们仍然要修改 Warrior 的实现方式才能改变武器,Warrior 仍然与 Sword 耦合在一起。

幸运的是,有一种简单的解决方法:相比于在构造函数内部创建 Sword,我们可以把 Sword 暴露给构造函数的参数:

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

于是,我们可以通过 Warrior 的构造函数来注入 Sword 对象。这就是依赖注入的一个案例(具体来说,这种方式叫“构造函数注入”)。我们先创建另一种武器:

class Cannon : IWeapon
{
    public void Hit(string target)
    {
        Console.WriteLine($"把 {target} 轰成了渣渣!");
    }
}

现在,我们就可以组件一支勇士军队了:

class Program
{
    public static void Main()
    {
        var warrior1 = new Warrior(new Sword());
        var warrior2 = new Warrior(new Cannon());
        warrior1.Attack("邪恶的敌人");
        warrior2.Attack("邪恶的敌人");
    }
}

控制台的输出结果为:

把 邪恶的敌人 砍成了两半儿!
把 邪恶的敌人 轰成了渣渣!

这种方法就叫做“手动依赖注入”,因为每次你想创建一个 Warrior 时,你就必须首先创建一个 IWeapon 接口的实现然后把它传递给 Warrior 的构造函数。既然现在我们在不修改 Warrior 类的实现时,就能改变它使用的武器,那么,Warrior 类就可以和 Sword 类位于不同的程序集中——实际上,我们可以创建任意新武器而不必事先了解 Warrior 类的源代码!

对于小项目来说,手动依赖注入是一种高效的策略。但是随着应用程序的规模和复杂度的增加,把所有的对象连接起来就变得越来越繁琐了。当依赖项具有自己的依赖项时会发生什么? 当你想在给定依赖项的每个实例前添加装饰器(例如缓存,跟踪日志,审计等)时会发生什么? 你应该去编写能够为软件增加真正价值的代码,而不是将大部分时间用于创建和连接对象,这就是Ninject之类的依赖注入库/框架可以提供帮助的地方。关于Ninject这方面可继续阅读Dependency Injection With Ninject

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值