文章目录
- [1. 行为型设计模式(Behavioral Pattern)](#1. 行为型设计模式(Behavioral Pattern))
-
- [1.1 责任链模式(Chain of Responsibility)](#1.1 责任链模式(Chain of Responsibility))
-
- [1.1.1 关键组成部分](#1.1.1 关键组成部分)
- [1.1.2 示例1](#1.1.2 示例1)
- [1.1.3 注意点](#1.1.3 注意点)
- [1.1.4 优点](#1.1.4 优点)
- [1.1.5 应用](#1.1.5 应用)
- [1.1.6 示例2](#1.1.6 示例2)
- [1.1.7 练习1](#1.1.7 练习1)
- [1.2 策略模式(Strategy Design Pattern)](#1.2 策略模式(Strategy Design Pattern))
-
- [1.2.1 关键组成部分](#1.2.1 关键组成部分)
- [1.2.2 示例1](#1.2.2 示例1)
- [1.2.3 开闭原则](#1.2.3 开闭原则)
- [1.2.4 策略模式在工业开发中的常见写法(对象注入)](#1.2.4 策略模式在工业开发中的常见写法(对象注入))
- [1.2.5 优点](#1.2.5 优点)
- [1.2.6 应用](#1.2.6 应用)
- [1.2.7 练习1](#1.2.7 练习1)
- [1.2.8 练习2](#1.2.8 练习2)
- [1.3 状态模式(State Pattern)](#1.3 状态模式(State Pattern))
-
- [1.3.1 关键组成部分](#1.3.1 关键组成部分)
- [1.3.2 示例1](#1.3.2 示例1)
- [1.3.3 练习1](#1.3.3 练习1)
- [1.3.4 优点](#1.3.4 优点)
- [1.3.5 应用](#1.3.5 应用)
- [1.3.6 练习2](#1.3.6 练习2)
1. 行为型设计模式(Behavioral Pattern)
行为型模式关注对象之间如何分配职责。
行为型模式会帮助我们安排这些对象之间的职责关系。把某种行为封装到一个对象里或者把请求委托给这个对象处理。
行为型模式也关注 对象之间如何连接和交互。对象之间需要互相通信,但不能依赖得太死,也就是低耦合(loosely coupled)。
具体实现和客户端代码之间应该保持低耦合,这样可以避免硬编码(hard coding)和强依赖(dependencies)。
包含以下几种模式:
-
责任链模式(Chain of Responsibilities)
创建一条由多个对象组成的处理链,请求会沿着这条链依次传递,直到有对象能够处理它。
就像客服系统,如果普通客服处理不了,就交给高级客服。高级客服处理不了,再交给技术专家。
适用场景:审批流程、异常处理、客服工单、权限验证、日志处理。 -
命令模式(Command)
命令模式会把一个请求封装成一个命令对象,然后交给调用者,由调用者再把命令交给合适的对象执行。
例如遥控器,用户按下按钮时,按钮本身不知道怎么开灯,它只执行命令对象。命令对象再去调用电灯的开灯方法。
适用场景:遥控器按钮、撤销和重做、任务队列、菜单操作、事务操作。 -
解释器模式(Interpreter)
解释器模式用于解释语言或表达式。它会定义一套解释规则,然后根据上下文对表达式进行解释和计算。
程序可以根据提前定义好的规则,解释这些表达式的含义。
适用场景:表达式计算、规则引擎、简单脚本语言、SQL 条件解析、数学表达式解析。 -
迭代器模式(Iterator)
迭代器模式用于按顺序访问集合对象中的元素,同时不需要暴露集合内部的具体结构。
这样可以一个一个访问集合里的元素,但不需要知道集合内部是数组、链表,还是其他结构。
适用场景:遍历数组、遍历列表、遍历树结构、遍历集合对象。 -
状态模式(State Pattern)
在状态模式中,一个对象的行为会随着它内部状态的变化而变化。
例如一个订单系统,有以下状态:未支付状态:可以付款,可以取消
已支付状态:可以发货,不可以重复付款
已发货状态:可以确认收货,不可以取消
已完成状态:不能再修改
因此就是:
Order 订单对象
PendingState 未支付状态
PaidState 已支付状态
ShippedState 已发货状态
所以适用场景有:订单状态、游戏角色状态、播放器状态、电梯状态、网络连接状态。
-
策略模式(Strategy Pattern)
策略模式用于在程序运行时改变对象的行为。
它会把不同的算法或行为封装成不同的策略类,然后由上下文对象在运行时选择使用哪一种策略。
例如支付系统可以选择不同的执行方式。PayPal 支付策略
CreditCard 信用卡支付策略
Crypto 加密货币支付策略
电商系统只需要调用
java
paymentStrategy.pay(amount);
至于具体用 PayPal、信用卡还是加密货币,可以在运行时决定。
适用场景:不同支付方式、不同排序算法、不同折扣规则、不同导航路线、不同攻击方式。
-
访问者模式(Visitor Pattern)
访问者模式会把一组操作放到访问者(Visitor)类中,让访问者去作用于不同的元素对象。这样元素对象本身不用频繁修改,新增操作时可以通过增加新的访问者来完成。
例如我们有一组不同的文件:PDF、Word、Excel。
我们要对它们做出不同操作:导出操作、打印操作、压缩操作、统计字数操作。
如果把所有操作都写进每个文件类里,文件类会越来越复杂。
所以访问者模式可以写成 ExportVisitor(导出访问者)、PrintVisitor(打印访问者)、CompressVisitor(压缩访问者),然后让访问者去访问不同文件对象并执行对应操作。
适合场景:对象结构比较稳定、操作经常增加、需要对一组对象执行不同操作。
-
中介者模式(Mediator Pattern)
中介者模式通过一个中介类,帮助多个类之间进行通信。
当很多对象之间需要互相通信时,如果它们彼此直接调用,关系会很复杂。
假设我们现在有4个对象,如果希望它们相互通信,那就要有6种组合,而中介者模式会增加一个中介者(Mediator),所有对象都通过中介者通信。
用户之间不需要一个个直接发送消息,所有消息都交给聊天室服务器转发。
适用场景:聊天室、航空塔台调度、GUI 组件交互、多人游戏房间、系统模块之间通信。
-
观察者模式(Observer Pattern)
当对象之间存在一对多关系时,可以使用观察者模式来管理对象之间的依赖关系。
一个对象状态变化后,需要通知多个对象。
例如:公众号发布新文章,所有订阅者收到通知。
这里:
公众号:Subject,被观察对象
订阅者:Observer,观察者
当公众号更新内容时,所有订阅者都会收到通知。
适用场景是一个对象变化,需要通知多个对象,对象之间是一对多关系,不希望通知逻辑写死在具体类中。
1.1 责任链模式(Chain of Responsibility)
一个请求来了以后,不一定由第一个对象处理。如果第一个对象处理不了,就交给下一个对象。
每个服务只负责一种特定职责。
如上图所示,如果普通客服处理不了,就把请求转给技术客服。技术客服处理不了,再转给主管。
它的核心思想是:客户端发出一个请求,请求会被传给一条处理对象组成的链。链上的每个对象自己判断能不能处理这个请求。如果能处理,就处理;如果不能处理,就传给下一个对象。

每个 Handler 都有两个选择:能处理------处理请求;不能处理------传给下一个 Handler。
最后某个 Handler 处理成功。
责任链模式用于实现软件设计中的低耦合。客户端发出的请求会被传递给一条对象链,由这条链上的对象来处理链中的每个对象会自己判。
1.1.1 关键组成部分

责任链模式一般由 4 个角色组成:
- 处理者接口(Handler)
Handler 定义处理请求的方法接口,有时也会定义设置下一个处理者的方法接口。
代码通常长这样:
java
interface Handler {
void setNext(Handler handler);
void handle(Request request);
}
它规定了每个处理者都必须有两个能力:
1. handle(request):处理请求
2. setNext(handler):设置下一个处理者
- 基础处理者(BaseHandler)
BaseHandler 是一个可选的抽象类,通常作为具体处理者的父类。它会写一些所有具体处理者都需要的通用代码。
比如每个处理者都需要保存下一个处理者:
java
abstract class BaseHandler implements Handler {
protected Handler next;
@Override
public void setNext(Handler handler) {
this.next = handler;
}
@Override
public void handle(Request request) {
if (next != null) {
next.handle(request);
}
}
}
如果当前处理者处理不了请求,就可以调用:
java
next.handle(request);
把请求传给下一个处理者。
-
具体处理者(ConcreteHandler)
ConcreteHandler 是真正处理请求的类。它们按照某种顺序连接在责任链中,每个具体处理者负责实际的请求处理逻辑。
比如审批流程:TeamLeaderHandler:处理小额审批
ManagerHandler:处理中等金额审批
DirectorHandler:处理大额审批
代码逻辑通常是:
java
class ManagerHandler extends BaseHandler {
@Override
public void handle(Request request) {
if (request.getAmount() <= 5000) {
System.out.println("Manager handled the request");
} else {
super.handle(request);
}
}
}
这样当自己处理不了,就会通过前面的 BaseHandler 交给下一个处理者 next。
- 客户端(Client)
Client 是请求的发起者。它会访问责任链中的第一个处理者,并且可以静态或动态地组装这条处理链。
例如:
java
Handler h1 = new TeamLeaderHandler();
Handler h2 = new ManagerHandler();
Handler h3 = new DirectorHandler();
h1.setNext(h2);
h2.setNext(h3);
h1.handle(request);
客户端只需要把请求交给链的第一个对象:
java
h1.handle(request);
至于最后是谁处理的,客户端不用关心。
1.1.2 示例1
我们以责任链模式在用户登录认证和权限校验中的应用为例。

这个过程可以分为三步:
- 检查用户输入的用户名是否存在。
- 检查用户输入的密码是否和该用户名匹配。
- 检查该用户绑定的角色。
这正好符合责任链模式,因为这里一个请求沿着一条处理链依次传递,每个处理者只负责一个检查任务,当前处理者通过后,再交给下一个处理者,当前处理者失败,就直接停止。
如下图所示。

逻辑如下:
LoginService→UserExistHandler,用户名存在才继续→ValidPasswordHandler,密码正确才继续→CheckRoleHandler,权限正确才通过
每一步都有两种结果:
检查通过:调用 handleNext,进入下一步。
检查失败:返回 false,流程结束。
参考的代码如下:
Handler 接口:
java
public interface Handler {
void setNext(Handler handler);
boolean process(User user);
}
BaseHandler 抽象类:
java
public abstract class BaseHandler implements Handler {
private Handler next;
@Override
public void setNext(Handler handler) {
this.next = handler;
}
protected boolean handleNext(User user) {
if (next == null) {
return true;
}
return next.process(user);
}
}
UsernameExistsHandler:
java
public class UsernameExistsHandler extends BaseHandler {
@Override
public boolean process(User user) {
if (!usernameExists(user)) {
System.out.println("Username does not exist");
return false;
}
return handleNext(user);
}
private boolean usernameExists(User user) {
return true;
}
}
PasswordCorrectHandler:
java
public class PasswordCorrectHandler extends BaseHandler {
@Override
public boolean process(User user) {
if (!passwordCorrect(user)) {
System.out.println("Password is incorrect");
return false;
}
return handleNext(user);
}
private boolean passwordCorrect(User user) {
return true;
}
}
RoleExistsHandler:
java
public class RoleExistsHandler extends BaseHandler {
@Override
public boolean process(User user) {
if (!hasRole(user)) {
System.out.println("User does not have requested roles");
return false;
}
return handleNext(user);
}
private boolean hasRole(User user) {
return true;
}
}
LoginService:
java
public class LoginService {
private Handler handlerChain;
public LoginService() {
Handler usernameHandler = new UsernameExistsHandler();
Handler passwordHandler = new PasswordCorrectHandler();
Handler roleHandler = new RoleExistsHandler();
usernameHandler.setNext(passwordHandler);
passwordHandler.setNext(roleHandler);
this.handlerChain = usernameHandler;
}
public boolean login(User user) {
return handlerChain.process(user);
}
}
Authentication 测试类:
java
public class Authentication {
public static void main(String[] args) {
LoginService loginService = new LoginService();
User user = new User("username", "password", "role");
boolean result = loginService.login(user);
if (result) {
System.out.println("Login successful");
} else {
System.out.println("Login failed");
}
}
}
1.1.3 注意点
- 客户端可以决定责任链中各个处理者的顺序,然后把请求发送给链上的第一个处理者。
例如登录验证:
java
usernameHandler.setNext(passwordHandler);
passwordHandler.setNext(roleHandler);
客户端只需要调用第一个处理者。
- 每个 Handler 负责自己的处理逻辑,每个类只负责一个任务,这样代码结构更清楚。
- 每个处理者都应该保存对下一个处理者的引用,这样才能把请求继续传下去。
- 创建责任链时必须仔细,否则可能出现的问题有某个 Handler 没有被连接到责任链里、整个责任链中没有任何 Handler 能处理这个请求。
还有一种情况是所有 Handler 都处理不了请求,最后没有处理结果。所以最好在链尾加一个默认处理逻辑,例如:
java
if (next == null) {
System.out.println("No handler can process this request.");
}
- 责任链模式有利于实现低耦合,但它也有代价。
优点:
- 客户端不需要知道具体由哪个 Handler 处理请求
- 每个 Handler 只负责自己的任务
- 可以灵活调整处理顺序
缺点:
- 可能会产生很多 Handler 类
- 如果很多 Handler 代码相似,会造成重复代码
- 后期维护成本可能增加
所以实际开发中,通常会加一个 BaseHandler 抽象类,把公共代码抽出来,正如前面示例中的:
java
public abstract class BaseHandler implements Handler {
private Handler next;
public void setNext(Handler next) {
this.next = next;
}
protected void handleNext(User user) {
if (next != null) {
next.process(user);
}
}
}
1.1.4 优点
我们单独再整理一遍责任链模式的优点。
- 降低耦合(Reduced Coupling)
责任链模式可以让客户端不用知道到底是哪个对象最终处理了请求。 - 增加灵活性(Added Flexibility)
可以灵活地添加、删除或改变处理者的顺序。
比如我们刚刚的登录流程如果想添加一个验证码检查,只需要新增一个 CaptchaHandler,然后调整链的连接顺序即可。 - 清晰的顺序流程(Clear Sequential Process)
责任链模式可以让处理顺序非常清楚。 - 简化代码(Simplicity)
责任链模式可以避免使用大量复杂的 if else 条件判断。
如果不用责任链,前面的示例代码可能写成:
java
if (checkUsername(user)) {
if (checkPassword(user)) {
if (checkRole(user)) {
System.out.println("Login success");
}
}
}
- 关注点分离(Separation of Concerns)
每个处理者都是相对独立的,不需要知道其他处理者内部是怎么实现的。
1.1.5 应用
当程序需要处理多种不同类型的请求,而且这些请求的类型和处理顺序在一开始并不完全确定时,可以使用责任链模式。
当多个处理步骤必须按照特定顺序执行时,可以使用责任链模式。
例如我们刚刚的登陆验证示例。
当处理者集合和处理顺序可能在程序运行时发生变化时,可以使用责任链模式。
例如我们刚刚的登陆验证示例可以再添加验证码验证和二次验证,责任链模式可以比较方便地添加、删除、调整这些 Handler。
1.1.6 示例2
我们再通过 ATM 取款机的出钞问题来使用责任链模式。
在 ATM 取款机中,用户输入想要取出的金额,机器会按照已有面额的纸币进行出钞,比如50、20、$10。
如果用户输入的金额不是 10 的倍数,就报错。
这个问题当然可以直接写在一个方法里解决,但这样代码复杂度会增加,而且耦合度会变高。

示例代码:
Handler 接口:
java
public interface Handler {
public Handler setNextHandler(Handler handler);
public void dispense(double amount);
}
BaseDispenser:
java
abstract class BaseDispenser implements Handler {
protected Handler nextHandler;
protected int denomination;
public BaseDispenser(int denomination) {
this.denomination = denomination;
}
@Override
public Handler setNextHandler(Handler handler) {
this.nextHandler = handler;
return handler;
}
@Override
public void dispense(double amount) {
if (amount >= denomination) {
int numberOfNotes = (int) (amount / denomination);
double remainingAmount = amount % denomination;
System.out.println("Dispense " + numberOfNotes + " note(s) of $" + denomination);
if (remainingAmount > 0) {
handleNext(remainingAmount);
}
} else {
handleNext(amount);
}
}
protected void handleNext(double amount) {
if (nextHandler != null) {
nextHandler.dispense(amount);
} else {
System.out.println("Cannot dispense remaining amount: $" + amount);
}
}
}
具体 Handler:
java
class Dollar50Dispenser extends BaseDispenser {
public Dollar50Dispenser() {
super(50);
}
}
class Dollar20Dispenser extends BaseDispenser {
public Dollar20Dispenser() {
super(20);
}
}
class Dollar10Dispenser extends BaseDispenser {
public Dollar10Dispenser() {
super(10);
}
}
所以对应的客户端:
java
class ATMDispenser {
private Handler chain;
public ATMDispenser() {
Handler dollar50 = new Dollar50Dispenser();
Handler dollar20 = new Dollar20Dispenser();
Handler dollar10 = new Dollar10Dispenser();
dollar50.setNextHandler(dollar20);
dollar20.setNextHandler(dollar10);
this.chain = dollar50;
}
public void dispense(double amount) {
if (amount <= 0) {
System.out.println("Amount must be greater than 0.");
return;
}
if (amount % 10 != 0) {
System.out.println("Error: Amount must be a multiple of 10.");
return;
}
System.out.println("ATM starts dispensing $" + amount);
chain.dispense(amount);
}
}
测试类:
java
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
ATMDispenser atm = new ATMDispenser();
System.out.print("Enter amount to withdraw: ");
double amount = scanner.nextDouble();
atm.dispense(amount);
scanner.close();
}
}
1.1.7 练习1
用责任链模式 设计一个 Web 服务器处理 HTTP 请求的流程。
具体场景如下:一个 Web 服务器收到 HTTP 请求后,通常不能直接处理业务逻辑,需要先经过一系列检查和处理步骤。
HTTP Request→Authentication Check(认证检查)→Logging(日志记录)→Data Validation(数据校验)→Actual Request Handling(实际请求处理)
示例代码如下:
java
class HttpRequest {
private String username;
private String token;
private String method;
private String path;
private String body;
public HttpRequest(String username, String token, String method, String path, String body) {
this.username = username;
this.token = token;
this.method = method;
this.path = path;
this.body = body;
}
public String getUsername() {
return username;
}
public String getToken() {
return token;
}
public String getMethod() {
return method;
}
public String getPath() {
return path;
}
public String getBody() {
return body;
}
}
interface Handler {
Handler setNext(Handler handler);
boolean handle(HttpRequest request);
}
abstract class BaseHandler implements Handler {
private Handler next;
@Override
public Handler setNext(Handler handler) {
this.next = handler;
return handler;
}
protected boolean handleNext(HttpRequest request) {
if (next == null) {
return true;
}
return next.handle(request);
}
}
class AuthenticationHandler extends BaseHandler {
@Override
public boolean handle(HttpRequest request) {
System.out.println("Step 1: Checking authentication");
if (request.getToken() == null || !request.getToken().equals("validToken")) {
System.out.println("Authentication failed");
return false;
}
System.out.println("Authentication passed");
return handleNext(request);
}
}
class LoggingHandler extends BaseHandler {
@Override
public boolean handle(HttpRequest request) {
System.out.println("Step 2: Logging request");
System.out.println("User: " + request.getUsername());
System.out.println("Method: " + request.getMethod());
System.out.println("Path: " + request.getPath());
return handleNext(request);
}
}
class ValidationHandler extends BaseHandler {
@Override
public boolean handle(HttpRequest request) {
System.out.println("Step 3: Validating request data");
if (request.getPath() == null || request.getPath().isEmpty()) {
System.out.println("Validation failed: path is empty");
return false;
}
if (request.getMethod() == null || request.getMethod().isEmpty()) {
System.out.println("Validation failed: method is empty");
return false;
}
if (request.getMethod().equals("POST")) {
if (request.getBody() == null || request.getBody().isEmpty()) {
System.out.println("Validation failed: POST request body is empty");
return false;
}
}
System.out.println("Validation passed");
return handleNext(request);
}
}
class RequestProcessingHandler extends BaseHandler {
@Override
public boolean handle(HttpRequest request) {
System.out.println("Step 4: Handling actual request");
System.out.println("Processing " + request.getMethod() + " request for " + request.getPath());
if (request.getPath().equals("/createOrder")) {
System.out.println("Order created successfully");
} else if (request.getPath().equals("/getUserInfo")) {
System.out.println("User information returned successfully");
} else {
System.out.println("Request path handled by default processor");
}
return handleNext(request);
}
}
class WebServer {
private Handler handlerChain;
public WebServer() {
Handler authenticationHandler = new AuthenticationHandler();
Handler loggingHandler = new LoggingHandler();
Handler validationHandler = new ValidationHandler();
Handler requestProcessingHandler = new RequestProcessingHandler();
authenticationHandler
.setNext(loggingHandler)
.setNext(validationHandler)
.setNext(requestProcessingHandler);
this.handlerChain = authenticationHandler;
}
public void handleRequest(HttpRequest request) {
boolean result = handlerChain.handle(request);
if (result) {
System.out.println("Request completed successfully");
} else {
System.out.println("Request failed");
}
}
}
public class Main {
public static void main(String[] args) {
WebServer server = new WebServer();
HttpRequest request = new HttpRequest(
"Alice",
"validToken",
"POST",
"/createOrder",
"productId=1001&quantity=2"
);
server.handleRequest(request);
}
}
1.2 策略模式(Strategy Design Pattern)
现在我们有一个程序,需要通过多种消息服务发送消息:
java
public class MessageService {
public void send(String type, String msg) {
if (type.equals("WeChat")) {
System.out.println("Sending message using WeChat");
// WechatClient client = getClient();
// ........
// System.out.println("Failed sending message");
} else if (type.equals("WhatsApp")) {
System.out.println("Sending message using WhatsApp");
// Twilio.init(ACCOUNT_SID, AUTH_TOKEN);
// ........
// .create();
}
}
}
现在只有 WeChat 和 WhatsApp,看起来还可以,但是如果消息平台越来越多,那么现在代码就会变成很多 if else。消息平台越多,代码越难维护。
这里同样违反开闭原则,也就是说,新增功能时,最好通过新增类完成,尽量少修改原来的核心代码。
这里代码每增加一个消息平台,都必须修改 MessageService 里的 send() 方法。
这里MessageService 和具体平台耦合太强,MessageService承担了太多职责,这些逻辑混在一起,后期不好改。
如果调用时写错:
java
send("Wechat", msg);
send("weChat", msg);
send("Whats App", msg);
程序就找不到对应逻辑。
这里所有发送逻辑都写在一个 send() 方法里,测试时很难单独测试 WeChat 发送、WhatsApp 发送、Email 发送。
因此这个场景很适合用策略模式去解决。
它的核心思想是:把不同的行为或算法分别封装成独立的类,然后在程序运行时选择使用哪一个。
策略模式用于处理程序运行时对象行为的变化。也就是说,同一个功能可以有多种实现方式,程序可以根据情况选择其中一种。
例如这里发送消息可以是微信也可以是 WhatsApp。
系统中会有多个策略类,同时有一个上下文对象负责在运行时使用某个策略。
把每一种消息发送方式放到单独的类中,这样可以实现单一职责原则。
这些策略类可以很容易互相替换,而且不需要修改原来的核心代码。
1.2.1 关键组成部分

策略模式通常由 4 个部分组成:
- 上下文类(Context)
Context 保存一个具体策略对象,并且通过策略接口调用它。
在消息发送例子里,MessageService 就是 Context。
Context 不需要知道具体用的是微信、WhatsApp 还是 Email,它只通过统一接口调用。 - 策略接口(Strategy interface)
Strategy 是所有策略类共同遵守的接口。
不管是微信发送、WhatsApp 发送、邮件发送,都要实现这个接口。
比如发送消息的策略接口可以写成:
java
interface MessageStrategy {
void send(String msg);
}
- 具体策略类(Concrete strategy)
具体策略类负责实现具体行为。每个具体策略类只负责一种发送方式。
例如:
java
class WeChatStrategy implements MessageStrategy {
public void send(String msg) {
System.out.println("Sending message using WeChat: " + msg);
}
}
java
class WhatsAppStrategy implements MessageStrategy {
public void send(String msg) {
System.out.println("Sending message using WhatsApp: " + msg);
}
}
- 客户端(Client)
客户端负责创建具体策略对象,并把它传给 Context。
java
MessageService service = new MessageService();
service.setStrategy(new WeChatStrategy());
service.send("Hello");
service.setStrategy(new WhatsAppStrategy());
service.send("Hello again");
这里客户端决定当前使用哪一种策略。
1.2.2 示例1
在电商系统中,用户下单后需要付款。不同用户可能选择不同支付方式,例如信用卡、借记卡、PayPal。
如果把所有支付方式都写在一个类里,代码会变得复杂,也更难维护。
使用策略模式时,可以为所有支付方式定义一个共同接口。每种支付方式都作为一个单独的类实现这个接口,也就是具体策略类。
然后,Context 类,比如购物车类或支付处理类,就可以通过这个统一接口,把支付任务交给用户选择的支付方式去执行。
策略接口:
java
interface PaymentStrategy {
void pay(double amount);
}
具体策略:
java
class CreditCardPayment implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("Paying $" + amount + " by credit card");
}
}
class PayPalPayment implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("Paying $" + amount + " by PayPal");
}
}
上下文类:
java
class ShoppingCart {
private PaymentStrategy paymentStrategy;
public void setPaymentStrategy(PaymentStrategy paymentStrategy) {
this.paymentStrategy = paymentStrategy;
}
public void checkout(double amount) {
paymentStrategy.pay(amount);
}
}
使用方式:
java
public class ECommerce {
public static void main(String[] args) {
ShoppingCart cart = new ShoppingCart();
cart.setPaymentStrategy(new PayPalPaymentStrategy());
cart.checkout(100.0);
}
}
1.2.3 开闭原则
我们现在通过了示例更能理解策略模式和开闭原则的关系。
开闭原则是当系统需要增加新功能时,尽量通过新增代码实现,尽量不要修改原来的核心代码。
因此如果我们现在想增加新的支付方式,只需要新增一个类,实现 PaymentStrategy 接口即可。
例如:
java
class CryptoPaymentStrategy implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("Processing crypto payment: $" + amount);
}
}
系统对修改是关闭的。新增支付方式时,不需要修改原来已有的支付策略类,也不需要在 ShoppingCart 里面增加大量 if else。
只需要在使用时传入新的策略对象:
java
ShoppingCart cart = new ShoppingCart();
cart.setPaymentStrategy(new CryptoPaymentStrategy());
cart.checkout(100.0);
在软件开发中,开闭原则经常可以通过策略模式来实现。
因为策略模式把不同算法或行为拆成独立策略类。
换句话说,策略模式可以很好地体现开闭原则。
1.2.4 策略模式在工业开发中的常见写法(对象注入)
在真实项目中,策略模式经常通过"把具体策略对象传进去"的方式实现。
如下面的数据到处接口为例。
java
public interface DataExporter {
void export(Data data);
}
不管你导出成 PDF、CSV、Excel,都要实现这个接口里的:
java
export(Data data)
这里的 DataExporter 就是策略模式中的策略接口。
java
public class PdfExporter implements DataExporter {
public void export(Data data) {
/* PDF Logic */
}
}
public class CsvExporter implements DataExporter {
public void export(Data data) {
/* CSV Logic */
}
}
它们都实现了 DataExporter 接口,所以它们都可以被当成一种导出策略。也就是策略模式中的具体策略类。
java
public class ExportService {
private final DataExporter exporter;
public ExportService(DataExporter exporter) {
this.exporter = exporter;
}
public void executeExport(Data data) {
exporter.export(data);
}
}
这是真正执行导出任务的服务类。
因此具体使用哪个导出器,是在程序运行时传进来的。
比如:
java
ExportService service = new ExportService(new PdfExporter());
表示使用 PDF 导出。
如果想换成 CSV:
java
ExportService service = new ExportService(new CsvExporter());
ExportService 本身不用改,只要传入不同的对象即可。
这就是 对象注入 Object Injection。
1.2.5 优点
策略模式的优点如下:
- 灵活性(Flexibility)
我们可以在程序运行时,通过替换 Context 里面的策略对象,改变程序行为。
同一个 ShoppingCart,可以在运行时切换不同支付方式,如下所示。
java
cart.setPaymentStrategy(new PayPalPaymentStrategy());
cart.checkout(100.0);
cart.setPaymentStrategy(new CreditCardPaymentStrategy());
cart.checkout(200.0);
- 封装性(Encapsulation)
相关算法或行为会被封装到不同的策略类中,避免复杂逻辑堆在其他类里。 - 复用性(Reusability)
策略类和上下文类之间耦合度低,所以策略类可以在不同地方复用。
例如 PayPalPaymentStrategy 不只可以用于 ShoppingCart,也可以用于 SubscriptionService (订阅服务)等,只要它们需要支付功能,就可以复用这个策略类。 - 可维护性(Maintainability)
策略模式可以让代码更容易维护。
修改某一个策略时,不会影响其他策略。
如果以后新增一种支付方式,只需要新增一个策略类,而不需要修改 ShoppingCart 的核心代码。 - 测试方便(Testing)
每个策略类都可以单独测试,不需要依赖上下文类和其他策略类。
1.2.6 应用
当一个对象的行为或算法需要在程序运行时动态改变时,策略模式特别适合使用。
也就是说,如果同一个功能有多种不同实现方式,并且具体用哪一种要到运行时才知道,就可以使用策略模式。
比如电商支付系统或者信息发送系统,这种同一个任务有多种执行方式,运行时才决定具体用哪一种。
1.2.7 练习1
我们现在尝试用策略模式解决一个快递费用计算问题。
快递费用的计算方式取决于寄送物品的类型,有包裹类和文件类,不同物品使用不同的费用计算策略。
对于包裹类,费用需要用两个算法分别计算------重量或尺寸。最后实际费用取两个结果中较高的那个。
对于文件时,费用计算方法只有一种。
现在我们尝试用策略模式来解决这个快递费用计算问题。
示例代码如下:
java
interface ShippingCostStrategy {
double calculateCost(CourierItem item);
}
class CourierItem {
private String itemType;
private double weight;
private double length;
private double width;
private double height;
public CourierItem(String itemType, double weight, double length, double width, double height) {
this.itemType = itemType;
this.weight = weight;
this.length = length;
this.width = width;
this.height = height;
}
public String getItemType() {
return itemType;
}
public double getWeight() {
return weight;
}
public double getLength() {
return length;
}
public double getWidth() {
return width;
}
public double getHeight() {
return height;
}
}
class ParcelCostStrategy implements ShippingCostStrategy {
@Override
public double calculateCost(CourierItem item) {
double costByWeight = calculateByWeight(item);
double costByDimension = calculateByDimension(item);
return Math.max(costByWeight, costByDimension);
}
private double calculateByWeight(CourierItem item) {
// Algorithm A: based on weight
return item.getWeight() * 5.0;
}
private double calculateByDimension(CourierItem item) {
// Algorithm B: based on dimension
double volume = item.getLength() * item.getWidth() * item.getHeight();
return volume * 0.01;
}
}
class DocumentCostStrategy implements ShippingCostStrategy {
@Override
public double calculateCost(CourierItem item) {
// Algorithm C: document cost calculation
return 10.0 + item.getWeight() * 2.0;
}
}
class CourierService {
private ShippingCostStrategy costStrategy;
public void setCostStrategy(ShippingCostStrategy costStrategy) {
this.costStrategy = costStrategy;
}
public double calculateShippingCost(CourierItem item) {
if (costStrategy == null) {
throw new RuntimeException("No shipping cost strategy selected.");
}
return costStrategy.calculateCost(item);
}
}
public class Main {
public static void main(String[] args) {
CourierService courierService = new CourierService();
CourierItem parcel = new CourierItem(
"Parcel",
8.0,
40.0,
30.0,
20.0
);
courierService.setCostStrategy(new ParcelCostStrategy());
double parcelCost = courierService.calculateShippingCost(parcel);
System.out.println("Item type: " + parcel.getItemType());
System.out.println("Parcel shipping cost: $" + parcelCost);
System.out.println();
CourierItem document = new CourierItem(
"Document",
1.5,
0.0,
0.0,
0.0
);
courierService.setCostStrategy(new DocumentCostStrategy());
double documentCost = courierService.calculateShippingCost(document);
System.out.println("Item type: " + document.getItemType());
System.out.println("Document shipping cost: $" + documentCost);
}
}
1.2.8 练习2
我们先用策略模式设计一个灵活的排序系统。
这个应用需要对数据列表进行不同方式的排序,例如按数字大小排序、按字母顺序排序、按自定义规则排序。
而且排序方式需要能够在程序运行时灵活切换,同时不修改原来的代码结构。
例如这次按数字排序,下次按字母排序,再下次按自定义规则排序。
这正好适合使用策略模式。
因为我们可以使用策略模式定义一组算法,把每一种算法都封装成独立的类,并且让这些算法可以互相替换。这样算法的变化不会影响使用它的客户端代码。
这里体现了其优点中的:
- 灵活性:客户端可以在程序运行时选择或更换排序算法,而且不用修改客户端代码。
- 可维护性:新增排序策略时,可以直接增加一个新的策略类,不需要修改已有类,这符合开闭原则。
- 复用性:不同排序策略被封装在自己的类中,所以可以在不同场景中复用。
其类图如下图所示。

示例代码如下:
java
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
// Strategy interface
interface SortingStrategy {
void sort(List<String> data);
}
// Concrete Strategy 1: Numerical sorting
class Numerical implements SortingStrategy {
@Override
public void sort(List<String> data) {
Collections.sort(data, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return Integer.parseInt(a) - Integer.parseInt(b);
}
});
System.out.println("Numerical sorting result: " + data);
}
}
// Concrete Strategy 2: Alphanumeric sorting
class Alphanumeric implements SortingStrategy {
@Override
public void sort(List<String> data) {
Collections.sort(data);
System.out.println("Alphanumeric sorting result: " + data);
}
}
// Concrete Strategy 3: Custom sorting
class CustomSort implements SortingStrategy {
@Override
public void sort(List<String> data) {
Collections.sort(data, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.length() - b.length();
}
});
System.out.println("Custom sorting result by string length: " + data);
}
}
// Context class
class Context {
private SortingStrategy sortingStrategy;
private List<String> data;
public Context(List<String> data) {
this.data = data;
}
public void setStrategy(SortingStrategy sortingStrategy) {
this.sortingStrategy = sortingStrategy;
}
public void setData(List<String> data) {
this.data = data;
}
public void performSort() {
if (sortingStrategy == null) {
System.out.println("No sorting strategy selected.");
return;
}
sortingStrategy.sort(data);
}
}
// Client
public class Main {
public static void main(String[] args) {
List<String> numberData = new ArrayList<String>();
numberData.add("30");
numberData.add("5");
numberData.add("100");
numberData.add("12");
Context context = new Context(numberData);
context.setStrategy(new Numerical());
context.performSort();
System.out.println();
List<String> wordData = new ArrayList<String>();
wordData.add("Banana");
wordData.add("Apple");
wordData.add("Orange");
wordData.add("Grape");
context.setData(wordData);
context.setStrategy(new Alphanumeric());
context.performSort();
System.out.println();
List<String> customData = new ArrayList<String>();
customData.add("Watermelon");
customData.add("Kiwi");
customData.add("Apple");
customData.add("Pear");
context.setData(customData);
context.setStrategy(new CustomSort());
context.performSort();
}
}
1.3 状态模式(State Pattern)
状态模式是一种行为型设计模式,用来管理对象之间的行为、关系和职责。
例如一个订单对象,在不同状态下能做的事情不同:
未支付状态:可以付款、可以取消
已支付状态:可以发货
已发货状态:可以确认收货
已完成状态:不能再修改
当一个对象的内部状态发生变化时,它的行为也会随之改变。看起来就像这个对象变成了另一个类的对象一样。
状态模式会把不同状态下的行为封装到不同的状态类中。
1.3.1 关键组成部分
下图展现了状态模式的结构。

状态模式通常由 3 个核心部分组成:
- 上下文类(Context)
Context 会保存一个具体状态对象的引用,并把和状态相关的工作交给这个状态对象完成。Context 通过 State 接口和状态对象通信,同时提供一个 setter 方法,用来切换到新的状态对象。
Context 是当前业务对象,比如播放器、订单、电梯。
它保存当前状态并提供切换状态的代码。
java
private State state;
public void setState(State state) {
this.state = state;
}
当客户端调用某个操作时,Context 会把任务交给当前状态对象:
java
public void request() {
state.handle();
}
所以 Context 负责保存状态和切换状态,具体行为由状态类决定。
- 状态接口(State)
State 接口为所有具体状态定义一个共同接口,并封装某个特定状态下的相关行为。
所有状态类都要实现同一个接口。
这样 Context 不需要关心当前具体是哪一个状态,只要调用:
java
state.handle();
- 具体状态类(Concrete States)
具体状态类会为特定状态下的方法提供自己的实现。如果多个状态之间有重复代码,可以提供中间抽象类,把公共行为封装起来。
例如播放器的不同状态有不同的行为:
java
class StoppedState implements State {
public void handle() {
System.out.println("Start playing");
}
}
class PlayingState implements State {
public void handle() {
System.out.println("Pause playing");
}
}
注意:Context 和具体状态类都可以设置 Context 的下一个状态。状态切换通过替换 Context 中保存的状态对象来完成。
也就是说,状态切换可以由 Context 控制,也可以由具体状态类自己控制。
例如:
java
context.setState(new PlayingState());
这就表示当前对象从原来的状态切换到了 PlayingState。
在状态类里也可以这样切换:
java
class StoppedState implements State {
public void handle(Player player) {
System.out.println("Start playing");
player.setState(new PlayingState());
}
}
1.3.2 示例1
我们现在通过状态模式实现刚刚说的 MP3 播放器示例。
我们首先有一个状态接口:
java
interface State {
public void pressPlay(MP3PlayerContext context);
}
然后是具体的状态类:
java
public class StandbyState implements State {
public void pressPlay(MP3PlayerContext context) {
System.out.println("Playing...");
context.setState(new PlayingState());
}
}
public class PlayingState implements State {
public void pressPlay(MP3PlayerContext context) {
System.out.println("I am already in PLAYING state");
}
}
上下文类:
java
public class MP3PlayerContext {
private State state;
public MP3PlayerContext(State state) {
this.state = state;
}
public void play() {
state.pressPlay(this);
}
public void setState(State state) {
this.state = state;
}
public State getState() {
return state;
}
}
测试:
java
public class PlayMusic {
public static void main(String[] args) {
MP3PlayerContext player = new MP3PlayerContext(new StandbyState());
player.play();
player.play();
}
}
1.3.3 练习1
我们现在尝试练习一遍状态模式,用状态模式实现一个工作流系统。
工作流通常包含多个按顺序发生的流程,每个流程都有自己的规则、职责,以及进入下一个流程的状态转换规则。
例如文档审批系统,一个文档会经历多个状态:
草稿状态、审核状态、批准状态、驳回状态。
一个文档在不同状态下可以执行的操作不同。
草稿状态
可以提交审核
审核状态
可以批准,也可以驳回
批准状态
文档流程结束
驳回状态
可以退回修改
参考代码如下:
状态接口:
java
public interface State {
void review(Document doc);
void approve(Document doc);
void disapprove(Document doc);
}
具体状态类:
java
class Draft implements State {
@Override
public void review(Document doc) {
System.out.println("Start to review the document");
doc.setState(new Review());
}
@Override
public void approve(Document doc) {
System.out.println("The document is not being reviewed yet, please review the document before approve");
}
@Override
public void disapprove(Document doc) {
System.out.println("The document is not being reviewed yet, please review the document before disapprove");
}
}
class Review implements State {
@Override
public void review(Document doc) {
System.out.println("The document is in the middle of reviewing.");
}
@Override
public void approve(Document doc) {
System.out.println("The document is approved.");
doc.setState(new Approved());
}
@Override
public void disapprove(Document doc) {
System.out.println("The document is disapproved.");
doc.setState(new Disapproved());
}
}
class Approved implements State {
@Override
public void review(Document doc) {
System.out.println("The document is already approved.");
}
@Override
public void approve(Document doc) {
System.out.println("The document is already approved.");
}
@Override
public void disapprove(Document doc) {
System.out.println("The document is already approved, it cannot be disapproved.");
}
}
class Disapproved implements State {
@Override
public void review(Document doc) {
System.out.println("The document is already disapproved.");
}
@Override
public void approve(Document doc) {
System.out.println("You have approved the previously disapproved document.");
doc.setState(new Approved());
}
@Override
public void disapprove(Document doc) {
System.out.println("The document is already disapproved.");
}
}
上下文类:
java
class Document {
private State state;
public Document() {
state = new Draft();
}
public void setState(State state) {
this.state = state;
}
public void review() {
state.review(this);
}
public void approve() {
state.approve(this);
}
public void disapprove() {
state.disapprove(this);
}
}
测试代码如下:
java
public class Workflow {
public static void main(String[] args) {
Document document = new Document();
// Edit the document here
document.review();
// Review the document here
document.approve();
document.approve();
document.review();
document.disapprove();
}
}
1.3.4 优点
状态模式的优点如下:
- 让控制流程更有组织(Organized Control Flow)
如果一个系统经常在多个状态之间复杂切换,状态模式可以让流程更清晰。 - 把状态相关行为集中到对应状态类中(Localize State Specific Behavior)
所有和状态有关的操作都会被写进单独的状态类中。这样可以把某个状态下的行为集中管理。 - 状态转换更明确(State Transitions Explicit)
状态之间如何转换会变得更清楚、更可预测,代码也更容易阅读和理解。 - 扩展性更好(Extensibility)
如果以后要增加新状态,只需要新增一个状态类。不用大幅修改原来的类。 - 简化代码(Simplify Code)
状态模式可以减少大量复杂的条件判断,让代码更干净。
1.3.5 应用
当一个对象会根据当前状态表现出不同的行为,而且状态数量很多、状态相关代码经常变化时,可以使用状态模式。
例如前面的文档审批系统。
当一个类里有大量条件判断,并且这些条件判断都是根据对象当前字段值来决定行为时,可以使用状态模式。因为状态模式可以把这些逻辑拆到不同状态类里。
当不同状态之间存在很多重复代码,或者状态转换逻辑很复杂时,也适合使用状态模式。
1.3.6 练习2
现在我们尝试用状态模式设计一个交通灯系统。
交通灯通常有三个状态红灯、绿灯、黄灯。
在红灯状态下,经过一段时间后,交通灯会切换到绿灯。
在绿灯状态下,经过一段时间后,或者检测到紧急情况时,交通灯会切换到黄灯。
在黄灯状态下,经过短暂时间后,交通灯会切换到红灯。
参考代码如下:
java
interface TrafficLightState {
void changeLight(TrafficLight trafficLight);
void showState();
}
class TrafficLight {
private TrafficLightState state;
private boolean emergencyCondition;
public TrafficLight() {
this.state = new RedState();
this.emergencyCondition = false;
}
public void setState(TrafficLightState state) {
this.state = state;
}
public TrafficLightState getState() {
return state;
}
public void setEmergencyCondition(boolean emergencyCondition) {
this.emergencyCondition = emergencyCondition;
}
public boolean hasEmergencyCondition() {
return emergencyCondition;
}
public void showCurrentState() {
state.showState();
}
public void changeLight() {
state.changeLight(this);
}
}
class RedState implements TrafficLightState {
@Override
public void showState() {
System.out.println("Current light: RED");
System.out.println("Vehicles must stop.");
}
@Override
public void changeLight(TrafficLight trafficLight) {
System.out.println("Red light finished. Changing to GREEN.");
trafficLight.setState(new GreenState());
}
}
class GreenState implements TrafficLightState {
@Override
public void showState() {
System.out.println("Current light: GREEN");
System.out.println("Vehicles can go.");
}
@Override
public void changeLight(TrafficLight trafficLight) {
if (trafficLight.hasEmergencyCondition()) {
System.out.println("Emergency condition detected. Changing to YELLOW.");
} else {
System.out.println("Green light finished. Changing to YELLOW.");
}
trafficLight.setEmergencyCondition(false);
trafficLight.setState(new YellowState());
}
}
class YellowState implements TrafficLightState {
@Override
public void showState() {
System.out.println("Current light: YELLOW");
System.out.println("Vehicles should prepare to stop.");
}
@Override
public void changeLight(TrafficLight trafficLight) {
System.out.println("Yellow light finished. Changing to RED.");
trafficLight.setState(new RedState());
}
}
public class Main {
public static void main(String[] args) {
TrafficLight trafficLight = new TrafficLight();
trafficLight.showCurrentState();
trafficLight.changeLight();
System.out.println();
trafficLight.showCurrentState();
trafficLight.changeLight();
System.out.println();
trafficLight.showCurrentState();
trafficLight.changeLight();
System.out.println();
trafficLight.showCurrentState();
System.out.println();
System.out.println("Testing emergency condition:");
trafficLight.changeLight();
System.out.println();
trafficLight.showCurrentState();
trafficLight.setEmergencyCondition(true);
trafficLight.changeLight();
System.out.println();
trafficLight.showCurrentState();
}
}