依赖注入和控制反转

依赖注入和控制反转到底是什么意思?

控制反转(Ioc):调用者不再创建被调用者的实例,由IOC容器框架创建(C#中常用的IOC框架有Autofac/Unity等等),这种方式称为控制反转

依赖注入(DI):容器框架将创建好的实例注入到调用者称为依赖注入

依赖倒置原则(DIP)

高层模块不应依赖于低层模块,两者应该依赖于抽象。

抽象不不应该依赖于实现,实现应该依赖于抽象。

使用Ioc的好处是什么?

控制反转是面向对象编程中的一种设计原则,可以用来降低代码之间的耦合度,Ioc把创建和查找依赖对象的控制权交给容器,由容器进行注入,所以对象与对象之间是松散耦合,传统的应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间是紧耦合

举例

class SqlDal
{
    public void Add()
    {
        Console.WriteLine("向SQLServer数据库添加一条新订单");
    }
}
class Order
{
    SqlDal sqlDal = new SqlDal();

    public void Add()
    {
        sqlDal.Add();
    }
}

首先定义了SqlDal类用于SqlServer数据库的读写操作,再定义Order类负责订单的逻辑处理,因为订单数据是要插入到数据库的,所以在Order类中定了SqlDal类的变量并初始化.这里Order是依赖于SqlDal类的

控制台调用

static void Main(string[] args)
{
    Order order = new Order();
    order.Add();
    Console.ReadKey();
}

这是传统开发中比较常见的方式,如果这时候数据库改为了mysql,那么SqlDal是无法继续使用了,需要重新定义mysqlDal,负责mysql数据库的读写操作

class MySqlDal
{
    public void Add()
    {
        Console.WriteLine("向MySql数据库添加一条新订单");
    }
}

由于Order类中直接引用了SqlDal类的对象,所以也需要同步修改为MySqlDal

class Order
{        
    MySqlDal mySqlDal = new MySqlDal();

    public void Add()
    {
        mySqlDal.Add();
    }
}

OK,可以满足当前需求了,但是如果后期类似的情况再次出现,就意味着需要将刚才的操作重新做一次,显然这不是一个良好的设计,类与类之间是高度耦合的,可扩展性较差,它违背了DIP原则,高层模块Order不应依赖于底层模块SqlDal、MySqlDal,两者应该依赖于抽象

改写代码

public interface IDal
{
    void Add();
}
class SqlDal : IDal
{
    public void Add()
    {
        Console.WriteLine("向SQLServer数据库添加一条新订单");
    }
}
class Order
{
    private IDal _dal;

    /// <summary>
    /// 构造函数注入
    /// </summary>
    /// <param name="dal"></param>
    public Order(IDal dal)
    {
        _dal = dal;
    }
    public void Add()
    {
        _dal.Add();
    }
}

第一步:定义接口IDal,添加方法Add()

第二步:定义SqlDal继承IDal接口并实现接口中的Add()方法

第三步:在Order类中添加私有变量用于存储抽象并在构造函数中传递具体依赖对象

控制台调用

static void Main(string[] args)
{
    Order order = new Order(new SqlDal());
    order.Add();
    Console.ReadKey();
}
class MySqlDal : IDal
{
    public void Add()
    {
        Console.WriteLine("向MySql数据库添加一条新订单");
    }
}
Order order = new Order(new MySqlDal());
order.Add();

输出结果是一样的,但是这里将依赖对象SqlDal对象的创建和绑定转移到了Order外部来实现,这就解除了Order与SqlDal类的耦合关系,如果现在出现需要更改数据库为MySql的需求,只需要定义MySqlDal类继承IDal并实现Add()方法,然后外部绑定依赖,不需要修改Order类内部的代码即可实现替换,很明显这种方式程序的可扩展性更高

属性注入

上面所示的方式是在Order类的构造函数中注入依赖对象,还可以使用属性注入的方式,顾名思义,属性注入是通过属性来传递依赖,因此在Order类中定义一个属性

class Order
{
    private IDal _dal;

    /// <summary>
    /// 属性注入
    /// </summary>
    public IDal Dal
    {
        get { return _dal; }
        set { _dal = value; }
    }

public void Add() { _dal.Add(); } }

在控制台调用时给属性赋值从而传递依赖

static void Main(string[] args)
{          
    Order order = new Order();
    order.Dal = new SqlDal();
    order.Add();            
    Console.ReadKey();
}

接口注入

相比构造函数注入和属性注入,接口注入显得有些复杂,使用也不常见。具体思路是先定义一个接口,包含一个设置依赖的方法。然后依赖类,继承并实现这个接口

public interface IDependent
{
    void SetDepend(IDal idal);
}
class Order: IDependent
{
    private IDal _dal;

    public void Add()
    {
        _dal.Add();
    }

    public void SetDepend(IDal idal)
    {
        _dal = idal;
    }
}

 通过SetDepend方法传递依赖

 static void Main(string[] args)
 {
     Order order = new Order();
     order.SetDepend(new SqlDal());
     order.Add();
     Console.ReadKey();
 }

 Ioc容器

前面所有的例子中,我们都是通过手动的方式来创建依赖对象,并将引用传递给被依赖模块,对于大型项目来说,相互依赖的组件比较多。如果还用手动的方式自己来创建和注入依赖的话,显然效率很低,而且往往还会出现不可控的场面。正因如此,Ioc容器诞生了。Ioc容器实际上是一个DI框架,它能简化我们的工作量。它包含以下几个功能 

  • 动态创建、注入依赖对象
  • 管理对象生命周期
  • 映射依赖关系

总结:DIP是软件设计的一种思想,Ioc则是基于DIP衍生出的一种软件设计模式。DI是Ioc的具体实现方式之一,使用最为广泛。Ioc容器是DI构造函注入的框架,它管理着依赖项的生命周期以及映射关系

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章