Android开发Android技术知识Android开发

依赖注入不仅是java开发者需要学习的,Android也必备的知

2019-05-02  本文已影响4人  享学课堂

什么是依赖(Dependency)?

依赖是类与类之间的连接,依赖关系表示一个类依赖于另一个类的定义,通俗来讲

就是一种需要,例如一个人(Person)可以买车(Car)和房子(House),Person类依赖于Car类和House类

public static void main(String ... args){
        //TODO:

    Person person = new Person();
    person.buy(new House());
    person.buy(new Car());

}

static class Person{

    //表示依赖House
    public void buy(House house){}
    //表示依赖Car
    public void buy(Car car){}
}

static class House{

}

static class Car{

}

依赖倒置 (Dependency inversion principle)

依赖倒置是面向对象设计领域的一种软件设计原则

软件设计有 6 大设计原则,合称 SOLID

1、单一职责原则(Single Responsibility Principle,简称SRP )

2、里氏替换原则(Liskov Substitution Principle,简称LSP)

3、依赖倒置原则(Dependence Inversion Principle,简称DIP)

4、接口隔离原则(Interface Segregation Principle,简称ISP)

5、迪米特法则(Law of Demeter,简称LoD)

6、开放封闭原则(Open Close Principle,简称OCP)

依赖倒置原则的定义如下:

什么是上层模块和底层模块?

不管你承认不承认,“有人的地方就有江湖”,我们都说人人平等,但是对于任何一个组织机构而言,它一定有架构的设计有职能的划分。按照职能的重要性,自然而然就有了上下之分。并且,随着模块的粒度划分不同这种上层与底层模块会进行变动,也许某一模块相对于另外一模块它是底层,但是相对于其他模块它又可能是上层

组织架构

公司管理层就是上层,CEO 是整个事业群的上层,那么 CEO 职能之下就是底层。

然后,我们再以事业群为整个体系划分模块,各个部门经理以上部分是上层,那么之下的组织都可以称为底层。

由此,我们可以看到,在一个特定体系中,上层模块与底层模块可以按照决策能力高低为准绳进行划分。

那么,映射到我们软件实际开发中,一般我们也会将软件进行模块划分,比如业务层、逻辑层和数据层。

业务模块

业务层中是软件真正要进行的操作,也就是做什么

逻辑层是软件现阶段为了业务层的需求提供的实现细节,也就是怎么做

数据层指业务层和逻辑层所需要的数据模型。

因此,如前面所总结,按照决策能力的高低进行模块划分。业务层自然就处于上层模块,逻辑层和数据层自然就归类为底层。

什么是抽象和细节?

象如其名字一样,是一件很抽象的事物。抽象往往是相对于具体而言的,具体也可以被称为细节,当然也被称为具象。

比如:

  1. 这是一幅画。画是抽象,而油画、素描、国画而言就是具体。
  2. 这是一件艺术品,艺术品是抽象,而画、照片、瓷器等等就是具体了。
  3. 交通工具是抽象,而公交车、单车、火车等就是具体了。
  4. 表演是抽象,而唱歌、跳舞、小品等就是具体。

上面可以知道,抽象可以是物也可以是行为。

具体映射到软件开发中,抽象可以是接口或者抽象类形式。

 /**
 * Driveable 是接口,所以它是抽象
 */
public interface Driveable {
    void drive();
}
/**
 * 而 Bike 实现了接口,它们被称为具体。
 */
public class Bike implements Driveable {
    @Override
    public void drive() {
        System.out.println("Bike drive");
    }
}
/**
 * 而 Car实现了接口,它们被称为具体。
 */
public class Car implements Driveable {
    @Override
    public void drive() {
        System.out.println("Car drive.");
    }
}

依赖倒置的好处

在平常的开发中,我们大概都会这样编码。

public class Person {

    private Bike mBike;
    private Car mCar;
    private Train mTrain;

    public Person(){
        mBike = new Bike();
        //mCar = new Car();
//        mTrain = new Train();
    }

    public void goOut(){
        System.out.println("出门啦");
        mBike.drive();
        //mCar.drive();
//        mTrain.drive();
    }

    public static void main(String ... args){
            //TODO:
        Person person = new Person();
        person.goOut();
    }
}

我们创建了一个 Person 类,它拥有一台自行车,出门的时候就骑自行车。

不过,自行车适应很短的距离。如果,我要出门逛街呢?自行车就不大合适了。于是就要改成汽车。

不过,如果我要到北京去,那么汽车也不合适了。

有没有一种方法能让 Person 的变动少一点呢?因为这是最基础的演示代码,如果工程大了,代码复杂了,Person 面对需求变动时改动的地方会更多。

而依赖倒置原则正好适用于解决这类情况。

下面,我们尝试运用依赖倒置原则对代码进行改造。

我们再次回顾下它的定义。

上层模块不应该依赖底层模块,它们都应该依赖于抽象。

抽象不应该依赖于细节,细节应该依赖于抽象。

首先是上层模块和底层模块的拆分。

按照决策能力高低或者重要性划分,Person 属于上层模块,Bike、Car 和 Train 属于底层模块。

上层模块不应该依赖于底层模块。


person架构
public class Person {

//    private Bike mBike;
    private Car mCar;
    private Train mTrain;
    private Driveable mDriveable;

    public Person(){
//        mBike = new Bike();
        //mCar = new Car();
       mDriveable = new Train();
    }

    public void goOut(){
        System.out.println("出门啦");
        mDriveable.drive();
        //mCar.drive();
//        mTrain.drive();
    }

    public static void main(String ... args){
            //TODO:
        Person person = new Person();
        person.goOut();
    }
}

可以看到,依赖倒置实质上是面向接口编程的体现

控制反转 (IoC)

控制反转 IoC 是 Inversion of Control的缩写,意思就是对于控制权的反转,对么控制权是什么控制权呢?

Person自己掌控着内部 mDriveable 的实例化。

现在,我们可以更改一种方式。将 mDriveable 的实例化移到 Person 外面。

public class Person2 {

    private Driveable mDriveable;

    public Person2(Driveable driveable){
        this.mDriveable = driveable;
    }

    public void goOut(){
        System.out.println("出门啦");
        mDriveable.drive();
        //mCar.drive();
//        mTrain.drive();
    }

    public static void main(String ... args){
            //TODO:
        Person2 person = new Person2(new Car());
        person.goOut();
    }
}

就这样无论出行方式怎么变化,Person 这个类都不需要更改代码了。

在上面代码中,Person 把内部依赖的创建权力移交给了 Person2这个类中的 main() 方法。也就是说 Person 只关心依赖提供的功能,但并不关心依赖的创建。

这种思想其实就是 IoC,IoC 是一种新的设计模式,它对上层模块与底层模块进行了更进一步的解耦。控制反转的意思是反转了上层模块对于底层模块的依赖控制。

比如上面代码,Person 不再亲自创建 Driveable 对象,它将依赖的实例化的权力交接给了 Person2。而 Person2在 IoC 中又指代了 IoC 容器 这个概念。

依赖注入(Dependency injection)

依赖注入,也经常被简称为 DI,其实在上一节中,我们已经见到了它的身影。它是一种实现 IoC 的手段。什么意思呢?

为了不因为依赖实现的变动而去修改 Person,也就是说以可能在 Driveable 实现类的改变下不改动 Person 这个类的代码,尽可能减少两者之间的耦合。我们需要采用上一节介绍的 IoC 模式来进行改写代码。

这个需要我们移交出对于依赖实例化的控制权,那么依赖怎么办?Person 无法实例化依赖了,它就需要在外部(IoC 容器)赋值给它,这个赋值的动作有个专门的术语叫做注入(injection),需要注意的是在 IoC 概念中,这个注入依赖的地方被称为 IoC 容器,但在依赖注入概念中,一般被称为注射器 (injector)。

表达通俗一点就是:我不想自己实例化依赖,你(injector)创建它们,然后在合适的时候注入给我

实现依赖注入有 3 种方式:

  1. 构造函数中注入
  2. setter 方式注入
  3. 接口注入
/**
 * 接口方式注入
 * 接口的存在,表明了一种依赖配置的能力。
 */
public interface DepedencySetter {
    void set(Driveable driveable);
}
public class Person2  implements DepedencySetter {

    //接口方式注入
    @Override
    public void set(Driveable driveable) {
        this.mDriveable = mDriveable;
    }

    private Driveable mDriveable;

    //构造函数注入
    public Person2(Driveable driveable){
        this.mDriveable = driveable;
    }

    //setter 方式注入
    public void setDriveable(Driveable mDriveable) {
        this.mDriveable = mDriveable;
    }

    public void goOut(){
        System.out.println("出门啦");
        mDriveable.drive();
        //mCar.drive();
//        mTrain.drive();
    }

    public static void main(String ... args){
            //TODO:
        Person2 person = new Person2(new Car());
        person.goOut();
    }
}

最后

读到这的朋友关注下,以后会不停更新更多精选干货及资讯分享,欢迎大家在评论区留言讨论!

上一篇下一篇

猜你喜欢

热点阅读