三、依赖注入是什么?

 

接口

在说依赖注入之前,先了解下什么是接口。

我们在学编程的时候都知道,接口的相关规则:来源百度百科

  • 1. 接口是一个引用类型,通过接口可以实现多重继承。
  • 2. C#中接口的成员不能有new、public、protected、internal、private等修饰符。
  • 3. 接口中只能声明"抽象"成员(所以不能直接下一步对接口进行实例化(即不能使用new操作符声明一个接口的实例对 象)),而不能声明共有的域或者私有的成员变量。
  • 4. 接口声明不包括数据成员,只能包含方法、属性、事件、索引等成员。
  • 5. 接口名称一般都以“I”作为首字母(当然不这样声明也可以),这也是接口和类的一个区别之一。
  • 6. 接口成员的访问级别是默认的(默认为public),所以在声明时不能再为接口成员指定任何访问修饰符,否则 编译器会报错。
  • 7. 接口成员不能有static、abstract、override、virtual修饰符,使用new修饰符不会报错,但会给出警告说不需要关键字new。
  • 8. 在声明接口成员的时候,不准为接口成员编写具体的可执行代码,也就是说,只要在对接口进行声明时指明接口的成员名称和参数就可以了。
  • 9. 接口一旦被实现,实现类必须实现接口中的所有成员,除非实现类本身是抽象类(通过具体的可执行代码实现接口抽象成员的操作)。

                                                                                                                            

很多时候看到这么多的概念,也是云里雾里的。项目中的接口使用也是按照老代码依葫芦画瓢。如果是自己练手的代码或者demo,也是没有使用接口。(给自己的借口就是,我只是做些小的东西,根本就不需要使用接口一样可以跑很溜啊。)

 

接口是什么?(说说我自己的理解,不一定对)

接口就是为了更换一个可能过时或者错误的实现而准备的。就想我们的电脑,里面就到处都是接口。usb、内存条、硬盘、电池、键盘...等等都是有各自的接口。我们可以通过硬盘接口换个更大的硬盘或者换个更快的固态硬盘。如果键盘坏了,也可以通过键盘接口买个新的键盘换上去。这就是接口明显的好处。接口也可以理解成大家的约定。约定了特定接口的大小功能等等。

 

那么我们写代码也是一样,在某些地方可能会经常变动,逻辑会经常修改的地方使用接口约定。下面我们就用硬盘的接口来做示例吧。

首先定义一个硬盘接口。(一个name属性,一个读一个写的方法

/// <summary>
/// 硬盘接口
/// </summary>
interface IHardDisk
{
    /// <summary>
    /// 硬盘的名字属性
    /// </summary>
    string name { get; }
    /// <summary>
    /// 读取数据方法
    /// </summary>
    void read();
    /// <summary>
    /// 写数据
    /// </summary>
    void write(string str);
}

然后我们买了一个200G的硬盘,它实现了上面的接口。

public class HardDisk200 : IHardDisk
{
    public string name
    {
        get
        {
            return "我是200G硬盘";
        }
    }

    public void read()
    {
        Console.WriteLine("我可以写入数据哦....");
    }

    public void write(string str)
    {
        Console.WriteLine(str);
    }

}

在电脑中使用这个硬盘。

static void Main(string[] args)
{
    //这里的h就是一个插在接口上的设备
    IHardDisk h = new HardDisk200();
    h.read();
    h.write(h.name + ",我可以写入数据哦");


    Console.ReadKey();
}

 

某天,我们发现这个硬盘太小了,需要换个1T的。(那样我们可以存很多很多的电影>_<),那么买吧。

public class HardDisk1T : IHardDisk
{
    public string name
    {
        get
        {
            return "我是1T硬盘";
        }
    } 
    public void read()
    {
        Console.WriteLine("我可以写入数据哦....");
    } 
    public void write(string str)
    {
        Console.WriteLine(str);
    }
}

然后怎么使用了?只要在电脑上的接口直接插上新的硬盘就ok了,其他的什么地方都不用改。

 

这就是使用接口的好处。当某天我们发现电脑太慢了,我们可以买个固态硬盘,直接在接口使用的地方换上就可以了,其他地方完全不用修改。

这样,我们就可以在不同时期或不同情况下灵活更换继承实现了接口的任何对象,而不用修改其它地方的代码。

又或者说,实现了这个接口的设备就是存储设备。(它一定有存也一定可以储,也就是一定可以写入和读出数据。

 

依赖注入

在我们了解了什么是接口之后,我们接着来说说今天主要的主题吧。

还是先从例子入手,且是我们学过编程都知道的例子,三层。(什么?你不知道什么是三层?那你别看了,先补习了再过来

我们先来写个简单的三层伪代码。

DAL:

 public class DALMsSqlHelper
 {
     public int add(string str)
     {
         //...省略具体实现
         return 1;
     }
     //...省略具体实现,如修改 删除 查询
 }

BLL:

public class BLLAddStudent
{
    DALMsSqlHelper mssql = null;
    public BLLAddStudent()
    {
        mssql = new DALMsSqlHelper();
    }
    public int addStudent()
    {
        string str = "";  //...省略具体实现
        return mssql.add(str);
    }
}

UI:

 public class UI
 {
     BLLAddStudent s = new BLLAddStudent();
     public UI()
     {
         s.addStudent();
     }
 }

应该说简单得不能在简单的三层。

就在系统用了一年之后,老板说:”听说oracle很牛逼,大公司都是用的oracle。咱们也换上吧。“。 好,那就换吧。

DAL:

public class DALOracleSqlHelper
{
    public int addOracle(string str)
    {
        //...省略具体实现
        return 1;
    }
    //...省略具体实现,如修改 删除 查询
}

显然BLL也要进行修改,因为BLL引用了DAL的查询类。

BLL:

public class BLLAddStudent
{
    DALOracleSqlHelper mssql = null;
    public BLLAddStudent()
    {
        mssql = new DALOracleSqlHelper();
    }
    public int addStudent()
    {
        string str = "";  //...省略具体实现
        return mssql.addOracle(str);
    }
}

不就换个数据库吗?为何修改这么大,要是老板哪天又要换回oracle怎么办?这得好好想个办法。

首先,我们定义一个数据访问的接口。

public interface ISqlHelper
{
    int add();
    //...省略具体实现,如修改 删除 查询
 }

DAL修改如下:

public class DALMsSqlHelper : ISqlHelper
{
    public int add(string str)
    {
        //...省略具体实现
        return 1;
    }
    //...省略具体实现,如修改 删除 查询
}

public class DALOracleSqlHelper : ISqlHelper
{
    public int addOracle(string str)
    {
        //...省略具体实现
        return 1;
    }
    //...省略具体实现,如修改 删除 查询

    public int add(string str)
    {
        //...省略具体实现
        return 1;
    }
}

BLL:

 public class BLLAddStudent
 {
     ISqlHelper mssql = null;
     public BLLAddStudent(ISqlHelper sqlhelper)
     {
         mssql = sqlhelper;
     }
     public int addStudent()
     {
         string str = "";  //...省略具体实现
         return mssql.add(str);
     }
 }

UI:

public class UI
{       
    public UI()
    {
        ISqlHelper sqlhelper = new DALOracleSqlHelper();
        BLLAddStudent s = new BLLAddStudent(sqlhelper);
        s.addStudent();
    }
}

如果哪天老板又要换会mssql怎样办。那么仅仅只要修改UI

 

又过一年之后,因为公司不景气。所以又来需求了。老板:”唉,算了。我们还是用mysql吧。免费的,为公司节省点“。那么我们又要修改了。

首先需要重新写个mysql的实现。

DAL:

public class DALMySqlHelper : ISqlHelper
{
    public int add(string str)
    {
        //...省略具体实现
        return 1;
    }
    //...省略具体实现,如修改 删除 查询
}

UI实现如下:

public class UI
{       
    public UI()
    {
        ISqlHelper sqlhelper = new DALMySqlHelper();
        BLLAddStudent s = new BLLAddStudent(sqlhelper);
        s.addStudent();
    }
}

我们有没有发现。我们只是在DAL新增了一个mysql的实现和修改了下UI层的接口构造。其中BLL我们根本就没有动它的。

是的,这样我们就可以说这里的UI对于BLL来说就是”依赖注入“,BLL对于UI来说就是”控制反转“。所以,我觉得依赖注入和控制反转是同一个概念,只是立场不同。

 

上面,我们看到了虽然BLL层已经不需要变动就可以新增一个数据源的访问。那么我们能不能也不修改UI层呢?

这里就可以用到我们上篇讲的反射了。

 

然后,不管老板想怎么折腾,我只需要改改配置文件就可以了。甚至都不用动代码。(如果需要新增一个数据源操作,也只要重新实现下,然后改改配置文件)。

 

本文以同步至《C#基础知识巩固系列