面向对象设计原则是指导开发者设计灵活、可维护、可扩展代码的核心思想。
这些原则的核心目标是 “高内聚、低耦合”:
单一职责、迪米特法则侧重 “高内聚”(模块 / 对象职责清晰、减少交互);
开闭、里氏替换、接口隔离、依赖倒置、合成复用原则侧重 “低耦合”(模块 / 对象间关联松散、易于扩展)。
一、单一职责原则(Single Responsibility Principle, SRP)
单一职责原则(Single Responsibility Principle,简称 SRP)是面向对象设计中最基础、最核心的原则之一,其核心思想是 “一个类或模块应该只有一个引起它变化的原因”,即只负责一项明确的职责。目的就是让相同职责的代码『高内聚』。
核心定义
职责:指一个类所承担的功能或负责的业务逻辑,通常对应 “引起它变化的原因”。
单一:一个类只能有一个职责,当需求变化时,只会有一个原因导致这个类需要修改。
为什么需要遵守 SRP?
如果一个类承担多个职责,会导致以下问题:
耦合度高:多个职责交织在一起,修改一个职责可能影响其他职责,引发连锁反应(“牵一发而动全身”)。
可读性差:一个类功能混杂,代码逻辑复杂,开发者难以快速理解其核心作用。
可维护性低:职责越多,修改时需要考虑的因素越多,出错风险越高。
复用性差:当需要复用其中一个职责时,不得不引入其他无关的功能代码。
反例(违反 SRP)
一个 UserService 类同时负责 “用户信息管理” 和 “用户登录验证” 两个职责
public class UserService {
// 职责1:用户信息管理(增删改查)
public void addUser(User user) {
... }
public void deleteUser(String userId) {
... }
// 职责2:用户登录验证
public boolean login(String username, String password) {
... }
public void logout() {
... }
}
正例(遵守 SRP)
将职责拆分到两个类:
// 职责1:用户信息管理
public class UserManager {
public void addUser(User user) {
... }
public void deleteUser(String userId) {
... }
}
// 职责2:用户登录验证
public class UserAuthenticator {
public boolean login(String username, String password) {
... }
public void logout() {
... }
}
如何判断一个类是否符合 SRP?
问自己:“这个类有几个不同的功能?” 如果能列举出两个及以上独立的功能,可能需要拆分。
观察变化:如果有两个不同的需求变化会导致这个类修改,说明它承担了多个职责。
总结
单一职责原则的本质是 “分离关注点”—— 让每个类 / 模块专注于自己的核心功能,从而降低复杂度、提高代码的可读性、可维护性和复用性。它是面向对象设计的 “基石”,也是写出清晰、稳健代码的第一步。
二、组合/聚合复用原则(Composite Reuse Principle, CRP)
核心思想
优先通过 “组合 / 聚合” 实现代码复用,而非 “继承”,让复用更灵活、可扩展。
用“对象持有(has-a)”替代“类继承(is-a)”
核心优势
其核心解耦价值,在于用“动态持有关系”替代“静态继承绑定”,彻底打破类之间的强耦合链条,其解耦逻辑的关键是“依赖抽象而非具体”,通过让类持有其他类的接口实例(而非具体实现类)
避免了“继承爆炸”带来的耦合膨胀,复杂功能通过组合多个单一职责的组件实现(如滑动+缩放+旋转功能,由三个独立组件组合而成),每个组件仅专注于自身功能,彼此间无依赖,这种“分而治之”的方式,让类与类之间的关联从“层级嵌套的强绑定”转变为“按需组合的弱关联”,任一组件的修改都局限在自身,不会引发连锁反应

汽车需要的能源类型和不同颜色,通过组合接口的方式进行依赖。通过定义不同的接口分离功能,再通过实现类实现特定功能,符合单一职责原则。
低耦合: Car 与 Electric 是 “使用” 关系(has-a),Electric 修改不直接影响 Car。依赖接口而非具体实现(如A依赖B的接口IB,B的实现修改不影响A)。
高灵活性: 可动态替换发动机(如通过构造or Set函数传入 Electric),无需修改 Car 类。
避免“继承爆炸”:复杂功能通过组合多个单一职责的类实现,而非多层继承(如TextView若通过继承实现“可点击+可滑动”会导致层级臃肿,组合ClickHandler+ScrollHandler更清晰)。
缺点:
接口定义成本:需为每个可组合的功能定义接口(如IClick、IScroll),初期代码量增加。
对象管理复杂:需手动维护组件的创建、生命周期(如组合多个工具类时,需确保资源正确释放)。
组合与聚合的区别
合成复用原则中的 “组合 / 聚合” 是两种关联关系,核心区别在于生命周期是否绑定:
组合(Composition):部分(如 Engine)的生命周期依赖于整体(如 Car),当 Car 销毁时,Engine 也随之销毁(如 “汽车包含发动机”)
┌─────────┐ 组合 ┌─────────┐
│ 整体 │◄────────────►│ 部分 │
│ (Car) │ │(Engine) │
└─────────┘ └─────────┘
(同生共死)
聚合(Aggregation):部分(如 Wheel)的生命周期独立于整体(如 Car),Car 销毁后,Wheel 可被其他对象使用(如 “汽车聚合车轮”)。
┌─────────┐ 聚合 ┌─────────┐
│ 整体 │◄────────────►│ 部分 │
│ (Car) │ │(Wheel) │
└─────────┘ └─────────┘
(各自独立)
反例:过度继承导致的强耦合
// 父类:包含绘制和点击功能
open class BaseView : UIView {
// 绘制逻辑
open fun drawContent(canvas: Canvas) {
... }
// 点击逻辑
open fun handleClick() {
... }
}
// 子类:仅需绘制功能,但被迫继承点击逻辑
class DrawOnlyView : BaseView {
override fun drawContent(canvas: Canvas) {
... } // 仅需重写绘制
// 被迫继承handleClick(),即使不需要,若父类修改该方法,子类可能受影响
override fun handleClick() {
... }
}
正例:组合实现功能复用
// 定义协议(相当于Kotlin的接口):分离功能
protocol Drawable {
func draw(canvas: CGContext)
}
protocol Clickable {
func onClick()
}
// 实现类:单一职责
class CircleDrawer: Drawable {
func draw(canvas: CGContext) {
// 处理圆形绘制的实现
}
}
class ButtonClicker: Clickable {
func onClick() {
// 处理按钮点击的实现
}
}
// 自定义View:通过组合复用功能,按需选择
class CustomView: UIView {
// 持有协议实例(依赖抽象,而非具体实现)
// 若修改为外部传入遵循对应协议的新实现(如SquareDrawer),则符合 开闭原则
private let drawer: Drawable = CircleDrawer()
private let clicker: Clickable = ButtonClicker()
override func draw(


642

被折叠的 条评论
为什么被折叠?



