设计模式七大原则之依赖倒置原则

概述

定义:

1)高层模块不应该依赖低层模块,二者都应该依赖其抽象。从代码的角度来说,高层模块就是调用者,低层模块就是被调用者。

2)抽象不应该依赖细节,细节应该依赖抽象

依赖倒置的中心思想是面向抽象(抽象类或接口)编程

依赖倒置原则是基于这样的设计理念:相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建的架构比以细节为基础的架构要稳定的多。在java中,抽象指的是抽象类或接口;细节就是具体的实现类,实现接口或者继承抽象类所产生的类。

使用接口或抽象类的目的是制定好规范,而不涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成

案例演示

人喂养动物案例:

依赖正置方案:

动物类:

public class Dog {
    public void eat() {
        System.out.println("狗吃骨头");
    }
}
public class Cat {
    public void eat() {
        System.out.println("猫吃鱼");
    }
}

Person类:

public class Person {
    public void feed(Dog dog) {
        dog.eat();
    }

    public void feed(Cat cat) {
        cat.eat();
    }
}

测试类:

public class Test {
    public static void main(String[] args) {
        Person p = new Person();
        p.feed(new Dog());
        p.feed(new Cat());
    }
}

依赖正置:就是类间的依赖是具体类间的依赖,也就是面向实现编程,高层模块依赖低层模块(Person类的方法中调用了动物类,即Person类依赖动物类),当低层模块发生变动时,高层模块也得跟着一起变(当增加动物时,Person类也要修改,增加相应的重载的feed方法)。

优点:代码简单,容易想到。

缺点:如果增加鸟、猪等,则需要增加类,同时Person类需要增加相应的重载方法。在类级别违反了开闭原则,类间的耦合度高,可维护性和复用性较低。

改进方案:依赖倒置

定义IAnimal接口,包含抽象的eat()方法,让动物类实现该接口实现eat()方法,Person类不再依赖具体动物类,而是依赖IAnimal接口,当需要增加喂养的动物时,让它实现IAnimal接口即可,无需修改IAnimal接口和Person类。

依赖正置与依赖倒置的UML图:
在这里插入图片描述

依赖倒置的三种实现方式:普通方法传递依赖对象、构造方法传递依赖对象、set方法传入依赖对象

普通方法传递依赖对象

声明接口类型的形参,调用时传递实现类对象

IAnimal接口:

public interface IAnimal {
    void eat();
}

动物类实现IAnimal接口:

public class Dog implements IAnimal {
    public void eat() {
        System.out.println("狗吃骨头");
    }
}
public class Cat implements IAnimal {
    public void eat() {
        System.out.println("猫吃鱼");
    }
}

Person类:

public class Person {
    //feed方法传递依赖对象
    public void feed(IAnimal animal) {
        animal.eat();
    }
}

测试类:

public class Test {
    public static void main(String[] args) {
        Person p = new Person();
        p.feed(new Dog());
        p.feed(new Cat());
    }
}

一般只有在调用方法的时候才需要传递依赖,依赖程度最低。

构造方法传递依赖对象

IAnimal接口:

public interface IAnimal {
    void eat();
}

动物类实现IAnimal接口:

public class Dog implements IAnimal {
    public void eat() {
        System.out.println("狗吃骨头");
    }
}
public class Cat implements IAnimal {
    public void eat() {
        System.out.println("猫吃鱼");
    }
}

Person类:

public class Person {
    private IAnimal animal;
	//构造方法传递依赖对象
    public Person(IAnimal animal) {
        this.animal = animal;
    }

    public void feed() {
        this.animal.eat();
    }
}

测试类:

public class Test {
    public static void main(String[] args) {
        Person p1 = new Person(new Dog());
        p1.feed();

        Person p2 = new Person(new Cat());
        p2.feed();
    }
}

依赖对象对于调用对象是必须的,并且他们的生命周期也是一致的,在创建调用对象的时候就需要传递依赖对象才能构造成功,并且对于同一个调用对象 依赖对象一般是不可变的,使用的始终是同一个。

set方法传入依赖对象

IAnimal接口:

public interface IAnimal {
    void eat();
}

动物类实现IAnimal接口:

public class Dog implements IAnimal {
    public void eat() {
        System.out.println("狗吃骨头");
    }
}
public class Cat implements IAnimal {
    public void eat() {
        System.out.println("猫吃鱼");
    }
}

Person类:

public class Person {
    private IAnimal animal;
	//set方法传递依赖对象
    public void setAnimal(IAnimal animal) {
        this.animal = animal;
    }

    public void feed() {
        this.animal.eat();
    }
}

测试类:

public class Test {
    public static void main(String[] args) {
        Person p1 = new Person();
        p1.setAnimal(new Dog());
        p1.feed();

        Person p2 = new Person();
        p2.setAnimal(new Cat());
        p2.feed();
    }
}

依赖对象和调用对象的生命周期一般不是同步的,也有可能不是必须的。可以在需要的时候传递依赖对象,还可以多次传递。

依赖倒转原则的注意事项和细节

依赖倒置原则的本质就是通过抽象(抽象类或接口)使各个类或模块的实现彼此独立,不相互影响,实现模块间的松耦合。

低层模块尽量都要有抽象类或接口,或者两者都有,程序稳定性更好。

变量的声明类型尽量是抽象类或接口,这样我们的变量引用和实际对象间,就存在一个缓冲层,有利于程序扩展和优化。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值