从编码到设计模式:系统是如何被组织的?

本文最后更新于 2025年9月10日 下午

从编码开始

当我们开始实现一个需求时,首先关注的是”如何实现功能?”以虚拟房子为例,用面向对象思路组织基本结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class House {
private Bathroom bathroom;
private Bedroom bedroom;
private Kitchen kitchen;

public House() {
this.bathroom = new Bathroom();
this.bedroom = new Bedroom();
this.kitchen = new Kitchen();
}

*// 提供访问各个房间的方法*
public void useBathroom() { */* ... */* }
public void rest() { */* ... */* }
public void cook() { */* ... */* }
}

class Bathroom {
private Shower shower;
private Mirror mirror;

public void takeShower() { shower.start(); }
public void lookInMirror() { mirror.reflect(); }
}

class Bedroom {
private Bed bed;
private Chair chair;

public void sleep() { bed.layDown(); }
public void sit() { chair.sitOn(); }
}

class Kitchen {
private Knife knife;
private Dish dish;

public void prepareMeal() { knife.cut(); }
public void serveMeal() { dish.serve(); }
}

通过面向对象,我们可以明确地描述对象具备的功能与方法,创建可操作的对象结构。

系统中的对象角色

房子并非目的本身,构建房子是为了更好地利用这个结构完成我们的目标。我们可以将对象分为三种角色:

1. 事务发起者 (Initiator)

  • 职责:定义需要达成的目的,orchestrate整个处理流程
  • 特征:了解业务目标,控制执行顺序
  • 示例PersonLivingInHouse 类,决定何时睡觉、做饭、洗澡

2. 事务描述者 (Domain Object)

  • 职责:描述被处理事务的状态和行为规则
  • 特征:封装业务逻辑,维护对象状态
  • 示例HouseRoom 等领域对象

3. 过程处理者 (Service/Handler)

  • 职责:处理事务在特定阶段的逻辑,协调对象间交互
  • 特征:无状态,专注于处理逻辑
  • 示例HousekeepingServiceMealPreparationService

语义关系重新定义

基于面向对象设计原则,类与类之间主要有以下语义关系:

IS-A 关系 (继承)

1
2
3
4
5
6
7
8
abstract class Room {
protected String name;
public abstract void clean();
}

class Bedroom extends Room {
public void clean() { */* 卧室特有的清洁方式 */* }
}
  • 语义:描述类型层次关系,子类是父类的特化
  • 原则:子类必须能完全替代父类(里氏替换原则)
  • 创建:通过继承机制,子类自动获得父类的属性和方法

HAS-A 关系 (组合/聚合)

1
2
3
4
class House {
private List<Room> rooms; *// 组合关系*
private Address address; *// 聚合关系*
}
  • 语义:描述整体与部分的关系
  • 区别:组合是强拥有(生命周期一致),聚合是弱拥有
  • 创建:通过成员变量持有其他对象的引用

USE-A 关系 (依赖)

1
2
3
4
5
class Person {
public void liveIn(House house) {
house.useBathroom(); *// 依赖关系*
}
}`
  • 语义:描述临时性的使用关系
  • 特征:对象间耦合度最低,通过参数传递或临时创建
  • 创建:通过方法参数、局部变量或工厂模式获得对象

CAN-DO 关系 (接口实现)

1
2
3
4
5
6
7
interface Cleanable {
void clean();
}

class Room implements Cleanable {
public void clean() { */* 实现清洁功能 */* }
}
  • 语义:描述对象具备某种能力或契约
  • 原则:定义what而不是how,支持多重实现
  • 创建:通过实现接口方法,获得多态能力

定义与调用分离:不同关系模式的解耦机制

在面向对象设计中,每种关系模式都代表了不同的”定义与调用分离”策略。核心在于回答三个问题:

  • 定义权:属性和方法由谁定义?
  • 调用权:属性和方法由谁调用?
  • 管理权:对象生命周期由谁管理?

继承关系:纵向的定义与调用分离

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
abstract class Vehicle {
protected String brand; // 父类定义通用属性
public abstract void start(); // 父类定义调用契约

public void displayInfo() { // 父类定义并实现调用逻辑
System.out.println("Brand: " + brand);
start(); // 调用子类的具体实现
}
}

class Car extends Vehicle {
public void start() { // 子类定义具体实现
System.out.println("Car engine starts");
}
}

分离特征

  • 定义分工:父类定义契约和通用逻辑,子类定义具体实现
  • 调用分工:父类负责统一调用逻辑,子类提供被调用的实现
  • 管理模式:通过多态机制,调用者无需知道具体类型,只需面向抽象编程
1
2
3
Vehicle vehicle = new Car(); // 调用者只需要知道Vehicle接口
vehicle.displayInfo(); // 父类控制调用流程,子类提供实现

接口关系:契约式的定义与调用分离

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
interface Flyable {
void fly(); // 接口定义调用契约,不提供实现
}

class Bird implements Flyable {
public void fly() { // 实现类定义具体实现
System.out.println("Bird flies");
}
}

class FlightController {
public void controlFlight(Flyable flyable) { // 调用者面向接口调用
flyable.fly(); // 不关心具体是谁实现的fly方法
}
}

分离特征

  • 定义分工:接口定义what(契约),实现类定义how(具体实现)
  • 调用分工:调用者面向接口调用,完全不依赖具体实现
  • 管理模式:调用者通过依赖注入或参数传递获得实例,不负责创建

组合关系:委托式的定义与调用分离

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Engine {
private boolean running = false;

public void start() { // 被组合类定义自己的行为
running = true;
System.out.println("Engine started");
}

public boolean isRunning() { return running; }
}

class Car {
private Engine engine; // 组合类拥有被组合对象

public Car() {
this.engine = new Engine(); // 组合类负责创建和管理
}

public void start() { // 组合类定义对外接口
engine.start(); // 委托给被组合对象执行
System.out.println("Car is ready");
}
}

分离特征

  • 定义分工:被组合类定义内部行为,组合类定义对外接口和协调逻辑
  • 调用分工:外部调用组合类接口,组合类内部调用被组合对象方法
  • 管理模式:组合类全权负责被组合对象的生命周期管理

依赖关系:临时式的定义与调用分离

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class EmailService {
public void sendEmail(String message) { // 被依赖类定义服务能力
System.out.println("Sending: " + message);
}
}

class OrderProcessor {
// 依赖类定义使用场景和调用时机
public void processOrder(Order order, EmailService emailService) {
// 处理订单逻辑...

// 在需要时调用被依赖对象的服务
emailService.sendEmail("Order processed: " + order.getId());
}
}

分离特征

  • 定义分工:被依赖类定义服务能力,依赖类定义使用场景
  • 调用分工:依赖类在特定时机调用被依赖类的服务
  • 管理模式:被依赖对象由外部创建和管理,依赖类只负责使用

所谓解耦,就是将定义,调用,生命周期分离的设计过程。

SOLID原则

单一职责原则

核心:一个类应该只有一个引起它变化的原因。这意味着在定义层面上一个类只对其关注的处理过程负责,通过这种设计方便于减少与其他类之间的依赖,同时因为功能与类的一一对应,减少了后续扩展时的修改成本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 违反SRP - 订单类承担了太多职责
class Order {
private List<Item> items;
private Customer customer;

public void addItem(Item item) { /* ... */ }

// 职责1:计算逻辑
public double calculateTotal() { /* ... */ }

// 职责2:持久化逻辑
public void saveToDatabase() { /* ... */ }

// 职责3:通知逻辑
public void sendConfirmationEmail() { /* ... */ }
}

// 遵循SRP - 职责分离
class Order {
private List<Item> items;
private Customer customer;

public void addItem(Item item) { /* ... */ }
public double calculateTotal() { /* ... */ }
}

class OrderRepository {
public void save(Order order) { /* 持久化逻辑 */ }
}

class OrderNotificationService {
public void sendConfirmation(Order order) { /* 通知逻辑 */ }
}

开闭原则

核心:对扩展开放,对修改封闭。这意味着基类提供基础明确的功能定义,派生类完成对原功能的增强和扩展。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 违反OCP - 每次新增形状都要修改AreaCalculator
class AreaCalculator {
public double calculateArea(Object shape) {
if (shape instanceof Rectangle) {
Rectangle rect = (Rectangle) shape;
return rect.width * rect.height;
} else if (shape instanceof Circle) {
Circle circle = (Circle) shape;
return Math.PI * circle.radius * circle.radius;
}
// 每次新增形状都要在这里添加if-else
return 0;
}
}

// 遵循OCP - 通过抽象实现扩展
interface Shape {
double calculateArea();
}

class Rectangle implements Shape {
public double calculateArea() {
return width * height;
}
}

class AreaCalculator {
public double calculateArea(Shape shape) {
return shape.calculateArea(); // 新增形状无需修改此方法
}
}

里氏替换原则

核心:子类对象必须能够替换其基类对象。通过这条规则,我们可以更好的完成定义阶段和调用阶段的解耦,子类服从于标准的父类实现,就可以在编码阶段优先完成过程的设计。多态的安全边界,建立在语义契约的一致性之上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 正确的LSP示例
abstract class Bird {
public abstract void move();
}

class Sparrow extends Bird {
public void move() {
fly(); // 麻雀通过飞行移动
}

private void fly() { /* 飞行实现 */ }
}

class Penguin extends Bird {
public void move() {
walk(); // 企鹅通过走路移动
}

private void walk() { /* 行走实现 */ }
}

// 客户端代码可以统一处理所有鸟类
public void moveBird(Bird bird) {
bird.move(); // 无论是麻雀还是企鹅都能正确工作
}

接口隔离原则

核心:客户端不应该依赖它不需要的接口。通过接口隔离,我们可以更好的在编码阶段定义业务处理过程,面向一个过程,只需要该过程的具体实现,而不需要关系该实现类具备的其他能力。接口不是能力的集合,而是角色的边界。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 违反ISP - 胖接口
interface Worker {
void work();
void eat();
void sleep();
}

class Robot implements Worker {
public void work() { /* 机器人工作 */ }
public void eat() { /* 机器人不需要吃饭! */ }
public void sleep() { /* 机器人不需要睡觉! */ }
}

// 遵循ISP - 接口隔离
interface Workable {
void work();
}

interface Eatable {
void eat();
}

interface Sleepable {
void sleep();
}

class Human implements Workable, Eatable, Sleepable {
public void work() { /* ... */ }
public void eat() { /* ... */ }
public void sleep() { /* ... */ }
}

class Robot implements Workable {
public void work() { /* 机器人只需要实现工作接口 */ }
}

依赖倒置原则

核心:高层模块不应该依赖低层模块,两者都应该依赖于抽象。依赖倒置原则讨论的是设计与实现的分离。高层模块服务于业务流程,而底层模块则服务于功能实现。两者通过抽象完成从流程到每一阶段的业务处理的绑定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 违反DIP - 高层模块直接依赖低层模块
class EmailService {
public void sendEmail(String message) { /* ... */ }
}

class OrderProcessor {
private EmailService emailService = new EmailService(); // 直接依赖具体类

public void processOrder(Order order) {
// 处理订单...
emailService.sendEmail("Order processed");
}
}

// 遵循DIP - 依赖抽象
interface NotificationService {
void sendNotification(String message);
}

class EmailService implements NotificationService {
public void sendNotification(String message) { /* ... */ }
}

class OrderProcessor {
private NotificationService notificationService; // 依赖抽象

public OrderProcessor(NotificationService notificationService) {
this.notificationService = notificationService;
}

public void processOrder(Order order) {
// 处理订单...
notificationService.sendNotification("Order processed");
}
}

设计模式

所谓设计模式,就是在上述原则和解耦思想下提出的依据业务来实现的对象设计处理框架。设计模式不是凭空产生的编程技巧,而是在长期的软件开发实践中,针对特定问题场景总结出的成熟解决方案。每一个设计模式都体现了特定的解耦策略,帮助我们在复杂的业务逻辑中保持代码的灵活性和可维护性。

设计模式的区分

可以从 它们提供的思路和实现方式来区分:

  1. 创建型模式:创建型模式提供了一种创建对象的最佳方式。 如单例模式,工厂方法模式,抽象工厂模式
  2. 结构型模式:结构型模式关注对象之间的组合。如适配器模式,代理模式,装饰模式
  3. 行为型模式:行为型模式关注对象之间的通信。如迭代器模式,模板方法模式,策略模式

模式的设计理念

🔨 创建型模式(5)
单例模式:某个类只有一个实例,并提供一个全局访问点。

工厂方法模式:通过工厂方法创建对象实例,延迟到子类实现。

抽象工厂模式:创建多个相关对象的工厂接口,定义产品族。

建造者模式:将一个复杂对象的构建过程与其表示分离,使构建过程可复用、可扩展。

原型模式:通过复制原型对象来创建新对象,无需指定其具体类。

🧱 结构型模式(7)
适配器模式:将一个类的接口转换成客户端期望的接口,解决接口不兼容问题。

装饰器模式:动态地为对象添加新的功能,而无需修改其结构。

代理模式:为其他对象提供一种代理以控制对它的访问。

桥接模式:将抽象部分与实现部分分离,使它们可以独立变化。

组合模式:将对象组合成树形结构,以表示“部分-整体”的层次结构。

外观模式:为子系统中的一组接口提供统一的高层接口,简化使用。

享元模式:通过共享大量细粒度对象来减少内存使用和对象创建开销。

⚙️ 行为型模式(11)
模板方法模式:定义算法骨架,将一些步骤延迟到子类实现。

策略模式:定义一系列算法,并使它们可互换。

观察者模式:对象之间建立一对多的依赖关系,被观察者状态改变时通知所有观察者。

命令模式:将请求封装为对象,以支持命令的撤销、排队和记录日志等功能。

职责链模式:多个对象组成一条链,请求沿链传递,直到被某个对象处理。

状态模式:允许对象在内部状态改变时改变其行为。

访问者模式:在不改变对象结构的前提下定义新的操作。

中介者模式:通过一个中介对象封装对象间交互,减少对象之间的耦合。

解释器模式:为语言定义文法并建立解释器,用于解释语言中的句子。

备忘录模式:在不破坏封装性的前提下保存并恢复对象的内部状态。

迭代器模式:提供一种方式顺序访问聚合对象中的元素,而不暴露其内部结构。


从编码到设计模式:系统是如何被组织的?
http://gadoid.io/2025/07/23/从编码到设计模式:系统是如何被组织的/
作者
Codfish
发布于
2025年7月23日
许可协议