依赖倒转原则
基本介绍
高层模块不应该依赖底层模块,二者都应该依赖其抽象。
抽象不应该依赖细节,细节应该依赖抽象。
依赖倒转(倒置)的中心思想是面向接口编程。
依赖倒转原则是基于这样的的设计理念: 相对于细节的多变性,抽像的东西要稳定得多。
以抽象为基础搭建的架构比以细节为基础的架构要稳定得多。在java中,抽象指的是接口或者抽象类,
细节就是具体的实现类
使用接口或者抽象类的目的是制定好规范,而不涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成
问题代码示例
public class DependencyInversion {
public static void main(String[] args) {
Person person = new Person();
person.receive(new Email());
}
}
/**
* 一个Person有一个接受消息的功能
*/
class Person {
/**
* 这里传入的参数是Email类,这样就直接依赖死了Email类
* 如果我们还要接受的对象是 微信, QQ等消息,需要新增消息类,还要增加或重载receive方法
* @param email 消息类
*/
public void receive(Email email) {
System.out.println(email.getInfo());
}
}
class Email {
public String getInfo() {
return "电子邮件信息: hello";
}
}
Person 类中的receive方法直接依赖了具体的Email类,这样对后期的扩展很不友好
使用依赖倒装,面向接口编程
/**
* 根据依赖倒转优化后的代码
*/
public class DependencyInversion {
public static void main(String[] args) {
Person person = new Person();
person.receive(new Email());
person.receive(new QQ());
}
}
/**
* 一个Person有一个接受消息的功能
*/
class Person {
/**
* 采用依赖倒转,面向接口依赖,可以提高扩展性
*
* @param iReceiver 使用接口去依赖
*/
public void receive(IReceiver iReceiver) {
System.out.println(iReceiver.getInfo());
}
}
/**
* 电子邮件
*/
class Email implements IReceiver {
@Override
public String getInfo() {
return "电子邮件信息: hello";
}
}
class QQ implements IReceiver {
@Override
public String getInfo() {
return "QQ信息: hello";
}
}
/**
* 定义IReceiver接口, 所有的消息类都实现这个接口
*/
interface IReceiver {
public String getInfo();
}
将所有的消息类向上提取出一个接口或者抽象类,Person 类中的receive方法只需要依赖提取出的接口,这样提高了程序的扩展性
依赖传递的三种方式
接口方式传递
@SuppressWarnings("NonAsciiCharacters")
public class Test {
public static void main(String[] args) {
小米智能电视 小米智能电视 = new 小米智能电视();
华为智能电视 华为智能电视 = new 华为智能电视();
万能遥控器 w = new 万能遥控器();
w.open(小米智能电视);
w.open(华为智能电视);
w.close(小米智能电视);
w.close(华为智能电视);
}
}
interface IOpenAndClose {
/**
* 抽象一个open方法,可以打开一个Tv
*/
void open(ITv iTv);
void close(ITv iTv);
}
interface ITv {
/**
* 抽象一个Tv上的关闭方法
*/
void close();
void open();
}
@SuppressWarnings("NonAsciiCharacters")
class 小米智能电视 implements ITv {
@Override
public void close() {
System.out.println("我是小米智能电视,我关闭了");
}
@Override
public void open() {
System.out.println("我是小米智能电视,我打开了");
}
}
@SuppressWarnings("NonAsciiCharacters")
class 华为智能电视 implements ITv {
@Override
public void close() {
System.out.println("我是华为智能电视,我关闭了");
}
@Override
public void open() {
System.out.println("我是华为智能电视,我打开了");
}
}
@SuppressWarnings("NonAsciiCharacters")
class 万能遥控器 implements IOpenAndClose {
/**
* 抽象一个open方法,可以打开一个Tv,
* 因为是依赖的接口,所以只要是一个实现了ITv的电视都能被关闭
*
* @param iTv 不知道哪个品牌的电视机
*/
@Override
public void open(ITv iTv) {
iTv.open();
}
@Override
public void close(ITv iTv) {
iTv.close();
}
}
构造方式传递
@SuppressWarnings("NonAsciiCharacters")
public class Test {
public static void main(String[] args) {
小米智能电视 小米智能电视 = new 小米智能电视();
万能遥控器 万能遥控器 = new 万能遥控器(小米智能电视);
}
}
interface IOpenAndClose{
/**
* 注意这里是空参的,没有依赖接口
*/
void open();
void close();
}
interface ITv{
void open();
void close();
}
@SuppressWarnings("NonAsciiCharacters")
class 小米智能电视 implements ITv{
@Override
public void open() {
System.out.println("我是小米智能电视,我打开了");
}
@Override
public void close() {
System.out.println("我是小米智能电视,我关闭了");
}
}
@SuppressWarnings("NonAsciiCharacters")
class 万能遥控器 implements IOpenAndClose{
// 使用成员变量的形式,注入依赖
private ITv iTv;
/**
* 注意这里是空参的,没有依赖接口
*/
@Override
public void open() {
this.iTv.open();
}
@Override
public void close(){
this.iTv.close();
}
private 万能遥控器(){
}
/**
* 使用构造的方式给成员变量赋值
* @param iTv 某个型号的电视
*/
public 万能遥控器(ITv iTv){
this.iTv = iTv;
}
}
setter方式传递
@SuppressWarnings("NonAsciiCharacters")
public class Test {
public static void main(String[] args) {
小米智能电视 小米智能电视 = new 小米智能电视();
万能遥控器 万能遥控器 = new 万能遥控器();
万能遥控器.setiTv(小米智能电视);
万能遥控器.close();
}
}
interface IOpenAndClose {
/**
* 注意这里是空参的,没有依赖接口
*/
void open();
void close();
}
interface ITv {
void open();
void close();
}
@SuppressWarnings("NonAsciiCharacters")
class 小米智能电视 implements ITv {
@Override
public void open() {
System.out.println("我是小米智能电视,我打开了");
}
@Override
public void close() {
System.out.println("我是小米智能电视,我关闭了");
}
}
@SuppressWarnings("NonAsciiCharacters")
class 万能遥控器 implements IOpenAndClose {
// 使用成员变量的形式,注入依赖
private ITv iTv;
/**
* 注意这里是空参的,没有依赖接口
*/
@Override
public void open() {
if (iTv == null) {
System.out.println("没有找到电视");
} else {
this.iTv.open();
}
}
@Override
public void close() {
if (iTv == null) {
System.out.println("没有找到电视");
} else {
this.iTv.close();
}
}
/**
* 使用set的方式给成员变量赋值
*
* @param iTv 某个型号的电视
*/
public void setiTv(ITv iTv) {
this.iTv = iTv;
}
}
依赖倒转的原则和注意事项
- 底层模块尽量都要有抽象类或者接口,或者两者都有,程序的稳定性更好。
- 变量的声明类型尽量是抽象或者接口,这样我们的变量引用和实际对象间,就存在一个缓冲。
- 继承时遵循里氏替换原则