设计模式 — 6大设计原则(依赖倒置和接口隔离原则)

article/2025/9/22 23:40:04

设计模式

      • 依赖倒置原则
        • 示例 一
        • 示例 二
        • 依赖的三种写法
        • 总结
      • 接口隔离原则
        • 实例 一
        • 总结

依赖倒置原则

依赖倒置原则(Dependence Inversion Principle,DIP)这个名字看着有点别扭,“依赖” 还 “倒置” ,这到底是什么意思?依赖倒置原则翻译过来包含三层含义

1、高层模块不应该依赖底层模块,两者都应该依赖其抽象;
2、抽象不应该依赖细节;
3、细节应该依赖抽象。
辅助理解:底层模块:每个逻辑的实现都是由原子逻辑组成的,不可分割的原子逻辑就是底层模块。高层模块:原子逻辑的再组装就是高层模块。(领域编排)抽象:指接口或抽象类,两者都是不能直接被实例化的。细节:细节就是实现类实现接口或继承抽象类而产生的类,其特点就是可以直接被实例化

依赖倒置原则在Java语言中的表现就是:

1、模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的;
2、接口或抽象类不依赖实现类;
3、实现类依赖接口或抽象类
4、简单来说就是 “面向接口编程”

采用依赖倒置原则可以减少类间的耦合性,提高系统的稳定性。降低并行开发引起的风险,提高代码的可读性和可维护性。

论题:采用依赖倒置原则可以减少类间的耦合性,提高系统的稳定性。降低并行开发引起的风险,提高代码的可读性和可维护性。

反论题:不使用依赖倒置原则也可以减少类间的耦合性,提高系统的稳定性,降低并行开发引起的风险,提高代码的可读性和维护性。

示例 一

现在的汽车越来越便宜了,一个卫生间的造价就可以买一辆不错的汽车,有汽车就必然有人来驾驶,司机驾驶奔驰车的类图如下:
在这里插入图片描述
奔驰车可以提高一个方法run,代表车辆运行,实现过程代码如下:

public class Benz {public void run() {System.out.println("奔驰汽车开始运行...");}
}public class Driver {//司机的主要职责就是驾驶汽车public void drive(Benz benz) {benz.run();}
}public class Client {public static void main(String[] args) {Driver zhangSan = new Driver();Benz benz = new Benz();//张三开奔驰车zhangSan.drive(benz);}
}

运行结果得出:

奔驰汽车开始运行...

通过以上的代码,完成了司机开动奔驰车的场景,到目前为止,这个司机开奔驰车的项目没有任何问题,但是变更缺不灵活,业务需求变更永无止休,技术前进就永无止境,在原有程序上加上,张三不仅要开奔驰车,还要开宝马车,新增代码如下:

public class BMW {//宝马车当然也可以开动了public void run() {System.out.println("宝马汽车开始运行...");}
}

到这里,我们发现没有办法让张三把宝马车也开动起来,为什么?张三没有(调用)开动宝马车的方法呀!很显然,从设计上出了问题,司机类和奔驰车类之间是紧耦合关系,其导致的结果就是系统的可维护性大大降低,可读性降低。通过以上的例子证明反论题已经部分不成立了。

注意:设计是否具备稳定性,只要适当地 “松松土” ,观察 “设计的蓝图” ,是否还可以茁壮地成长就可以得出结论,稳定性较高的设计,在周围环境频繁变化的时候,依然可以做到 “我自巍然不动”

示例 二

我们继续证明, “减少并行开发引起的风险” ,什么是并行开发的风险? 并行开发最大的风险就是风险扩散,本来只是一段程序的错误或异常,逐步波及了整个项目。为什么并行开发就有这样的风险呢? 一个团队,20个开发人员,各人负责不同的功能模块,甲负责汽车类的建造,乙负责司机类的建造,在甲没有完成的情况下,乙是不能完全地编写代码的,缺少汽车类,编译器根本就不会让你通过!所以为了解决并行开发的问题,依赖倒置原则就产生了!

引入依赖倒置原则后的类图如下:
在这里插入图片描述

司机接口及实现

public interface IDriver {public void drive(ICar car);
}public class Driver implements IDriver {@Overridepublic void drive(ICar car) {car.run();}
}

汽车接口以及奔驰和宝马的实现类

public interface ICar {//是汽车就应该能跑public void run();
}public class BMW implements ICar {//宝马车当然也可以开动了public void run() {System.out.println("宝马汽车开始运行...");}
}public class Benz implements ICar {public void run() {System.out.println("奔驰汽车开始运行...");}
}

在业务场景中,我们贯彻 “抽象不应该依赖细节” ,也就是我们认为抽象 (ICar接口)不依赖BMW和Benz两个实现类(细节),因此在高层次的模块中应用都是抽象的,Client的实现过程如下:

public class Client1 {public static void main(String[] args) {IDriver zhangSan = new Driver();ICar benz = new Benz();//张三开奔驰车zhangSan.drive(benz);}
}

Client属于高层业务逻辑,它对底层模块的依赖都建立在抽象上,zhangSan的表面类型是IDriver,Benz的表面类型是ICar,
这里,也许你要问,在这个高层模块中也调用到了底层模块,比如new Driver() 和 new Benz()等,如何解释?确实如此,zhangSan的表面类型是IDriver,是一个接口,是抽象的、非实体化的,在其后的所有操作中,zhangSan都是以IDriver类型进行操作,屏蔽了细节对抽象的影响。当然,张三如果要开宝马车,修改一下业务场景即可,如下:

public class Client2 {public static void main(String[] args) {IDriver zhangSan = new Driver();ICar bmw = new BMW();//张三开宝马车zhangSan.drive(bmw);}
}

在新增加底层模块时,只修改了业务场景类,也就是高层模块,对其他底层模块如Driver类不需要做任何修改,业务就可以运行,把 “变更” 引起的风险扩散降低到最小。

注意:在Java中,只要定义变量就必然要有类型,一个变量可以有两种类型:表面类型和实际类型,表面类型是在定义的时候赋予的类型,实际类型是对象的类型,如zhangSan的表面类型是IDriver,实际类型是Driver

抽象是对实现的约束,对依赖而言,也是一种契约,不仅仅约束自己,还同时约束自己与外部的关系,其目的是保证所有的细节不脱离契约的范畴,确保约束双方按照既定的契约(抽象)共同发展。

依赖的三种写法

依赖是可以传递的,A对象依赖B对象,B对象依赖C,C又依赖D,生生不息,依赖不止。只要做到抽象依赖,即使是多层的依赖传递也无所谓!

对象的依赖关系有三种方式来传递,如下所示:

  1. 构造函数传递依赖对象
    在类中通过构造函数声明依赖对象,按照依赖注入的说法,这种方式叫做构造函数注入,按照这种方式的注入,IDriver和Driver的程序修改后如代码清单:
public class Driver implements IDriver {private ICar car;//构造函数注入public Driver(ICar _car) {this.car = _car;}//司机的主要职责就是驾驶汽车@Overridepublic void drive(ICar car) {car.run();}
}
  1. Setter方法传递依赖对象
    在抽象中设置Setter方法声明依赖关系,依照依赖注入的说法,这是Setter依赖注入,按照这种方式的注入,IDriver和Driver的程序修改后代码如下:
public interface IDriver {public void drive(ICar car);public void setCar(ICar car);
}public class Driver implements IDriver {private ICar car;public void setCar(ICar _car) {this.car = _car;}@Overridepublic void drive(ICar car) {car.run();}
}
  1. 接口声明依赖对象
    在接口中的方法声明依赖对象,在示例 二的例子就使用了接口声明依赖的方法,该方法也叫接口注入。
public interface IDriver {public void drive(ICar car);
}public class Driver implements IDriver {@Overridepublic void drive(ICar car) {car.run();}
}

总结

依赖倒置原则的本质就是通过抽象(接口或抽象类) 使各个类或模块的实现彼此独立,不互相影响,实现模块间的松耦合,在项目中使用这个规则只需要遵循以下几个规则:

1、每个类尽量都有接口或抽象类,或者抽象类和接口两者都具备
这是依赖倒置的基本要求,接口和抽象类都是属于抽象的,有了
抽象才可能依赖倒置
2、变量的表面类型尽量是接口或者是抽象类
3、任何类都不应该从具体类派生
4、尽量不要覆写基类的方法,如果基类是一个抽象类,而且这个方法已经实现了,子类尽量不要覆写。类间依赖的是抽象,覆写了抽象方法,对依赖的稳定性毁产生一定的影响。
5、结合里氏替换原则使用

“倒置” 到底是什么?先说 “正置”是什么意思,依赖正置就是类间的依赖是实实在在的实现类间的依赖,也就是面向实现编程,这也是正常人的思维方法,我要开奔驰车就依赖奔驰车,我要使用笔记本电脑就直接依赖笔记本电脑,而编写程序需要的是对现实世界的事物进行抽象,抽象的结果就是有了抽象类和接口,然后我们根据系统设计的需要产生了抽象间的依赖,代替了人们传统思维中的事物间的依赖, “倒置” 就是从这里产生的。

依赖倒置原则的优点在小型项目中很难体现出来,例如小于10个人月的项目,主要体现在大中型项目中,采用依赖倒置原则有非常多的优点,特别是规避一些非技术因素引起的问题。

依赖倒置原则是6个设计原则中最难以实现的原则,它是实现开闭原则的重要途径,依赖倒置原则没有实现,就别想实现对扩展开放,对修改关闭。在项目中,只需要记住 “面向接口编程” 基本上抓住了依赖倒置原则的核心。

上述讲了这么多依赖倒置原则的优点,同时也存在缺点,在现实世界中确实存在着必须依赖细节的事物,比如法律,就必须依赖细节的定义。 “杀人偿命” 在中国的法律中古今有之,那这个的 “杀人” 就是一个抽象的定义,怎么杀,杀什么人,为什么杀人,都没有定义,只要是杀人就统统偿命,这就是有问题了,好人杀了坏人,还要赔上自己的一条性命,这是不公正的,从这一点看,在实际项目中使用依赖倒置原则时需要审时度势,不要抓住一个原则不放,每一个原则的优点都是有限度的,并不是放之四海而皆准的真理,所以别为了遵循一个原则而放弃了一个项目的终极目标:投产上线和盈利。

接口隔离原则

在讲接口隔离原则之前,先明确一下我们的主角。接口分为两种:

实例接口 (Object Interface),在Java中声明一个类,然后用new关键字产生一个实例,它是对一个类型的事物的描述,这是一种接口。
比如定义Person这个类,然后使用Person zhangSan = new Person()产生了一个实例,这个实例要遵从的标准就是Person这个类。
类接口(Class Interface),Java中经常使用的interface关键字定义的接口。

主角已经定义清楚了,那什么是隔离呢?它有两种定义。如下所示:

客户端不应该依赖它不需要的接口。
类间的依赖关系应该建立在最小的接口上。

上面两个定义概括为一句话:建立单一接口,不要建立臃肿庞大的接口。再通俗一点讲:接口尽量细化,同时接口中的方法尽量少。

单一职责原则与接口隔离原则的区别:
单一职责:类和接口职责单一,注重职责,这是业务逻辑上的划分
接口隔离:要求接口的方法尽量少。

实例 一

比如定义一下什么是美女,首先要面膜好看,其次是身材要窈窕,然后要有气质,当然了,这三者各人的排列顺序不一样,总之要成为一名美女就必须具备:面貌、身材和气质,我们用类图体现一下星探找美女的过程,如图:
在这里插入图片描述
定义了一个IPettyGirl接口,声明所有的美女都应该有goodLooking、niceFigure和great - Temperament,然后又定义了一个抽象类AbstractSearcher,其作用就是搜索美女并显示其信息,只要美女都按照这个规范定义,Searcher(星探) 就轻松多了,美女类的实现代码清单如下:

// 美女接口
public interface IPettyGirl {//要有姣好的面孔public void goodLooking();//要有好身材public void niceFigure();//要有气质public void greatTemperament();
}public class PettyGirl implements IPettyGirl {private String name;//美女都要有名字public PettyGirl(String _name) {this.name = _name;}@Overridepublic void goodLooking() {System.out.println(this.name+ "~~~~脸蛋很漂亮!");}@Overridepublic void niceFigure() {System.out.println(this.name+ "~~~~气质很好!");}@Overridepublic void greatTemperament() {System.out.println(this.name+ "~~~~身材特别好!");}
}

通过三个方法,把对美女的要求都定义出来了,按照这个标准,如花姑娘被排除在美女标准之外了。有美女,就有搜索美女的星探,其具体实现如代码清单:

星探抽象类:

public abstract class AbstractSearcher {protected  IPettyGirl pettyGirl;public AbstractSearcher(IPettyGirl _pettyGirl){this.pettyGirl = _pettyGirl;}//搜索美女,列出美女信息public abstract void show();
}

星探实现类:

public class Searcher extends AbstractSearcher{public Searcher(IPettyGirl _pettyGirl) {super(_pettyGirl);}//展示美女的信息@Overridepublic void show() {System.out.println("------美女的信息如下:----------");//展示面容super.pettyGirl.goodLooking();//展示身材super.pettyGirl.niceFigure();//展示气质super.pettyGirl.greatTemperament();}
}

场景类:

public class Client {//搜索并展示美女信息public static void main(String[] args) {//定义一个美女IPettyGirl feiFei = new PettyGirl("菲菲");AbstractSearcher searcher = new Searcher(feiFei);searcher.show();}
}

星探搜索美女的运行结果如下所示:

------美女的信息如下:----------
菲菲~~~~脸蛋很漂亮!
菲菲~~~~气质很好!
菲菲~~~~身材特别好!

星探寻找美女的程序开发完毕了,但是接口是否做好了最优设计?答案是没有,还可以对接口进行优化。
我们的审美观点都在变化,美女的定义也在变化。唐朝的杨贵妃如果活在现在这个年代非羞愧而死不可,为什么?胖呀!但是胖并不影响她入选中国四大美女,说明当时的审美观与现在是有差异的。气质型美女也是美女,每个人的定义不一样,不一样要样貌、身材、气质都具备才算美女,所有需要扩展一个美女类,只实现greatTemperament方法,其他两个方法置空,什么都不写,不就可以了吗?但是行不通!为毛呢?星探greatTemperament依赖的是IPettyGirl接口,它有三个方法,你只实现了两个方法,星探的方法是不是又要修改?我们上面的程序打印出来的信息少了两条,还让星探怎么去辨别是不是美女呢?

分析到这里,我们发现接口IPettyGirl的设计是有缺陷的,过于庞大了,容纳了一些可变的因素,根据接口隔离原则,星探AbstractSearcher应该依赖于具有部分特质的女孩子,而我们却把这些特质都封装起来,放到一个接口中,封装过度了,需要重新设计一下类图,修改后的类图如下所示:
在这里插入图片描述
从上述类图可以看到,把原IPettyGirl接口拆分为两个接口,一种是外形美的美女IGoodBodyGirl,这类美女的特别就是脸蛋和身材极棒,另外一种是气质美的美女IGreatTemperamentGirl,谈吐和修养都非常高。

两种类型的美女定义:

public interface IGoodBodyGirl {//要有姣好的面孔public void goodLooking();//要有好身材public void niceFigure();
}
public interface IGreatTemperamentGirl {//要有气质public void greatTemperament();
}

按照脸蛋、身材、气质都具备才算美女,实现类实现两个接口,如代码清单:

public class PettyGirlS implements IGoodBodyGirl, IGreatTemperamentGirl {private String name;//美女都要有名字public PettyGirlS(String _name) {this.name = _name;}@Overridepublic void goodLooking() {System.out.println(this.name+ "~~~~脸蛋很漂亮!");}@Overridepublic void niceFigure() {System.out.println(this.name+ "~~~~气质很好!");}@Overridepublic void greatTemperament() {System.out.println(this.name+ "~~~~身材特别好!");}
}

通过这样的重构以后,不管以后是要气质美女还是要外形美女,都可以保持接口的稳定,以上把一个臃肿的接口变更为两个独立的接口所依赖的原则就是接口隔离原则,接口是我们设计时对外提供的契约,通过分散定义多个接口,可以预防未来变更的扩散,提高系统的灵活性和可维护性。

总结

接口隔离原则是对外接口进行规范约束,其包含以下4层含义:

接口要尽量小(根据接口隔离原则拆分接口的前提必须满足单一职责原则)
接口要高内聚 (提高接口、类、模块的处理能力,减少对外的交互)
定义服务
接口设计是有限度的

接口隔离原则是对接口的定义,同时也是对类的定义,接口和类尽量使用原子接口或原子类来组装。在实践中可以根据以下几个规则来衡量:

一个接口只服务于一个子模块或业务逻辑;
通过业务逻辑压缩接口中的public方法,接口时常去回顾,尽量让接口达成 “满身筋骨肉”,而不是 “肥嘟嘟” 的一大堆方法。
已经被污染了的接口,尽量去修改,若变更的风险较大,则采用适配器模式进行转换处理。
了解环境,拒绝盲从。

设计模式 — 6大设计原则(迪米特法则和开闭原则)


http://chatgpt.dhexx.cn/article/5jp0W7By.shtml

相关文章

【六大设计原则-SOLID】

SOLID简介: 历史:由Robert CMartin汇总并推广 目标: 使软件更容易被改动是软件更容易被理解构建可以在多个软件系统中复用的组件 组成: 名称简写含义单一职责原则 SRP Single Responsibility Principle 初始定义&#xff08…

SOLID原则:解释和实例

在面向对象编程中,SOLID是5个重要的设计原则的缩写。首先是由著名的软件大师Robert C.Martin (Bob 大叔)在Design Principles and Design Patterns 中提出, 后来Michael Feathers 用SOLID来概括这五大原则。 SOLID原则使得软件设计更加容易理解、灵活和可维护。作为一名软件…

SOLID原则的含义和具体使用

单一职责原则(SRP)开放封闭原则(OCP)里氏替换原则(LSP)接口隔离原则(ISP)依赖倒置原则(DIP)小结 SOLID 是面向对象设计5大重要原则的首字母缩写,当…

设计模式:SOLID原则

单一职责原则 Single Responsibility Principle(SRP) 接口职责应该单一,不要承担过多的职责。 开放封闭原则 Open Closed Principle(OCP) 添加一个新的功能应该是,在已有代码基础上扩展代码(…

设计模式之SOLID原则

介绍 设计模式中的SOLID原则,分别是单一原则、开闭原则、里氏替换原则、接口隔离原则、依赖倒置原则。前辈们总结出来的,遵循五大原则可以使程序解决紧耦合,更加健壮。 SRP 单一责任原则 OCP …

SOLID 原则要这么理解!

什么是 SOLID 原则 SOLID 原则其实是用来指导软件设计的,它一共分为五条设计原则,分别是: 单一职责原则(SRP)开闭原则(OCP)里氏替换原则(LSP)接口隔离原则(…

SOLID 设计原则 (有点长但很透彻)

面向对象设计原则 SOLID 应该是职业程序员必须掌握的基本原则,每个程序员都应该了然于胸,遵守这 5个原则可以帮助我们写出易维护、易拓展的高内聚低耦合的代码。 它是由罗伯特C马丁(知名的 Rob 大叔)21世纪初期 (准确来说,2000年在他的论文De…

软件开发SOLID设计原则

前言:SOLID设计原则,不管是软件系统还是代码的实现,遵循SOLID设计原则,都能够有效的提高系统的灵活和可靠性,应对代码实现的需求变化也能更好的扩展和维护。因此提出了五大原则——SOLID。 我是通过老师讲解以及老师…

Python 中的 SOLID 原则

💂 个人网站:【海拥】【摸鱼游戏】【神级源码资源网】🤟 前端学习课程:👉【28个案例趣学前端】【400个JS面试题】💅 想寻找共同学习交流、摸鱼划水的小伙伴,请点击【摸鱼学习交流群】 SOLID 是一组面向对象…

Kotlin SOLID 原则

Kotlin SOLID 原则 许多 Kotlin 开发者并不完全了解 SOLID 原理,即使他们知道,他们也不知道为什么要使用它。您准备好了解所有细节了吗? 介绍 亲爱的 Kotlin 爱好者,您好!欢迎来到我的新文章。今天我要讲的是 Kotli…

超易懂!原来 SOLID 原则要这么理解!

点击蓝色 “陈树义” 关注我哟 说到 SOLID 原则,相信有过几年工作经验的朋友都有个大概印象,但就是不知道它具体是什么。甚至有些工作了十几年的朋友,它们对 SOLID 原则的理解也停留在表面。今天我们就来聊聊 SOLID 原则以及它们之间的关系。…

SOLID五大原则【图解】

目录 前序 五大基本原则-SOLID 1. SRP 2. OCP 3. LSP 4. ISP 5. DIP 参考链接 前序 做C语言开发的应该都知道,C是面向过程开发的,而c是面向对象开发的。而封装、继承与多态是面向对象开发的三大特征。 但你可能不知道OOD(Object-Oriented Desi…

我所理解的SOLID原则

S.O.L.I.D 是面向对象设计(OOD)和面向对象编程(OOP)中的几个重要编码原则(Programming Priciple)的首字母缩写。 面向对象设计的原则 SRP The Single Responsibility Principle单一职责原则OCP The Open Closed Principle开放封闭原则LSP The Liskov Substitution Principle里…

浅谈 SOLID 原则的具体使用

单一职责原则(SRP)开放封闭原则(OCP)里氏替换原则(LSP)接口隔离原则(ISP)依赖倒置原则(DIP)小结 SOLID 是面向对象设计5大重要原则的首字母缩写,当…

设计模式之SOLID原则再回首

本科阶段学过设计模式,那时对设计模式的五大原则——SOLID原则的概念与理解还是比较模糊,此时过去了2年时间,在学习《高级软件工程》课程中老师又提到了设计模式,课程中还详细讨论了五大原则的过程,这次SOLID原则再回首作者提出了一些更通俗的理解吧~ 一. 什么是设计模式&…

程序设计原则之SOLID原则

设计模式中的SOLID原则,分别是单一原则、开闭原则、里氏替换原则、接口隔离原则、依赖倒置原则。前辈们总结出来的,遵循五大原则可以使程序解决紧耦合,更加健壮。 SOLID原则是由5个设计原则组成,SOLID对应每个原则英文字母的开头…

SOLID原则

SOLID原则是一组设计原则,它们旨在帮助开发人员创建易于维护和可扩展的软件系统,这些原则的缩写代表以下5个原则: 1. 单一职责原则(SRP):一个类应该只有一个职责。 2. 开闭原则(OCP)…

【KAFKA】kafka可视化工具kafkaTool 免费下载

【资源是免费的,官网可下载,可是官网下载的网络实在是太慢了有时候还会断线,我也是花了很长时间才下载下来的,提供给大家一个方便】 符合kafka version 0.11 mac 版:链接:https://pan.baidu.com/s/1q6qKrEbaDGukvqH…

windows 安装kafka流程

1、安装jdk 安装地址:www.oracle.com/java/technologies/downloads 下载好后进行安装,基本上一路点击下一步,不要忘记了把安装目录更换一下! 安装好后需要配置环境变量 找到 "计算机-属性-高级系统设置-高级-环境变量“ 1&…

Window下安装Kafka

目录 一、下载安装 二、配置 三、启动 一、下载安装 注意:Kafka安装文件中包含zookeeper 首先打开Kafka的网站:https://kafka.apache.org/ 点击 Download Kafka,选择适合的版本进行下载。 这里后缀 .tgz 格式文件兼容Windows系统&#x…