本文最后更新于 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)
- 职责:描述被处理事务的状态和行为规则
- 特征:封装业务逻辑,维护对象状态
- 示例:
House、Room 等领域对象
3. 过程处理者 (Service/Handler)
- 职责:处理事务在特定阶段的逻辑,协调对象间交互
- 特征:无状态,专注于处理逻辑
- 示例:
HousekeepingService、MealPreparationService
语义关系重新定义
基于面向对象设计原则,类与类之间主要有以下语义关系:
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.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(); } }
|
分离特征:
- 定义分工:接口定义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
| class Order { private List<Item> items; private Customer customer; public void addItem(Item item) { } public double calculateTotal() { } public void saveToDatabase() { } public void sendConfirmationEmail() { } }
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
| 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; } return 0; } }
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
| 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
| interface Worker { void work(); void eat(); void sleep(); }
class Robot implements Worker { public void work() { } public void eat() { } public void sleep() { } }
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
| class EmailService { public void sendEmail(String message) { } }
class OrderProcessor { private EmailService emailService = new EmailService(); public void processOrder(Order order) { emailService.sendEmail("Order processed"); } }
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"); } }
|
设计模式
所谓设计模式,就是在上述原则和解耦思想下提出的依据业务来实现的对象设计处理框架。设计模式不是凭空产生的编程技巧,而是在长期的软件开发实践中,针对特定问题场景总结出的成熟解决方案。每一个设计模式都体现了特定的解耦策略,帮助我们在复杂的业务逻辑中保持代码的灵活性和可维护性。
设计模式的区分
可以从 它们提供的思路和实现方式来区分:
- 创建型模式:创建型模式提供了一种创建对象的最佳方式。 如单例模式,工厂方法模式,抽象工厂模式
- 结构型模式:结构型模式关注对象之间的组合。如适配器模式,代理模式,装饰模式
- 行为型模式:行为型模式关注对象之间的通信。如迭代器模式,模板方法模式,策略模式
模式的设计理念
🔨 创建型模式(5)
单例模式:某个类只有一个实例,并提供一个全局访问点。
工厂方法模式:通过工厂方法创建对象实例,延迟到子类实现。
抽象工厂模式:创建多个相关对象的工厂接口,定义产品族。
建造者模式:将一个复杂对象的构建过程与其表示分离,使构建过程可复用、可扩展。
原型模式:通过复制原型对象来创建新对象,无需指定其具体类。
🧱 结构型模式(7)
适配器模式:将一个类的接口转换成客户端期望的接口,解决接口不兼容问题。
装饰器模式:动态地为对象添加新的功能,而无需修改其结构。
代理模式:为其他对象提供一种代理以控制对它的访问。
桥接模式:将抽象部分与实现部分分离,使它们可以独立变化。
组合模式:将对象组合成树形结构,以表示“部分-整体”的层次结构。
外观模式:为子系统中的一组接口提供统一的高层接口,简化使用。
享元模式:通过共享大量细粒度对象来减少内存使用和对象创建开销。
⚙️ 行为型模式(11)
模板方法模式:定义算法骨架,将一些步骤延迟到子类实现。
策略模式:定义一系列算法,并使它们可互换。
观察者模式:对象之间建立一对多的依赖关系,被观察者状态改变时通知所有观察者。
命令模式:将请求封装为对象,以支持命令的撤销、排队和记录日志等功能。
职责链模式:多个对象组成一条链,请求沿链传递,直到被某个对象处理。
状态模式:允许对象在内部状态改变时改变其行为。
访问者模式:在不改变对象结构的前提下定义新的操作。
中介者模式:通过一个中介对象封装对象间交互,减少对象之间的耦合。
解释器模式:为语言定义文法并建立解释器,用于解释语言中的句子。
备忘录模式:在不破坏封装性的前提下保存并恢复对象的内部状态。
迭代器模式:提供一种方式顺序访问聚合对象中的元素,而不暴露其内部结构。