-
Notifications
You must be signed in to change notification settings - Fork 124
设计模式:六大原则
ZhangPan edited this page Jul 16, 2025
·
4 revisions
定义: 一个类应该只有一个引起它变化的原因,即一个类应该只负责一项职责
核心思想:
每个类只实现单一的职责,否则就应该把类拆开 不仅适用于类的设计,还适用于接口和方法的设计
优点:
- 降低类的复杂度,使代码更清晰易懂
- 提高代码的可维护性和可扩展性
- 更有利于代码高内聚,低耦合
- 可读性更高,每个类职责清晰
- 出现bug或业务变更时,可快速锁定修改范围
// 违反SRP的示例
class Employee {
public void workForProduceManager() { /* 整理需求文档 */ }
public void workForDeveloper() { /* 写代码 */ }
}
// 符合SRP的改进
interface Employee { void work(); }
class ProduceManager implements Employee {
@Override public void work() { /* 整理需求文档 */ }
}
class Developer implements Employee {
@Override public void work() { /* 写代码 */ }
}
定义: 软件实体(类、模块、函数等)应对扩展开放,对修改关闭 。
核心思想:
- 当程序需要扩展时,不去修改原有的代码,而是扩展原有代码
- 用抽象构建框架,用实现扩展细节
优点:
- 减少代码修改风险,提高代码稳定性和可维护性
- 能够扩展已有功能,满足新需求
- 不需要修改已有代码,保证原有功能稳定性
实现方式:
- 充分利用抽象类和接口、多态等特性
- 将可变部分封装起来
- 常见于依赖注入、策略模式等设计模式中
// 违反OCP的示例
class Shape {
int type; // 1=矩形, 2=圆形
}
class GraphicEditor {
public void drawShape(Shape s) {
if(s.type == 1) drawRectangle(s);
else if(s.type == 2) drawCircle(s);
// 添加新形状需要修改此处
}
}
// 符合OCP的改进
abstract class Shape { abstract void draw(); }
class Rectangle extends Shape {
@Override void draw() { /* 画矩形 */ }
}
class Circle extends Shape {
@Override void draw() { /* 画圆形 */ }
}
class GraphicEditor {
public void drawShape(Shape s) { s.draw(); } // 无需修改
}
定义: 所有引用基类的地方必须能透明地使用其子类的对象。
核心思想:
- 子类必须能够替换其基类而不会引起程序行为的改变
- 应优先使用对象组合而不是类继承
- 由Barbara Liskov在1987年提出
原则要求:
- 子类必须实现父类抽象方法,但不得重写(覆盖)父类的非抽象方法
- 子类中可以增加自己特有的方法
- 子类方法的前置条件(入参)要比父类更宽松
- 子类方法的后置条件(返回值)要比父类更严格
// 违反LSP的示例
class Bird { void fly() { /* 鸟会飞 */ } }
class Ostrich extends Bird { // 鸵鸟不会飞
@Override void fly() { throw new UnsupportedOperationException(); }
}
// 符合LSP的改进
abstract class Bird { abstract void move(); }
class Sparrow extends Bird {
@Override void move() { /* 飞 */ }
}
class Ostrich extends Bird {
@Override void move() { /* 跑 */ }
}
设计意义:
- 继承复用的基石,只有当衍生类可以替换基类,基类才能真正被复用
- 对"开-闭"原则的补充,是实现抽象化的具体步骤的规范
定义: 客户端不应该依赖它不需要的接口;类间的依赖关系应该建立在最小的接口上。
核心思想:
- 每个接口中不存在子类用不到却必须实现的方法
- 将臃肿的接口拆分为多个功能单一的独立接口
优点:
- 降低系统耦合度
- 提升系统灵活性:新增功能只需扩展特定接口
- 增强代码可维护性:接口变更影响范围限定在相关客户类内部
与SRP的区别:
- SRP强调类职责单一,ISP侧重接口依赖精简
- SRP作用于方法/类级别,ISP聚焦接口架构设计
- SRP提升内聚性,ISP降低耦合度
// 违反ISP的示例
interface Animal {
void eat();
void swim(); // 对不会游泳的动物是负担
}
class Dog implements Animal {
@Override public void eat() { /* 吃 */ }
@Override public void swim() { throw new UnsupportedOperationException(); }
}
// 符合ISP的改进
interface Eatable { void eat(); }
interface Swimmable { void swim(); }
class Dog implements Eatable { /* 只需实现eat */ }
class Fish implements Eatable, Swimmable { /* 实现两者 */ }
实现模式:
- 委托模式:通过创建适配器类实现接口转发
- 多重继承:允许目标类同时继承多个专用接口
定义: 高层模块不应该依赖低层模块,二者都应该依赖于抽象;抽象不应该依赖于细节,细节应该依赖于抽象。
核心思想:
- 面向接口编程,依赖于抽象而不依赖于具体
- 相对于细节的多变性,抽象的东西要稳定得多
实现技术:
- 依赖注入(DI):通过构造器、setter或接口注入依赖
- 控制反转(IoC):将控制权交给容器或框架
优点:
- 降低类间耦合度,提高系统稳定性
- 提高代码可读性、可维护性和扩展性
- 促进高内聚低耦合设计
// 违反DIP的示例
class Computer {
private Keyboard keyboard;
private Mouse mouse;
// 直接依赖具体实现
}
// 符合DIP的改进
interface InputDevice { void input(); }
class Keyboard implements InputDevice { /* 实现 */ }
class Mouse implements InputDevice { /* 实现 */ }
class Computer {
private List<InputDevice> devices; // 依赖抽象
}
高级应用:
- 框架设计与扩展
- 分层架构与解耦
- 插件架构与动态扩展
- 策略模式的运用
定义: 一个对象应该对其他对象有最少的了解,只与直接的朋友通信。
核心思想:
- 类之间只要有耦合关系,就是朋友关系
- 一个类对自己依赖的类知道的越少越好
- 尽量减少一个对象对其他对象的直接依赖,尤其是对链式调用的依赖
优点:
- 降低类间耦合
- 提高模块的相对独立性
- 当依赖的类发生变化时,才能最小的影响该类
// 违反LoD的示例
class Customer {
private Wallet wallet;
public float getWalletMoney() {
return wallet.getMoney(); // 直接访问钱包内部
}
}
// 符合LoD的改进
class Customer {
private Wallet wallet;
public float pay(float payment) {
return wallet.subtractMoney(payment); // 通过钱包提供的接口操作
}
}
实践建议:
- 在类结构设计上,尽量降低成员的访问权限
- 优先考虑将类设置成不变类
- 尽量降低一个类对另一个类的依赖
设计模式六大原则首字母联合起来就是SOLID(两个L算做一个),代表建立稳定、灵活、健壮的设计。这些原则相互关联、相辅相成:
- 单一职责原则是实现高内聚低耦合的基石
- 开闭原则是面向对象设计的终极目标
- 里氏替换原则是实现继承复用的前提
- 接口隔离原则是降低系统耦合度的有效手段
- 依赖倒置原则是面向接口编程的核心
- 迪米特法则是减少类间依赖的实践指南
在实际开发中,这些原则需要灵活运用而非机械套用。理解原则背后的思想比死守规则更重要,有时需要在不同原则间做出权衡。优秀的设计往往是这些原则综合应用的结果,它们共同指导我们构建更健壮、更易维护的软件系统
- JMM与volatile关键字
- synchronized的实现原理
- synchronized等待与唤醒机制
- ReentrantLock的实现原理
- ReentrantLock等待与唤醒机制
- CAS、Unsafe类以及Automic并发包
- ThreadLocal的实现原理
- 线程池的实现原理
- Java线程中断机制
- 多线程与并发常见面试题
- Android基础知识汇总
- MVC、MVP与MVVM
- SparseArray实现原理
- ArrayMap的实现原理
- SharedPreferences
- Bitmap
- Activity的启动模式
- Fragment核心原理
- 组件化项目架构搭建
- 组件化WebView架构搭建
- 为什么 Activity.finish() 之后 10s 才 onDestroy ?
- Android系统启动流程
- InputManagerService
- WindowManagerService
- Choreographer详解
- SurfaceFlinger
- ViewRootImpl
- APP启动流程
- Activity启动流程
- PMS 安装与签名校验
- Dalvik 与 ART
- 内存优化策略
- UI界面及卡顿优化
- App启动优化
- ANR问题
- 包体积优化
- APK打包流程
- 电池电量优化
- Android屏幕适配
- 线上性能监控1--线上监控切入点
- 线上性能监控2--Matrix实现原理
- Glide实现原理
- OkHttp实现原理
- Retrofit实现原理
- RxJava实现原理
- RxJava中的线程切换与线程池
- LeakCanary实现原理
- ButterKnife实现原理
- ARouter实现原理
- Tinker实现原理
- 2. 两数相加
- 19.删除链表的倒数第 N 个结点
- 21. 合并两个有序链表
- 24. 两两交换链表中的节点
- 61. 旋转链表
- 86. 分隔链表
- 92. 反转链表 II
- 141. 环形链表
- 160. 相交链表
- 206. 反转链表
- 206 反转链表 扩展
- 234. 回文链表
- 237. 删除链表中的节点
- 445. 两数相加 II
- 面试题 02.02. 返回倒数第 k 个节点
- 面试题 02.08. 环路检测
- 剑指 Offer 06. 从尾到头打印链表
- 剑指 Offer 18. 删除链表的节点
- 剑指 Offer 22. 链表中倒数第k个节点
- 剑指 Offer 35. 复杂链表的复制
- 1. 两数之和
- 11. 盛最多水的容器
- 53. 最大子序和
- 75. 颜色分类
- 124.验证回文串
- 167. 两数之和 II - 输入有序数组 -169. 多数元素
- 189.旋转数组
- 209. 长度最小的子数组
- 283.移动0
- 303.区域和检索 - 数组不可变
- 338. 比特位计数
- 448. 找到所有数组中消失的数字
- 643.有序数组的平方
- 977. 有序数组的平方