设计原则并非必须严格遵守,而是更像指导建议,这里推荐10个常用设计原则:

一、SOLID 原则(含5种设计原则)

SOLID 是面向对象设计中最核心、最广为人知的五个原则的首字母缩写,该设计原则对于写出干净可维护代码有极高的指导价值。

1. 单一职责原则 (Single Responsibility Principle, SRP)

  • 核心思想:一个类应该只有一个引起它变化的原因。

  • 通俗理解:让每个类都专注于一件事,做好一件事。例如,一个用户类只负责管理用户数据,而不应同时负责将用户保存到数据库或发送欢迎邮件。职责的分离使得代码更清晰,修改风险更低。

  • 个人理解:这不仅限于类,对于方法/函数也适用。

2. 开闭原则 (Open/Closed Principle, OCP)

  • 核心思想:软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。

  • 通俗理解:当需要添加新功能时,应该通过增加新代码来实现,而不是去修改已有的、经过测试的稳定代码。这就像给手机增加App,而不是去修改手机的操作系统内核。

3. 里氏替换原则 (Liskov Substitution Principle, LSP)

  • 核心思想:子类对象必须能够替换掉所有父类对象,而不会导致程序出错。

  • 通俗理解:“儿子”应该能完美地扮演“父亲”的角色。如果一个函数接收一个父类对象,那么传入一个子类对象时,函数的行为应该依然正确。这保证了继承关系的正确性和多态的有效性。

  • 个人理解:举个例子,玩家对象有个装备武器的方法,传入单持刀对象,而若有单持刀子类是双持刀,传入后装备武器方法依然正确。

4. 接口隔离原则 (Interface Segregation Principle, ISP)

  • 核心思想:客户端不应该被迫依赖于它们不使用的方法。

  • 通俗理解:多个特定的客户接口要好于一个通用的大接口。不要强迫一个类去实现它根本用不到的方法。这就像给员工发工具,应该按需分配,而不是把整个工具箱都塞给他。

5. 依赖倒置原则 (Dependency Inversion Principle, DIP)

  • 核心思想:高层模块不应该依赖低层模块,二者都应该依赖其抽象。要面向接口编程,而不是面向实现编程。

  • 通俗理解:业务逻辑(高层)不应该直接依赖具体的数据库或网络请求(低层),而应该依赖一个抽象的接口。这样,更换底层实现时,高层业务逻辑无需任何改动,极大地降低了模块间的耦合度。

二、其它重要原则(5种设计原则)

1. 迪米特法则 (Law of Demeter, LoD)

  • 核心思想:也叫“最少知识原则”。一个对象应该对其他对象有最少的了解。只与你“直接的朋友”通信,不要和“陌生人”说话。

  • 通俗理解:减少对象之间的直接联系。例如,一个人想开车,应该直接和“车”交互,而不是去操作车里的“发动机”。这能有效降低类之间的耦合,提高系统的封装性。

2. 组合/聚合复用原则 (Composition/Aggregation Reuse Principle, CARP)

  • 核心思想:优先使用组合(或聚合),而不是继承来复用代码。

  • 通俗理解:“有一个(Has-a)”的关系通常比“是一个(Is-a)”的关系更灵活、更安全。继承是白盒复用,耦合度高;组合是黑盒复用,耦合度低。当需要复用功能时,应优先考虑将已有类的对象作为新类的成员。

3. DRY 原则 (Don't Repeat Yourself)

  • 核心思想:系统中的每一处知识都必须有一个唯一、明确、权威的表示。

  • 通俗理解:避免代码重复。相同的逻辑不应该在多个地方出现,否则修改一处时,很容易忘记修改其他地方,从而引入 bug。

4. KISS 原则 (Keep It Simple, Stupid)

  • 核心思想:保持简单。

  • 通俗理解:设计应尽可能简洁,避免不必要的复杂性。简单的代码更容易理解、调试和维护。

5. YAGNI 原则 (You Aren't Gonna Need It)

  • 核心思想:你不会需要它。

  • 通俗理解:不要预先实现“可能”会用到的功能。只实现当前明确需要的功能,避免过度设计和浪费。

三、汇总表

设计原则

核心思想

通俗理解

其它说明

(含个人理解)

S

O

L

I

D

单一职责

一个类应该只有一个引起它变化的原因

让每个类都专注于一件事,做好一件事。例如,一个用户类只负责管理用户数据,而不应同时负责将用户保存到数据库或发送欢迎邮件。职责的分离使得代码更清晰,修改风险更低。

这不仅限于类,对于方法/函数也适用。

开闭原则

软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。

当需要添加新功能时,应该通过增加新代码来实现,而不是去修改已有的、经过测试的稳定代码。这就像给手机增加App,而不是去修改手机的操作系统内核。

里氏替换

子类对象必须能够替换掉所有父类对象,而不会导致程序出错。

“儿子”应该能完美地扮演“父亲”的角色。如果一个函数接收一个父类对象,那么传入一个子类对象时,函数的行为应该依然正确。这保证了继承关系的正确性和多态的有效性。

举个例子,玩家对象有个装备武器的方法,传入单持刀对象,而若有单持刀子类是双持刀,传入后装备武器方法依然正确。

接口隔离

客户端(调用端)不应该被迫依赖于它们不使用的方法。

多个特定的客户接口要好于一个通用的大接口。不要强迫一个类去实现它根本用不到的方法。这就像给员工发工具,应该按需分配,而不是把整个工具箱都塞给他。

依赖倒置

高层模块不应该依赖低层模块,二者都应该依赖其抽象。要面向接口编程,而不是面向实现编程。

业务逻辑(高层)不应该直接依赖具体的数据库或网络请求(低层),而应该依赖一个抽象的接口。这样,更换底层实现时,高层业务逻辑无需任何改动,极大地降低了模块间的耦合度。

迪米特法则

也叫“最少知识原则”。一个对象应该对其他对象有最少的了解。只与你“直接的朋友”通信,不要和“陌生人”说话。

减少对象之间的直接联系。例如,一个人想开车,应该直接和“车”交互,而不是去操作车里的“发动机”。这能有效降低类之间的耦合,提高系统的封装性。

组合/聚合复用原则

优先使用组合(或聚合),而不是继承来复用代码。

“有一个(Has-a)”的关系通常比“是一个(Is-a)”的关系更灵活、更安全。继承是白盒复用,耦合度高;组合是黑盒复用,耦合度低。当需要复用功能时,应优先考虑将已有类的对象作为新类的成员。

DRY 原则

系统中的每一处知识都必须有一个唯一、明确、权威的表示。

避免代码重复。相同的逻辑不应该在多个地方出现,否则修改一处时,很容易忘记修改其他地方,从而引入 bug。

KISS 原则

保持简单。

设计应尽可能简洁,避免不必要的复杂性。简单的代码更容易理解、调试和维护。

YAGNI 原则

你不会需要它。

不要预先实现“可能”会用到的功能。只实现当前明确需要的功能,避免过度设计和浪费。