6大设计原则
一、单一职责原则
- 定义
单一职责原则(SRP:Single responsibility principle)又称单一功能原则。它规定一个类应该只有一个发生变化的原因。 - 特点
降低类的复杂性, 对类或接口的职责有清晰明确定义
提高可读性
提高可维护性
降低变更引起的风险, 接口改变只影响相应的实现类, 不影响其他类
3. 反面实例
示例要求:
1)创建一个绘图系统
2)绘图:可以绘制圆形,矩形
3)显示:显示绘制绘制好的图像
一般编写结果:
单一原则结果:
- 重点
接口一定要做到单一职责;
类的单一职责比较难以实现, 尽量做到只有一个原因引起变化;
一个方法尽可能做一件事, 能分解就分解, 分解到原子级别; - 适用范围
接口、类、方法
二、里氏替换原则
-
定义
所有引用基类的地方必须能透明地使用其子类的对象。
通俗点讲:只要父类出现的地方子类就可以出现, 替换为子类也不会产生任何错误, 使用者不需要知道父类还是子类。 -
需求
玩家玩射击气球游戏,他既能使用AK仿真枪,也可以使用步枪还可以使用手枪射击气球。 -
优缺点
里氏替换原则核心是继承,同样它的优缺点也是继承的优缺点
继承的优点
代码共享 : 共享代码, 子类都拥有父类的方法和属性, 将 父类的代码共享给了子类;
重用性 : 提高代码的重用性, 子类重用父类的代码;
子父类异同 : 子类形似父类, 异于父类, 父子都不同;
扩展性 : 提高代码的可扩展性, 子类就可以为所欲为了, 子类可以随意扩展父类;
开放性 : 提高产品或项目的开放性, 父类随意扩展, 开放性随之增加了;
继承缺点
侵入性 : 继承是侵入性的, 子类 强制继承 父类的方法和属性;
灵活性 : 降低代码的灵活性, 子类必须拥有父类的属性和方法, 子类收到了父类的约束, 这是从子类的角度讲得;
耦合性 : 增强了耦合性, 父类的属性和方法被修改时, 还需要顾及其子类, 可能会带来大
量的重构, 这是从父类的角度讲的;
优点: 将子类单独作为一个业务来使用, 会让代码间的耦合关系都复杂, 缺乏类替换标准。
缺点: 将子类当做父类用, 抹杀了子类的个性。
重点
返回值: 父类方法返回值类型 F, 子类方法返回值类型 S, 里氏替换原则是 S 范围必须小于 F;
重载: 父类 子类 方法参数类型或者数量不同, 如果要符合里氏替换要求的话, 子类参数必须 >= 父类参数, 即不能让子类自己定义的方法被调用;
三、依赖倒置原则
- 定义
细节依赖抽象 : 高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节.
细节应该依赖抽象。
抽象就是接口和抽象类
细节就是具体的实现类
2. 问题由来
类A直接依赖类B,假如要将类A改为依赖类C,则必须通过修改类A的代码来达成。这种场景下,类A一般是高层模块,负责复杂的业务逻辑;类B和类C是低层模块,负责基本的原子操作;假如修改类A,会给程序带来不必要的风险。
解决办法
将类A修改为依赖接口I,类B和类C各自实现接口I,类A通过接口I间接与类B或者类C发生联系,则会大大降低修改类A的几率。
- 依赖倒置好处
减少类之间的耦合
提高系统稳定性
降低并发风险
提高代码可读性 - 依赖倒置注入实现
构造函数依赖对象
注入方法 : 通过构造函数参数声明依赖对象, 即构造函数注入;
Setter 方法依赖对象
通过 Setter 函数参数声明依赖对象, 即Setter方法注入;
接口注入依赖对象
在接口方法的参数中声明依赖对象, 即接口注入;
-
依赖倒置本质
通过抽象即接口或者抽象类, 使各个类和模块实现彼此独立, 实现模块间松耦合; -
注意点
尽量不要覆盖方法, 如果方法在抽象类中已经实现, 子类不要覆盖。 -
使用场景
1)依赖倒置在小项目中得有点很难体现出来, 是否采用依赖倒置原则影响不大。
2)项目越大, 需求改变越多, 采用依赖倒置原则设计的接口或抽象类 对 实现类的约束, 会大大减少维护成本。
四、接口隔离原则
- 定义
接口隔离定义 : 建立单一的接口, 功能尽量细化, 不要建立臃肿的接口;
不需要的接口 : 客户端尽量不依赖其不需要的接口, 客户端需要什么接口就提供什么接口, 剔除不需要的接口, 对接口进行细化, 保持接口方法最少;
最小接口 : 类间的依赖关系应该建立在最小接口上, 细化接口;
2. 单一职责与接口隔离区别
单一原则:注重职责, 注重业务逻辑上得划分;
接口隔离:注重的是接口的方法尽量少
3. 特点
接口尽量小
拆分接口 : 接口隔离的核心定义, 不出现臃肿的接口;
接口高内聚
高内聚 : 提高接口, 类, 模块的处理能力, 减少对外界交互;
具体方法 : 接口中尽量少公布public 方法, 对外公布的 public 方法越少, 变更的风险就越小, 有利于后期的维护;
定制服务
起源 : 系统模块间的耦合需要有相互访问的接口, 这里就需要为各个 客户端 的访问提供定制的服务接口;
要求 : 只提供访问者需要的方法, 不需要的就不提供;
接口隔离限度
粒度小 : 接口粒度越小, 系统越灵活, 但是同时使系统结构复杂, 开发难度增加, 降低了系统的可维护性;
粒度大 : 灵活性降低, 无法提供定制服务, 增大项目风险;
4. 原子接口划分原则
接口模块对应关系 : 一个接口只服务于一个子模块 或 业务逻辑;
方法压缩 : 通过业务逻辑, 压缩接口中得 public 方法, 减少接口的方法的数量;
修改适配 : 尽量去修改已经污染的接口, 如果变更风险较大, 采用适配器模式进行转化处理;
五、迪米特法则
-
定义
最少知识原则, 一个对象应该对其它对象有最少的了解, 即一个类对自己需要耦合或者调用的类知道的最少; -
生活示例看代码
朋友间必须保持距离
距离太近示例 : 朋友间不能无话不说, 无所不知, 类 A 与 B 耦合, B 将很多方法暴露给 A, 两个类之间的的耦合关系非常牢固, 这明显违反设计原则;
保持距离方法 : 将 类 B 暴露给 A 的方法封装, 暴露的方法越少越好, 类 B 高内聚, 与 A 低耦合;
设计方法 : 一个类的 public 方法越多, 修改时涉及的范围也就越大, 变更引起的风险也就越大, 在系统设计时要注意, 能用 private 就用private , 能用 protected 就用 protected, 能少用 public 就少用 public, 能加上 final 就加上 final;
- 优缺点
优点
类间解耦, 弱耦合, 耦合降低, 复用率提高。
缺点
类间的耦合性太低, 会产生大量的中转或跳转类, 会导致系统的复杂性提高, 加大维护难度。
六、开闭原则
-
定义
软件的实体 类, 模块, 函数 应该对扩展开放, 对修改关闭; 即 软件实体 应该 通过扩展实现变化, 不是通过 修改已有的代码实现变化; -
优点
利于测试
如果改变软件内容, 需要将所有的测试流程都执行一遍, 如 单元测试, 功能测试, 集成测试等, 如果只是扩展, 只单独测试扩展部分即可;
提高复用性
所有逻辑都从原子逻辑组合, 原子逻辑粒度越小, 复用性越大; 这样避免相同逻辑存在, 修改时需要修改多个此相同逻辑;
提高可维护性
维护一个类最好的方式是 扩展一个类, 而不是修改一个类, 如果需要修改需要读懂源码才能修改, 扩展的话只需要了解即可, 直接继承扩展;
为什么要用开闭原则
-
开闭原则非常著名,只要是做面向对象编程的,在开发时都会提及开闭原则。
-
开闭原则是最基础的一个原则,前面介绍的5个原则都是开闭原则的具体形态,而开闭原则才是其精神领袖。
-
开闭原则提高了复用性,以及可维护性。
总结:
六大设计原则的核心:
抽象、单一、最小化、面向接口编程、高扩展性、高内聚、低耦合
总结六大设计原则
单一职责原则:一个类或接口只承担一个职责。
里氏替换原则:在继承类时,务必重写(override)父类中所有的方法,尤其需要注意父类的protected方法(它们往往是让你重写的),子类尽量不要暴露自己的public方法供外界调用。
依赖倒置原则:高层模块不应该依赖于低层模块,而应该依赖于抽象。抽象不应依赖于细节,细节应依赖于抽象。
接口隔离原则:不要对外暴露没有实际意义的接口。
迪米特法则:尽量减少对象之间的交互,从而减小类之间的耦合。
开闭原则:对软件实体的改动,最好用扩展而非修改的方式。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!