CPT304 SoftwareEngineeringII 软件工程 2 Pt.4 设计模式(下)

文章目录

  • [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)。

包含以下几种模式:

  1. 责任链模式(Chain of Responsibilities)
    创建一条由多个对象组成的处理链,请求会沿着这条链依次传递,直到有对象能够处理它。
    就像客服系统,如果普通客服处理不了,就交给高级客服。高级客服处理不了,再交给技术专家。
    适用场景:审批流程、异常处理、客服工单、权限验证、日志处理。

  2. 命令模式(Command)
    命令模式会把一个请求封装成一个命令对象,然后交给调用者,由调用者再把命令交给合适的对象执行。
    例如遥控器,用户按下按钮时,按钮本身不知道怎么开灯,它只执行命令对象。命令对象再去调用电灯的开灯方法。
    适用场景:遥控器按钮、撤销和重做、任务队列、菜单操作、事务操作。

  3. 解释器模式(Interpreter)
    解释器模式用于解释语言或表达式。它会定义一套解释规则,然后根据上下文对表达式进行解释和计算。
    程序可以根据提前定义好的规则,解释这些表达式的含义。
    适用场景:表达式计算、规则引擎、简单脚本语言、SQL 条件解析、数学表达式解析。

  4. 迭代器模式(Iterator)
    迭代器模式用于按顺序访问集合对象中的元素,同时不需要暴露集合内部的具体结构。
    这样可以一个一个访问集合里的元素,但不需要知道集合内部是数组、链表,还是其他结构。
    适用场景:遍历数组、遍历列表、遍历树结构、遍历集合对象。

  5. 状态模式(State Pattern)
    在状态模式中,一个对象的行为会随着它内部状态的变化而变化。
    例如一个订单系统,有以下状态:

    未支付状态:可以付款,可以取消
    已支付状态:可以发货,不可以重复付款
    已发货状态:可以确认收货,不可以取消
    已完成状态:不能再修改

因此就是:

复制代码
Order 订单对象
PendingState 未支付状态
PaidState 已支付状态
ShippedState 已发货状态

所以适用场景有:订单状态、游戏角色状态、播放器状态、电梯状态、网络连接状态。

  1. 策略模式(Strategy Pattern)
    策略模式用于在程序运行时改变对象的行为。
    它会把不同的算法或行为封装成不同的策略类,然后由上下文对象在运行时选择使用哪一种策略。
    例如支付系统可以选择不同的执行方式。

    PayPal 支付策略
    CreditCard 信用卡支付策略
    Crypto 加密货币支付策略

电商系统只需要调用

java 复制代码
paymentStrategy.pay(amount);

至于具体用 PayPal、信用卡还是加密货币,可以在运行时决定。

适用场景:不同支付方式、不同排序算法、不同折扣规则、不同导航路线、不同攻击方式。

  1. 访问者模式(Visitor Pattern)

    访问者模式会把一组操作放到访问者(Visitor)类中,让访问者去作用于不同的元素对象。这样元素对象本身不用频繁修改,新增操作时可以通过增加新的访问者来完成。

    例如我们有一组不同的文件:PDF、Word、Excel。

    我们要对它们做出不同操作:导出操作、打印操作、压缩操作、统计字数操作。

    如果把所有操作都写进每个文件类里,文件类会越来越复杂。

    所以访问者模式可以写成 ExportVisitor(导出访问者)、PrintVisitor(打印访问者)、CompressVisitor(压缩访问者),然后让访问者去访问不同文件对象并执行对应操作。

    适合场景:对象结构比较稳定、操作经常增加、需要对一组对象执行不同操作。

  2. 中介者模式(Mediator Pattern)

    中介者模式通过一个中介类,帮助多个类之间进行通信。

    当很多对象之间需要互相通信时,如果它们彼此直接调用,关系会很复杂。

    假设我们现在有4个对象,如果希望它们相互通信,那就要有6种组合,而中介者模式会增加一个中介者(Mediator),所有对象都通过中介者通信。

    用户之间不需要一个个直接发送消息,所有消息都交给聊天室服务器转发。

    适用场景:聊天室、航空塔台调度、GUI 组件交互、多人游戏房间、系统模块之间通信。

  3. 观察者模式(Observer Pattern)

    当对象之间存在一对多关系时,可以使用观察者模式来管理对象之间的依赖关系。

    一个对象状态变化后,需要通知多个对象。

    例如:公众号发布新文章,所有订阅者收到通知。

    这里:

    公众号:Subject,被观察对象
    订阅者:Observer,观察者

当公众号更新内容时,所有订阅者都会收到通知。

适用场景是一个对象变化,需要通知多个对象,对象之间是一对多关系,不希望通知逻辑写死在具体类中。

1.1 责任链模式(Chain of Responsibility)

一个请求来了以后,不一定由第一个对象处理。如果第一个对象处理不了,就交给下一个对象。

每个服务只负责一种特定职责。

如上图所示,如果普通客服处理不了,就把请求转给技术客服。技术客服处理不了,再转给主管。

它的核心思想是:客户端发出一个请求,请求会被传给一条处理对象组成的链。链上的每个对象自己判断能不能处理这个请求。如果能处理,就处理;如果不能处理,就传给下一个对象。

每个 Handler 都有两个选择:能处理------处理请求;不能处理------传给下一个 Handler。

最后某个 Handler 处理成功。

责任链模式用于实现软件设计中的低耦合。客户端发出的请求会被传递给一条对象链,由这条链上的对象来处理链中的每个对象会自己判。

1.1.1 关键组成部分

责任链模式一般由 4 个角色组成:

  1. 处理者接口(Handler)
    Handler 定义处理请求的方法接口,有时也会定义设置下一个处理者的方法接口。
    代码通常长这样:
java 复制代码
interface Handler {
    void setNext(Handler handler);

    void handle(Request request);
}

它规定了每个处理者都必须有两个能力:

复制代码
1. handle(request):处理请求
2. setNext(handler):设置下一个处理者
  1. 基础处理者(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);

把请求传给下一个处理者。

  1. 具体处理者(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。

  1. 客户端(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

我们以责任链模式在用户登录认证和权限校验中的应用为例。

这个过程可以分为三步:

  1. 检查用户输入的用户名是否存在。
  2. 检查用户输入的密码是否和该用户名匹配。
  3. 检查该用户绑定的角色。

这正好符合责任链模式,因为这里一个请求沿着一条处理链依次传递,每个处理者只负责一个检查任务,当前处理者通过后,再交给下一个处理者,当前处理者失败,就直接停止。

如下图所示。

逻辑如下:

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 注意点

  1. 客户端可以决定责任链中各个处理者的顺序,然后把请求发送给链上的第一个处理者。
    例如登录验证:
java 复制代码
usernameHandler.setNext(passwordHandler);
passwordHandler.setNext(roleHandler);

客户端只需要调用第一个处理者。

  1. 每个 Handler 负责自己的处理逻辑,每个类只负责一个任务,这样代码结构更清楚。
  2. 每个处理者都应该保存对下一个处理者的引用,这样才能把请求继续传下去。
  3. 创建责任链时必须仔细,否则可能出现的问题有某个 Handler 没有被连接到责任链里、整个责任链中没有任何 Handler 能处理这个请求。
    还有一种情况是所有 Handler 都处理不了请求,最后没有处理结果。所以最好在链尾加一个默认处理逻辑,例如:
java 复制代码
if (next == null) {
    System.out.println("No handler can process this request.");
}
  1. 责任链模式有利于实现低耦合,但它也有代价。

优点:

  • 客户端不需要知道具体由哪个 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 优点

我们单独再整理一遍责任链模式的优点。

  1. 降低耦合(Reduced Coupling)
    责任链模式可以让客户端不用知道到底是哪个对象最终处理了请求。
  2. 增加灵活性(Added Flexibility)
    可以灵活地添加、删除或改变处理者的顺序。
    比如我们刚刚的登录流程如果想添加一个验证码检查,只需要新增一个 CaptchaHandler,然后调整链的连接顺序即可。
  3. 清晰的顺序流程(Clear Sequential Process)
    责任链模式可以让处理顺序非常清楚。
  4. 简化代码(Simplicity)
    责任链模式可以避免使用大量复杂的 if else 条件判断。
    如果不用责任链,前面的示例代码可能写成:
java 复制代码
if (checkUsername(user)) {
    if (checkPassword(user)) {
        if (checkRole(user)) {
            System.out.println("Login success");
        }
    }
}
  1. 关注点分离(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 个部分组成:

  1. 上下文类(Context)
    Context 保存一个具体策略对象,并且通过策略接口调用它。
    在消息发送例子里,MessageService 就是 Context。
    Context 不需要知道具体用的是微信、WhatsApp 还是 Email,它只通过统一接口调用。
  2. 策略接口(Strategy interface)
    Strategy 是所有策略类共同遵守的接口。
    不管是微信发送、WhatsApp 发送、邮件发送,都要实现这个接口。
    比如发送消息的策略接口可以写成:
java 复制代码
interface MessageStrategy {
    void send(String msg);
}
  1. 具体策略类(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);
    }
}
  1. 客户端(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 优点

策略模式的优点如下:

  1. 灵活性(Flexibility)
    我们可以在程序运行时,通过替换 Context 里面的策略对象,改变程序行为。
    同一个 ShoppingCart,可以在运行时切换不同支付方式,如下所示。
java 复制代码
cart.setPaymentStrategy(new PayPalPaymentStrategy());
cart.checkout(100.0);

cart.setPaymentStrategy(new CreditCardPaymentStrategy());
cart.checkout(200.0);
  1. 封装性(Encapsulation)
    相关算法或行为会被封装到不同的策略类中,避免复杂逻辑堆在其他类里。
  2. 复用性(Reusability)
    策略类和上下文类之间耦合度低,所以策略类可以在不同地方复用。
    例如 PayPalPaymentStrategy 不只可以用于 ShoppingCart,也可以用于 SubscriptionService (订阅服务)等,只要它们需要支付功能,就可以复用这个策略类。
  3. 可维护性(Maintainability)
    策略模式可以让代码更容易维护。
    修改某一个策略时,不会影响其他策略。
    如果以后新增一种支付方式,只需要新增一个策略类,而不需要修改 ShoppingCart 的核心代码。
  4. 测试方便(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

我们先用策略模式设计一个灵活的排序系统。

这个应用需要对数据列表进行不同方式的排序,例如按数字大小排序、按字母顺序排序、按自定义规则排序。

而且排序方式需要能够在程序运行时灵活切换,同时不修改原来的代码结构。

例如这次按数字排序,下次按字母排序,再下次按自定义规则排序。

这正好适合使用策略模式。

因为我们可以使用策略模式定义一组算法,把每一种算法都封装成独立的类,并且让这些算法可以互相替换。这样算法的变化不会影响使用它的客户端代码。

这里体现了其优点中的:

  1. 灵活性:客户端可以在程序运行时选择或更换排序算法,而且不用修改客户端代码。
  2. 可维护性:新增排序策略时,可以直接增加一个新的策略类,不需要修改已有类,这符合开闭原则。
  3. 复用性:不同排序策略被封装在自己的类中,所以可以在不同场景中复用。

其类图如下图所示。

示例代码如下:

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 个核心部分组成:

  1. 上下文类(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 负责保存状态和切换状态,具体行为由状态类决定。

  1. 状态接口(State)
    State 接口为所有具体状态定义一个共同接口,并封装某个特定状态下的相关行为。
    所有状态类都要实现同一个接口。
    这样 Context 不需要关心当前具体是哪一个状态,只要调用:
java 复制代码
state.handle();
  1. 具体状态类(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 优点

状态模式的优点如下:

  1. 让控制流程更有组织(Organized Control Flow)
    如果一个系统经常在多个状态之间复杂切换,状态模式可以让流程更清晰。
  2. 把状态相关行为集中到对应状态类中(Localize State Specific Behavior)
    所有和状态有关的操作都会被写进单独的状态类中。这样可以把某个状态下的行为集中管理。
  3. 状态转换更明确(State Transitions Explicit)
    状态之间如何转换会变得更清楚、更可预测,代码也更容易阅读和理解。
  4. 扩展性更好(Extensibility)
    如果以后要增加新状态,只需要新增一个状态类。不用大幅修改原来的类。
  5. 简化代码(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();
    }
}
相关推荐
威尔逊·柏斯科·希伯理2 小时前
软考-软件工程(2-需求工程与系统设计)
软件工程
大迪deblog2 小时前
软件工程-②需求工程
系统架构·软件工程
大迪deblog2 小时前
软件工程-⑤系统运行与维护
系统架构·软件工程
多加点辣也没关系2 小时前
设计模式-适配器模式
设计模式
高翔·权衡之境3 小时前
主题3:天线与耦合——近场与远场
网络·嵌入式硬件·物联网·软件工程·信息与通信
Forget the Dream3 小时前
基于适配器模式的 Axios 封装实践
设计模式·typescript·axios·适配器模式
Java面试题总结3 小时前
【设计模式03】使用模版模式+责任链模式优化实战
设计模式·责任链模式
庞轩px3 小时前
Redis工具类重构——从臃肿到优雅的门面模式实践
数据库·redis·设计模式·重构·门面模式·可扩展性·可维护性
Supersist18 小时前
【设计模式03】使用模版模式+责任链模式优化实战
后端·设计模式·代码规范