首页 > 代码库 > 不可不知的DIP、IoC、DI以及IoC容器

不可不知的DIP、IoC、DI以及IoC容器

面向对象设计(OOD)有助于我们开发出高性能、易扩展以及易复用的程序。其中,OOD有一个重要的思想那就是依赖倒置原则(DIP),并由此引申出IoC、DI以及Ioc容器等概念。

本文首先用实例阐述四个概念,并且给出Java版本的示例代码。

依赖倒置原则(DIP)

依赖倒置是一种软件架构设计的原则,。依赖倒置原则,它转换了依赖,高层模块不依赖于低层模块的实现,而低层模块依赖于高层模块定义的接口。通俗的讲,就是高层模块定义接口,低层模块负责实现。

如何理解呢?举例说明吧。

先看生活中的一个例子。


                                                           图1  ATM与银行卡

相信大部分取过钱的朋友都深有感触,只要有一张卡,随便到哪一家银行的ATM都能取钱。在这个场景中,ATM相当于高层模块,而银行卡相当于低层模块。ATM定义了一个插口(接口),供所有的银行卡插入使用。也就是说,ATM不依赖于具体的哪种银行卡。它只需定义好银行卡的规格参数(接口),所有实现了这种规格参数的银行卡都能在ATM上使用。现实生活如此,软件开发更是如此。

依赖倒置(高层模块定义接口,低层模块负责实现)

在这个图中,我们发现高层模块定义了接口,将不再直接依赖于低层模块,低层模块负责实现高层模块定义的接口。这样,当有新的低层模块实现时,不需要修改高层模块的代码。

由此,我们可以总结出使用DIP的优点:

系统更柔韧:可以修改一部分代码而不影响其他模块。

系统更健壮:可以修改一部分代码而不会让系统崩溃。

系统更高效:组件松耦合,且可复用,提高开发效率。

 

控制反转(IoC

DIP是一种 软件设计原则,它仅仅告诉你两个模块之间应该如何依赖,但是它并没有告诉如何做。IoC则是一种 软件设计模式,它告诉你应该如何做,来解除相互依赖模块的耦合。控制反转(IoC),它为相互依赖的组件提供抽象,将依赖(低层模块)对象的获得交给第三方(系统)来控制,即依赖对象不在被依赖模块的类中直接通过new来获取。

 

依赖注入(DI

控制反转(IoC)一种重要的方式,就是将依赖对象的创建和绑定转移到被依赖对象类的外部来实现。主要实现有三种方式:构造函数注入;属性注入;接口注入。

 

下面以将订单存储到数据库为例,用Java示例来说明问题。

假设系统开发初期使用SqlServer,我们简历数据库操作类。

public class SqlServer {

  public void add(){

        System.out.println("Addorder to Sql Server!");

  }

}

 

定义一个order类:

public class Order {

  private SqlServer ss=newSqlServer();

  public void add(){

        ss.add();

  }

}

 

测试一下:

public class Main {

  public static void main(String[]args) {

        Order o=new Order();

        o.add();

  }

}

输出:

到这里没问题,但是如果需要添加MySql的数据库呢?

这里需要建立操作MySql的数据库类:

public class MySql {

 public void add(){

   System.out.println("Add to mysqlServer");

 }

}

订单类需要修改:

public class Order {

  private MySql ss=new MySql();

  public void add(){

        ss.add();

  }

}

测试输出:

如果还需要更多的数据库操作呢?这样下去还需修改代码。组件之间高度耦合,可扩展性较差,它违背了DIP原则。

 

依赖注入就是解决上述问题。

依赖注入主要有三种实现方式:构造函数注入;属性注入;接口注入。下面用示例进行说明。

1、        构造函数

构造函数函数注入,毫无疑问通过构造函数传递依赖。因此,构造函数的参数必然用来接收一个依赖对象。那么参数的类型是什么呢?具体依赖对象的类型?还是一个抽象类型?根据DIP原则,我们知道高层模块不应该依赖于低层模块,两者应该依赖于抽象。那么构造函数的参数应该是一个抽象类型。

定义接口:

public interface IDB {

    publicvoid add();

}

                      然后在SqlServer中实现这个接口

                            publicclass SqlServer implements IDB{

 

      @Override

      public void add() {

           // TODO Auto-generatedmethod stub

           System.out.println("Addinto Sql Server");

      }

     

}

                 修改order类,在构造函数中注入对象:

                 public class Order {

      private IDB idb;

      public Order(IDB idb){

           this.idb=idb;

      }

     

      public void add(){

           this.idb.add();

      }

}

测试类:

public class Main {

    publicstatic void main(String[] args) {

         IDBidb=new SqlServer();

         Ordero=new Order(idb);

         o.add();

    }

}

输出:

 

通过构造函数注入的方式我们将依赖对象SqlServer的创建和绑定转移到了Order类的外部来实现,这样就解除了SqlServe和Order类的讴歌关系。当我们需要换成MySql数据库的时候,只需要重新定义一个Mysql类实现IDB接口,在用到的地方新建,在Order的外部重新绑定依赖,不需要修改Order的代码。

例如:

新建MySql类:

public class MySql implements IDB{

 

    @Override

    publicvoid add() {

         //TODO Auto-generated method stub

         System.out.println("Addinto MySql server!");

    }

   

}

 

在Main函数中只需要需改:

IDB idb=new MySql();

 

测试:

 

 

2、        属性注入

属性注入是通过属性来传递依赖。因此,我们首先需要在依赖类Order中定义一个属性。

public class Order{

    private IDB idb;//私有属性

   

    public void add(){

         this.idb.add();

    }

    public IDB getIdb() {

         return idb;

    }

 

    public void setIdb(IDB idb) {

         this.idb = idb;//接受依赖

    }

   

}

接口类以及数据库访问类同上,Main方法需要这么写:

public class Main{

    public static void main(String[] args) {

         IDB idb=new SqlServer();

         Order o=new Order();

         o.setIdb(idb);

         o.add();

    }

}

测试输出:

Add into SqlServer

如果需要扩展MySql数据库,只需要在Main方法里修改为:

IDBidb=newMySql();

测试输出:

Add into MySql

 

3、        接口注入

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

首先定义一个接口:

 

public interface IDependent {

    publicvoid setDependency(IDB idb);

}

 

依赖类实现这个接口:

public class Order implements IDependent{

   

    privateIDB idb;

    @Override

    publicvoid setDependency(IDB idb) {

         //TODO Auto-generated method stub

         this.idb=idb;

    }

   

   

    publicvoid add(){

         this.idb.add();

    }

}

 

还需要定义数据库访问的接口:

public interface IDB {

    publicvoid add();

}

 

具体数据库的实现需要实现这个接口:

public class SqlServer implements IDB{

 

    @Override

    publicvoid add() {

         //TODO Auto-generated method stub

         System.out.println("Addinto Sql Server");

    }

   

}

 

测试类:

 

public class Main {

    publicstatic void main(String[] args) {

         Ordero=new Order();

         IDBidb=new SqlServer();

         o.setDependency(idb);

         o.add();

    }

}

测试输出:

Add into SqlServer

如果需要添加MySql的访问需要定义MySql的实现:

 

public class MySql implements IDB{

 

    @Override

    publicvoid add() {

         //TODO Auto-generated method stub

         System.out.println("Addinto MySql");

    }

   

}

 

在测试类只需要作如下修改即可:

IDBidb=newMySql();

 

测试输出:

Add into MySql。

IoC容器

简单的来讲就是抛弃手动的方式创建依赖对象传递给被依赖模块而选取DI框架替我们创建来减轻我们的工作量。对于大型项目来说,相互依赖的组件比较多。如果还用手动的方式,自己来创建和注入依赖的话,显然效率很低,而且往往还会出现不可控的场面。正因如此,IoC容器诞生了。IoC容器实际上是一个DI框架,它能简化我们的工作量。它包含以下几个功能:

l  动态创建、注入依赖对象。

l  管理对象生命周期。

l  映射依赖关系。

Java中比较突出的就是Spring了,更多Spring的介绍将出现在后面的博文里。

不可不知的DIP、IoC、DI以及IoC容器