Skip to content

Java 行为型模式

AI 整理版

本篇已整理为更适合学习与复习的笔记结构,欢迎前往阅读:行为型模式-AI整理版

  设计模式中的行为型模式有以下几种:

  1. 责任链模式(Chain of Responsibility Pattern)
  2. 命令模式(Command Pattern)
  3. 解释器模式(Interpreter Pattern)
  4. 迭代器模式(Iterator Pattern)
  5. 中介者模式(Mediator Pattern)
  6. 备忘录模式(Memento Pattern)
  7. 观察者模式(Observer Pattern)
  8. 状态模式(State Pattern)
  9. 策略模式(Strategy Pattern)
  10. 模板方法模式(Template Method Pattern)
  11. 访问者模式(Visitor Pattern)

  在设计模式中,行为型模式是指关注对象之间的通信和协作,以及如何将职责划分到不同的对象中。行为型模式可以进一步分为类行为型模式和对象行为型模式两类。

  类行为型模式关注类之间的通信,包括模板方法模式、策略模式和状态模式。其中,模板方法模式定义了算法的骨架,具体实现由子类完成;策略模式定义了一组算法,使它们之间可以互相替换;状态模式允许对象在内部状态改变时改变它的行为。

  对象行为型模式关注对象之间的通信,包括观察者模式、迭代器模式、责任链模式、命令模式、备忘录模式、访问者模式和中介者模式。其中,观察者模式定义了对象之间的一对多依赖关系,当一个对象状态改变时,所有依赖它的对象都会收到通知;迭代器模式提供一种顺序访问聚合对象中各个元素的方法,而又不暴露聚合对象的内部结构;责任链模式为请求创建一个接收者对象的链,请求沿着链传递,直到有一个对象处理请求;命令模式将请求封装成对象,从而允许使用不同的请求、队列或日志来参数化其他对象;备忘录模式提供了在不破坏封装性的前提下,保存和恢复对象之前状态的能力;访问者模式定义了一组操作,可以在不改变对象结构的前提下,对对象元素进行操作;中介者模式定义了一个中介对象来封装对象之间的交互,从而使对象之间的交互松散耦合。

  总之,行为型模式关注对象之间的交互和协作,通过将职责分配到不同的对象中,使得系统更加灵活、可扩展和易于维护。

责任链模式(Chain of Responsibility Pattern)

  责任链模式是一种行为型设计模式,它允许将请求沿着处理者链进行传递,直到有一个处理者能够处理请求为止。

  在责任链模式中,每个处理者都有一个指向下一个处理者的引用,形成一个链式结构。当收到请求时,处理者首先判断自己是否有能力处理该请求,如果有,则处理请求并返回处理结果;如果没有,则将请求传递给下一个处理者,直到有一个处理者能够处理为止。

  责任链模式可以有效地避免请求发送者和接收者之间的耦合,使得请求发送者无需知道请求会被哪个处理者处理,同时也提高了系统的灵活性和可扩展性。

  在实际应用中,责任链模式经常被用于日志记录、异常处理、权限验证等场景,例如,在一个Web应用中,可以将请求传递给多个过滤器进行处理,每个过滤器只负责处理自己关心的请求,从而实现请求的过滤和控制。

如何使用

  演示如何使用责任链模式实现请求日志记录:

  场景:一个Web应用需要记录所有进入系统的请求日志,包括请求路径、请求参数、请求时间等信息,并将日志写入数据库。

  1. 定义责任链节点类:

    java
    /**
     * @description: 请求日志节点类
     * @author: black tea
     * @date: 2023/3/24 16:12
     */
    public abstract class RequestLogger {
    
        protected RequestLogger nextHandler;
    
        public void setNextHandler(RequestLogger nextHandler) {
            this.nextHandler = nextHandler;
        }
    
        /**
         * 执行任务
         * @param request 请求对象
         */
        public abstract void handler(HttpServletRequest request);
    
        /**
         * 执照该顺序执行,越小执行越早
         * @return int
         */
        public abstract int order();
    }
    
    /**
     * @description: 打印请求路径
     * @author: black tea
     * @date: 2023/3/24 16:19
     */
    @Slf4j
    @Component
    public class PathLogger extends RequestLogger {
    
        @Override
        public void handler(HttpServletRequest request) {
            log.info("请求路径:{}", request.getRequestURI());
            if (nextHandler != null){
                nextHandler.handler(request);
            }
        }
    
        @Override
        public int order() {
            return 100;
        }
    }
    
    /**
     * @description: 打印请求参数
     * @author: black tea
     * @date: 2023/3/24 16:21
     */
    @Slf4j
    @Component
    public class ParamLogger extends RequestLogger {
    
        @Override
        public void handler(HttpServletRequest request) {
            log.info("请求参数:{}", JSONUtil.toJsonStr(request.getParameterMap()));
            if (nextHandler != null){
                nextHandler.handler(request);
            }
        }
    
        @Override
        public int order() {
            return 101;
        }
    }
    
    /**
     * @description: 打印请求接收时间
     * @author: black tea
     * @date: 2023/3/24 16:22
     */
    @Slf4j
    @Component
    public class TimeLogger extends RequestLogger {
    
        @Override
        public void handler(HttpServletRequest request) {
            log.info("请求接收时间:{}", DateUtil.now());
            if (nextHandler != null){
                nextHandler.handler(request);
            }
        }
    
        @Override
        public int order() {
            return 102;
        }
    }
    
    /**
     * @description: 将请求写入库
     * @author: black tea
     * @date: 2023/3/24 16:23
     */
    @Slf4j
    @Component
    public class DatabaseLogger extends RequestLogger {
    
        @Override
        public void handler(HttpServletRequest request) {
            log.info("将请求写入库:{}", request);
            if (nextHandler != null){
                nextHandler.handler(request);
            }
        }
    
        @Override
        public int order() {
            return 200;
        }
    }
  2. 配置责任链:

    java
    /**
     * @description: 责任链配置
     * @author: black tea
     * @date: 2023/3/24 16:28
     */
    @Configuration
    @RequiredArgsConstructor
    public class ChainOfResponsibilityPatternConfig {
    
        private final List<RequestLogger> loggers;
    
        @Bean
        public RequestLogger requestLogger(){
            loggers.sort(Comparator.comparingInt(RequestLogger::order));
            IntStream.range(0, loggers.size())
                    .forEach(index -> {
                        RequestLogger current = loggers.get(index);
                        RequestLogger next = index < loggers.size() - 1 ? loggers.get(index + 1) : null;
                        if (next != null){
                            current.setNextHandler(next);
                        }
                    });
            return loggers.get(0);
        }
    
    }
  3. 对外接口:

    java
    /**
     * @description: 测试请求日志责任链接口
     * @author: black tea
     * @date: 2023/3/24 16:41
     */
    @RequestMapping("/request/test")
    @RestController
    @RequiredArgsConstructor
    public class RequestLoggerTestController {
    
        private final RequestLogger requestLogger;
    
        @RequestMapping("{name}")
        public String handlerRequest(@PathVariable String name, HttpServletRequest request){
            requestLogger.handler(request);
            return "请求处理成功";
        }
    
    }
  4. 单元测试:

    java
     	/**
         * 测试 责任链模式
         */
        @Test
        void testChainOfResponsibilityPattern() {
            String name = "张三";
            String apiUrl = "request/test/"+name;
            String url = "http://localhost:8080/"+apiUrl;
    
            // get请求测试
            String getUrl = url + "?a=1&b=33&c=search";
            HttpResponse httpResponseGet = HttpRequest.get(getUrl).execute();
            log.info("get请求{},返回内容:{}", httpResponseGet.isOk(), httpResponseGet.body());
    
    
            // post请求测试
            Map<String, Object> paramMap = new HashMap<>();
            paramMap.put("username", "333333");
            paramMap.put("password", "******");
            HttpResponse httpResponsePost = HttpRequest.post(url)
                    .form(paramMap)//表单内容
                    .timeout(20000)//超时,毫秒
                    .execute();
            log.info("post请求{},返回内容:{}", httpResponsePost.isOk(), httpResponsePost.body());
    
        }

  可以看到,责任链模式可以方便地将不同的日志记录节点组合起来,实现灵活的请求日志记录。

  为方便查看,省略前面的完整信息。

text
handler.PathLogger               : 请求路径:/request/test/%E5%BC%A0%E4%B8%89
handler.ParamLogger              : 请求参数:{"a":["1"],"b":["33"],"c":["search"]}
handler.TimeLogger               : 请求接收时间:2023-03-24 17:24:13
handler.DatabaseLogger           : 将请求写入库:org.apache.catalina.connector.RequestFacade@70d5695e
handler.PathLogger               : 请求路径:/request/test/%E5%BC%A0%E4%B8%89
handler.ParamLogger              : 请求参数:{"password":["******"],"username":["333333"]}
handler.TimeLogger               : 请求接收时间:2023-03-24 17:24:14
handler.DatabaseLogger           : 将请求写入库:org.apache.catalina.connector.RequestFacade@70d5695e

  GitHub学习地址:java-design-patterns/localization/zh/chain at master · iluwatar/java-design-patterns (github.com)

命令模式(Command Pattern)

  命令模式是一种行为型设计模式,它将请求封装成一个对象,从而使不同的请求可以进行参数化,支持撤销操作,以及支持命令队列和日志记录等功能。在命令模式中,请求发送者和请求接收者之间被解耦,请求接收者只需要实现一个执行命令的方法,即可完成请求的处理。命令模式的核心思想是将请求和实现解耦,从而实现请求和实现的松耦合。命令模式常用于实现菜单、工具栏和快捷键等用户界面操作的处理,也常用于实现事务处理、日志记录、撤销操作和重做操作等功能。

如何使用

  这里是一个简单文本编辑器的案例,通过以下步骤实现:

  1. 定义命令接口(Command)及其实现类,其中包含执行(execute)和撤销(undo)两个方法。

    java
    /**
     * @description: 命令接口
     * @author: black tea
     * @date: 2023/3/24 20:17
     */
    public interface Command {
    
        /**
         * 执行
         */
        void execute();
    
        /**
         * 撤销
         */
        void undo();
    }
    
    /**
     * @description: 添加命令类
     * @author: black tea
     * @date: 2023/3/24 20:18
     */
    public class AddCommand implements Command {
    
        /**
         * 内容
         */
        private String text;
    
        /**
         * 开始的位置
         */
        private int start;
    
        /**
         * 文本编辑器
         */
        private Editor editor;
    
    
        public AddCommand(Editor editor, String text, int start) {
            this.text = text;
            this.start = start;
            this.editor = editor;
        }
    
        @Override
        public void execute() {
            editor.add(text, start);
        }
    
        @Override
        public void undo() {
            editor.delete(start, start + text.length());
        }
    }
    
    /**
     * @description: 删除命令类
     * @author: black tea
     * @date: 2023/3/24 22:52
     */
    public class DeleteCommand implements  Command {
    
        /**
         * 内容
         */
        private String text;
    
        /**
         * 开始的位置
         */
        private int start;
    
        /**
         * 文本编辑器
         */
        private Editor editor;
    
        public DeleteCommand(Editor editor, String text, int start) {
            this.text = text;
            this.start = start;
            this.editor = editor;
        }
    
        @Override
        public void execute() {
            editor.delete(start, start + text.length());
        }
    
        @Override
        public void undo() {
            editor.add(text, start);
        }
    }
  2. 定义命令执行器(CommandExecutor),用于执行和撤销命令,并维护命令历史记录。

    java
    /**
     * @description: 命令执行器类
     * @author: black tea
     * @date: 2023/3/24 20:23
     */
    public class CommandExecutor {
    
        private final Stack<Command> historyStack;
    
        public CommandExecutor() {
            this.historyStack = new Stack<>();
        }
    
        public void executeCommand(Command command){
            command.execute();
            historyStack.push(command);
        }
    
        public void undoCommand(){
            if (!historyStack.isEmpty()){
                // 进行撤回操作
                Command command = historyStack.pop();
                command.undo();
            }
        }
    
    }
  3. 定义文本编辑器(Editor),包含添加(add)和删除(delete)方法,并将它们封装成命令对象。

    java
    /**
     * @description: 文本编辑器
     * @author: black tea
     * @date: 2023/3/24 20:20
     */
    @Component
    public class Editor {
    
        private final CommandExecutor executor = new CommandExecutor();
        private final StringBuilder content = new StringBuilder();
    
        public void add(String text, int start) {
            content.insert(start, text);
        }
    
        public void delete(int start, int end) {
            content.delete(start, end);
        }
    
        public void execute(Command command) {
            executor.executeCommand(command);
        }
    
        public void undo() {
            executor.undoCommand();
        }
    
        public String getContent() {
            return content.toString();
        }
    }
  4. 单元测试使用:

    java
     	@Autowired
        private Editor editor;
    
        /**
         * 测试 命令模式
         */
        @Test
        void testCommandPttern(){
            log.debug("测试 命令模式");
    
            AddCommand addCommand = new AddCommand(editor,"你好啊,我是红茶。", 0);
            editor.execute(addCommand);
            log.info("1. 添加命令后,本次编辑器内容:{}", editor.getContent());
    
            String text = editor.getContent().substring(2, 3);
            DeleteCommand deleteCommand = new DeleteCommand(editor, text, 2);
            editor.execute(deleteCommand);
            log.info("2. 删除命令后,本次编辑器内容:{}", editor.getContent());
    
            editor.undo();
            log.info("3. 撤销之前的命令后,本次编辑器内容:{}", editor.getContent());
    
        }

  以上代码实现了一个简单的编辑器,支持添加和删除文本,并提供了撤销操作。在添加和删除文本时,会创建相应的命令对象并执行;在撤销操作时,会从命令历史记录中取出最后一个命令对象并执行其撤销操作。

  GitHub学习地址:java-design-patterns/localization/zh/command at master · iluwatar/java-design-patterns (github.com)

解释器模式(Interpreter Pattern)

  解释器模式是一种行为型设计模式,它定义了一种语言语法的表示,并定义了该语言解释器的解释方法,从而实现对该语言的解释和执行。解释器模式常用于实现编译器、解释器、正则表达式引擎等应用程序。在解释器模式中,通常有两种角色:抽象表达式和具体表达式。抽象表达式定义了解释器的接口和解释方法,具体表达式实现了具体的解释方法。解释器模式的核心思想是将语言解释器和语言语法分离,从而实现语言的灵活扩展和重用。

  解释器模式的优点是增加新的语法规则相对容易,缺点是增加新的表达式类型比较困难,且解释器的性能通常较低。

如何使用

  使用解释器模式来实现一个简单的DSL(领域特定语言)。

  假设我们有一个DSL,它由一系列表达式组成,可以用于描述一个人的基本信息。例如,我们可以使用以下语句来描述一个人的基本信息:

text
name is John and age is greater than 18 and gender is male

  这个语句由三个表达式组成:一个等于运算、一个大于运算和一个等于运算。我们可以使用解释器模式来将这个DSL解析为一个Java对象,并使用这个对象来处理人的基本信息。

  1. 首先,我们定义一个接口Expression,它有一个interpret方法用于解析表达式并返回结果。

    java
    /**
     * @description: 表达式接口
     * @author: black tea
     * @date: 2023/3/25 15:20
     */
    public interface Expression {
    
        boolean interpret(Context context);
    }
  2. 我们实现两个具体的表达式类:EqualsExpression和GreaterThanExpression,它们分别表示等于和大于运算。

    java
    /**
     * @description: 等于
     *  具体的表达式类
     * @author: black tea
     * @date: 2023/3/25 15:22
     */
    public class EqualsExpression implements Expression {
        private String variableName;
        private String value;
    
        public EqualsExpression(String variableName, String value) {
            this.variableName = variableName;
            this.value = value;
        }
    
        @Override
        public boolean interpret(Context context) {
            String variableValue = (String) context.getVariableValue(variableName);
            return variableValue != null && variableValue.equals(value);
        }
    }
    
    /**
     * @description: 大于
     * 具体的表达式类
     * @author: black tea
     * @date: 2023/3/25 15:23
     */
    public class GreaterThanExpression implements Expression {
        private final String variableName;
        private final int value;
    
        public GreaterThanExpression(String variableName, int value) {
            this.variableName = variableName;
            this.value = value;
        }
    
        @Override
        public boolean interpret(Context context) {
            Object variableValue = context.getVariableValue(variableName);
            if (variableValue == null) {
                return false;
            }
            try {
                int intValue = (int) variableValue;
                return intValue > value;
            } catch (NumberFormatException e) {
                return false;
            }
        }
    }
  3. 我们实现一个具体的非终结符表达式类:AndExpression,它表示与运算。它需要一个左表达式和右表达式作为参数。

    java
    /**
     * @description: and 表达式
     *  非终结符表达式类,它表示与运算。它需要一个左表达式和右表达式作为参数。
     * @author: black tea
     * @date: 2023/3/25 15:23
     */
    public class AndExpression implements Expression {
        private final Expression left;
        private final Expression right;
    
        public AndExpression(Expression left, Expression right) {
            this.left = left;
            this.right = right;
        }
    
        @Override
        public boolean interpret(Context context) {
            return left.interpret(context) && right.interpret(context);
        }
    }
  4. 单元测试:

    java
     /**
      * 测试 解释器模式
      */
    @Test
    void testInterpreterPattern(){
        log.debug("测试 解释器模式");
    
        // 需要进行解释的dsl语句
        String dsl = "name is John and age is greater than 18 and gender is male";
        // 构建解释表达式
        Expression expression = new AndExpression(
            new AndExpression(
                new EqualsExpression("name", "John"),
                new GreaterThanExpression("age", 18)
            ),
            new EqualsExpression("gender", "male")
        );
        // 假设正确的值
        Map<String, Object> metaMap = new HashMap<>();
        metaMap.put("name", "John");
        metaMap.put("age", 20);
        metaMap.put("gender", "male");
        Context context = new Context(metaMap);
    
        boolean result = expression.interpret(context);
        log.info("解释最后的结果:{}", result);
    }

    在这个例子中,我们使用了解释器模式来将DSL解析为一个Java对象,并使用这个对象来处理人的基本信息。这种方法可以很容易地扩展到更复杂的DSL中。

  GitHub学习地址:java-design-patterns/localization/zh/interpreter at master · iluwatar/java-design-patterns (github.com)

迭代器模式(Iterator Pattern)

  迭代器模式是一种行为型设计模式,主要用于按照特定顺序遍历集合中的元素,而无需暴露集合内部的结构。迭代器模式将遍历集合的过程与集合本身解耦,使得集合可以独立地变化而不影响遍历算法。

  通常,迭代器模式包含以下组件:

  1. 抽象迭代器(Iterator):定义了遍历集合的接口,包括获取下一个元素、判断是否还有元素等方法。
  2. 具体迭代器(ConcreteIterator):实现了抽象迭代器接口,负责遍历集合并维护遍历状态。
  3. 抽象集合(Aggregate):定义了集合的接口,包括获取迭代器方法等。
  4. 具体集合(ConcreteAggregate):实现了抽象集合接口,负责创建具体迭代器。

  迭代器模式的优点在于:

  1. 隐藏了集合的内部结构,使得遍历算法与集合的实现相互独立。
  2. 可以按照不同的顺序遍历集合,而无需修改集合的代码。
  3. 可以同时遍历多个集合,而无需了解它们的具体实现。

  迭代器模式的缺点在于:

  1. 需要额外的迭代器类,增加了代码复杂度。
  2. 遍历时需要额外的空间存储迭代器对象,增加了内存开销。

如何使用

  使用Java标准库提供的迭代器接口来实现迭代器模式。

  以下是一个简单的示例:

  1. 首先,定义一个接口,该接口包含迭代器方法:

    java
    /**
     * @description:
     * @author: black tea
     * @date: 2023/3/25 16:08
     */
    public interface MyCollection<T> extends Iterable<T> {
    
        @Override
        Iterator<T> iterator();
    }
  2. 然后,实现该接口的类需要提供一个返回迭代器的方法:

    java
    /**
     * @description:
     * @author: black tea
     * @date: 2023/3/25 16:09
     */
    public class MyArrayList<T> implements MyCollection<T> {
    
        private List<T> list;
    
    
        public MyArrayList(List<T> list) {
            this.list = list;
        }
    
        @Override
        public Iterator<T> iterator() {
            return list.iterator();
        }
    }

    在这个示例中,MyArrayList类使用Java标准库中的List接口来存储元素。它实现了MyCollection接口,并提供了一个返回迭代器的方法。

  3. 单元测试,使用MyArrayList类来遍历元素:

    java
     	/**
         * 测试 迭代器模式
         */
        @Test
        void testIteratorPattern(){
            log.debug("测试 迭代器模式");
    
            List<String> myList = new ArrayList<>();
            myList.add("foo");
            myList.add("bar");
    
            MyCollection<String> myCollection = new MyArrayList<>(myList);
    
            for (String element : myCollection) {
              log.info(element);
            }
        }

  GitHub学习地址:java-design-patterns/localization/zh/iterator at master · iluwatar/java-design-patterns (github.com)

中介者模式(Mediator Pattern)

  中介者模式是一种行为型设计模式,它用于降低多个对象之间的耦合度,通过将对象间的通信集中到一个中介者对象中进行处理,从而避免对象间的直接相互作用。

  中介者模式的主要组成部分包括:

  1. 抽象中介者(Mediator):定义了中介者对象的接口,包括注册对象、转发消息等方法。
  2. 具体中介者(ConcreteMediator):实现了抽象中介者接口,负责协调各个对象之间的通信。
  3. 抽象同事类(Colleague):定义了同事类的接口,包括注册中介者、发送消息等方法。
  4. 具体同事类(ConcreteColleague):实现了抽象同事类接口,负责向中介者发送消息,同时也接收中介者转发的消息。

  在中介者模式中,对象间的通信是通过中介者对象进行的,而不是直接相互作用。这种方式可以降低对象间的耦合度,从而使系统更加灵活、可扩展和易维护。同时,中介者模式也可以避免出现复杂的逻辑交互,使系统更加简单、清晰。

  中介者模式的优点在于:

  1. 可以降低对象间的耦合度,使系统更加灵活、可扩展和易维护。
  2. 可以避免出现复杂的逻辑交互,使系统更加简单、清晰。
  3. 可以集中处理对象间的通信,提高系统的性能和效率。

  中介者模式的缺点在于:

  1. 中介者对象本身可能会变得过于复杂,难以维护。
  2. 中介者模式可能会导致系统变得过于集中化,从而降低系统的灵活性和可扩展性。

如何使用

  假设有一个图书馆管理系统,其中包含了借书和还书两个模块。当用户借书或还书时,需要更新图书库存和用户借阅信息。这时就可以使用中介者模式来协调图书服务和用户服务之间的消息传递和处理。

  1. 定义中介者接口Mediator。

    java
    /**
     * @description: 中介者接口
     * @author: black tea
     * @date: 2023/3/25 16:34
     */
    public interface Mediator {
    
        /**
         * 处理消息
         * 
         * @param message 消息
         * @param colleague 同事对象
         * @param args 入参参数
         */
        void handleMessage(String message, Colleague colleague, Object... args);
    
        /**
         * 注册同事类
         * 
         * @param name  同事名称
         * @param colleague 同事对象
         */
        void register(String name, Colleague colleague);
    
    }
  2. 实现中介者接口Mediator的具体类ConcreteMediator,包含一个Map类型的成员变量,用于存储不同对象之间的消息关系。

    注意:colleagueMap不要使用注入,会引起循环依赖问题(可以解决但是不好),我这里为了避免该问题,选择手动注册。

    java
    /**
     * @description:
     * @author: black tea
     * @date: 2023/3/25 16:36
     */
    @Component
    @Slf4j
    public class ConcreteMediator implements Mediator {
    
        private final Map<String, Colleague> colleagueMap = new HashMap<>();
    
        @Override
        public void handleMessage(String message, Colleague colleague, Object... args) {
            if (colleague instanceof BookService) {
                // 图书服务发送消息
                if (message.equals("borrowBook")) {
                    // 借书
                    log.info("中介 图书服务借出图书...");
                    // 更新图书库存
                    Book book = (Book) args[0];
                    UserService userService = (UserService) colleagueMap.get("userService");
                    userService.updateBorrowInfo(book.getIsbn(), book.getBorrower());
                } else if (message.equals("returnBook")) {
                    // 还书
                    log.info("中介 图书服务收回图书...");
                    // 更新图书库存
                    Book book = (Book) args[0];
                    UserService userService = (UserService) colleagueMap.get("userService");
                    userService.updateBorrowInfo(book.getIsbn(), null);
                }
            } else if (colleague instanceof UserService) {
                // 用户服务发送消息
                if (message.equals("updateBorrowInfo")) {
                    // 更新用户借阅信息
                    log.info("中介 用户服务更新借阅信息...");
                }
            }
        }
    
        @Override
        public void register(String name, Colleague colleague) {
            colleagueMap.put(name, colleague);
        }
    }
  3. 定义同事接口Colleague,包含发送消息的方法send和接收消息的方法receive,并持有中介者对象。

    java
    /**
     * @description: 抽象同事接口
     * @author: black tea
     * @date: 2023/3/25 16:34
     */
    public interface Colleague {
    
        public abstract void send(String message, Object... args);
    
        public abstract void receive(String message);
    
    }
  4. 实现具体同事类BookService和UserService,分别实现抽象同事接口Colleague的方法。

    java
    /**
     * @description:
     * @author: black tea
     * @date: 2023/3/25 16:37
     */
    @Slf4j
    @Service
    public class BookService implements Colleague {
    
        private final Mediator mediator;
    
        public BookService(@Autowired Mediator mediator) {
            mediator.register(StrUtil.lowerFirst(this.getClass().getSimpleName()), this);
            this.mediator = mediator;
        }
    
        public void borrowBook(Book book) {
            log.info("book service图书服务借出图书...");
            send("borrowBook", book);
        }
    
        public void returnBook(Book book) {
            log.info("book service 图书服务收回图书...");
            send("returnBook", book);
        }
    
        @Override
        public void send(String message, Object... args) {
            mediator.handleMessage(message, this, args);
        }
    
        @Override
        public void receive(String message) {
            log.info("图书服务接收到消息:" + message);
        }
    
    }
    
    /**
     * @description:
     * @author: black tea
     * @date: 2023/3/25 16:44
     */
    @Slf4j
    @Service
    public class UserService implements Colleague {
    
        private final Mediator mediator;
    
        public UserService(@Autowired Mediator mediator) {
            mediator.register(StrUtil.lowerFirst(this.getClass().getSimpleName()), this);
            this.mediator = mediator;
        }
    
        public void updateBorrowInfo(String isbn, String borrower) {
            log.info("user service 用户服务更新借阅信息...");
            send("updateBorrowInfo");
        }
    
        @Override
        public void send(String message, Object... args) {
            mediator.handleMessage(message, this, args);
        }
    
        @Override
        public void receive(String message) {
            log.info("用户服务接收到消息:" + message);
        }
    }
  5. 在业务逻辑中,通过具体同事类发送消息,并由中介者来协调处理消息。

    java
    /**
     * @description:
     * @author: black tea
     * @date: 2023/3/25 16:48
     */
    @Service
    public class BorrowService {
    
        @Autowired
        private BookService bookService;
    
        @Autowired
        private UserService userService;
    
        public void borrow(Book book, String borrower) {
            // 借书
            book.setBorrower(borrower);
            bookService.borrowBook(book);
        }
    
        public void returnBook(Book book) {
            // 还书
            book.setBorrower(null);
            bookService.returnBook(book);
        }
    }
  6. 单元测试使用:

    java
    
        @Autowired
        private BorrowService borrowService;
    
        /**
         * 测试 中介模式
         */
        @Test
        void testMediatorPattern(){
            log.debug("测试 中介模式");
            
            Book book = new Book();
    
            borrowService.borrow(book, "张三");
            log.info("借书后,图书的信息:{}",book.toString());
    
            borrowService.returnBook(book);
            log.info("还书后,图书的信息:{}",book.toString());
        }

    在以上示例中,中介者ConcreteMediator协调了BookService和UserService之间的消息传递和处理,当BookService借出图书或收回图书时,ConcreteMediator会调用UserService的updateBorrowInfo方法来更新用户借阅信息。

测试接口打印:

text
BehavioralPatternsApplicationTests : 测试 中介模式
mediatorpattern.BookService        : book service图书服务借出图书...
mediatorpattern.ConcreteMediator   : 中介 图书服务借出图书...
mediatorpattern.UserService        : user service 用户服务更新借阅信息...
mediatorpattern.ConcreteMediator   : 中介 用户服务更新借阅信息...
BehavioralPatternsApplicationTests : 借书后,图书的信息:Book(borrower=张三, isBn=null)
mediatorpattern.BookService        : book service 图书服务收回图书...
mediatorpattern.ConcreteMediator   : 中介 图书服务收回图书...
mediatorpattern.UserService        : user service 用户服务更新借阅信息...
mediatorpattern.ConcreteMediator   : 中介 用户服务更新借阅信息...
BehavioralPatternsApplicationTests : 还书后,图书的信息:Book(borrower=null, isBn=null)

  GitHub学习地址:java-design-patterns/mediator at master · iluwatar/java-design-patterns (github.com)

备忘录模式(Memento Pattern)

  备忘录模式是一种行为型设计模式,它允许在不破坏封装性的前提下捕获和存储一个对象的内部状态,并能在需要时将对象恢复到先前的状态。简单来说,备忘录模式可以让你在不暴露对象实现细节的情况下,保存和恢复对象状态。

  在备忘录模式中,有三个主要角色:

  1. Originator(原发器):创建并持有对象的状态,并能够将其状态保存到备忘录中或从备忘录中恢复状态。
  2. Memento(备忘录):存储原发器对象的内部状态,并且可以防止原发器以外的对象访问备忘录。
  3. Caretaker(负责人):负责管理备忘录,并在需要时向原发器请求恢复状态。

  备忘录模式的优点是能够轻松地实现对象状态的保存和恢复,同时保持了对象的封装性。但是,备忘录模式也有一些缺点,比如可能会占用大量的内存空间,因为需要保存对象的所有状态。

如何使用

  在购物车中使用备忘录模式。

  假设有一个购物车类Cart,其包含商品列表items和总价totalPrice。当用户向购物车中添加商品时,Cart类的状态会发生改变。为了实现撤销操作,我们可以使用备忘录模式。

  1. 创建一个商品类:

    java
    /**
     * @description: 商品类
     * @author: black tea
     * @date: 2023/3/25 20:08
     */
    @Data
    public class Goods {
    
        private Long id;
    
        private String name;
    
        private BigDecimal price;
    
    }
  2. 再定义一个备忘录类CartMemento,用于保存Cart类的状态:

    java
    /**
     * @description: 备忘录类
     * @author: black tea
     * @date: 2023/3/25 20:07
     */
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class CartMemento {
    
        /**
         * 商品列表
         */
        private List<Goods> items = new ArrayList<>();
        /**
         * 总价
         */
        private BigDecimal totalPrice = BigDecimal.ZERO;
    
    }
  3. 在Cart类中定义一个createMemento()方法用于创建备忘录对象,以及一个restoreMemento()方法用于恢复状态:

    java
    /**
     * @description: 购物车类
     * @author: black tea
     * @date: 2023/3/25 20:12
     */
    @Component
    public class Cart {
    
        /**
         * 商品列表
         */
        private List<Goods> items = new ArrayList<>();
        /**
         * 总价
         */
        private BigDecimal totalPrice = BigDecimal.ZERO;
    
        /**
         * 添加商品
         * @param goods 商品
         */
        public void addItem(Goods goods) {
            items.add(goods);
            totalPrice = totalPrice.add(goods.getPrice());
        }
    
        /**
         * 创建备忘录对象
         * @return CartMemento 备忘录对象
         */
        public CartMemento createMemento() {
            return new CartMemento(new ArrayList<>(items), totalPrice);
        }
    
        /**
         * 恢复状态
         *
         * @param memento 备忘录对象
         */
        public void restoreMemento(CartMemento memento) {
            items = new ArrayList<>(memento.getItems());
            totalPrice = memento.getTotalPrice();
        }
    
        public List<Goods> getItems() {
            return items;
        }
    
        public BigDecimal getTotalPrice() {
            return totalPrice;
        }
    }
  4. 在CartService类中使用备忘录模式实现撤销操作:

    java
    /**
     * @description: 购物车服务类
     * @author: black tea
     * @date: 2023/3/25 20:14
     */
    @Service
    @RequiredArgsConstructor
    public class CartService {
    
        private final Cart cart;
        private final Stack<CartMemento> history = new Stack<>();
    
        /**
         * 添加商品
         * @param goods 商品对象
         */
        public void addGoods(Goods goods){
            history.push(cart.createMemento());
            cart.addItem(goods);
        }
    
        /**
         * 撤销操作
         */
        public void undo() {
            if (!history.isEmpty()) {
                CartMemento memento = history.pop();
                cart.restoreMemento(memento);
            }
        }
    
        public List<Goods> getGoodsList(){
            return cart.getItems();
        }
    
        public BigDecimal getTotalPrice(){
            return cart.getTotalPrice();
        }
    
    }
  5. 单元测试:

    java
        @Autowired
        private CartService cartService;
    
        /**
         * 测试 备忘录模式
         */
        @Test
        void testMementoPattern(){
            log.debug("测试 备忘录模式");
    
            // 添加商品1
            Goods goods = new Goods();
            goods.setId(1L);
            goods.setName("350ml可乐");
            goods.setPrice(new BigDecimal("3.5"));
            cartService.addGoods(goods);
            // 添加商品2
            Goods goods1 = new Goods();
            goods1.setId(2L);
            goods1.setName("550ml雪碧");
            goods1.setPrice(new BigDecimal("5.0"));
            cartService.addGoods(goods1);
            log.info("当前购物车里有:{},总金额为:{}",
                    JSONUtil.toJsonStr(cartService.getGoodsList()), cartService.getTotalPrice());
    
            // 撤销上次的添加
            cartService.undo();
            log.info("当前购物车里有:{},总金额为:{}",
                    JSONUtil.toJsonStr(cartService.getGoodsList()), cartService.getTotalPrice());
    
        }

      这样,当用户向购物车中添加商品时,每次操作都会创建一个备忘录对象保存Cart类的状态。当用户需要撤销操作时,可以从备忘录栈中取出最近一次的备忘录对象,然后使用restoreMemento()方法恢复Cart类的状态。

  GitHub学习地址:java-design-patterns/memento at master · iluwatar/java-design-patterns (github.com)

观察者模式(Observer Pattern)

  观察者模式是一种设计模式,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象,当主题对象发生变化时,它的所有观察者都会收到通知并自动更新自己的状态。这种模式可以实现松耦合,让主题和观察者之间的耦合度降低,从而提高系统的可扩展性和可维护性。

  在观察者模式中,主题对象(也称为被观察者)维护了一个观察者列表,并提供了添加、删除和通知观察者的方法。观察者对象需要实现一个接口或抽象类,其中定义了接收主题对象通知的方法。当主题对象发生变化时,它会遍历观察者列表,并调用每个观察者的通知方法,将变化的信息传递给它们。

  观察者模式可以应用于许多场景,例如事件处理、GUI编程、消息传递等等。它可以帮助我们实现更灵活、更可扩展的系统,并且可以让我们更好地理解和管理对象之间的依赖关系。

如何使用

  假设有一个订单系统,当订单状态发生改变时,需要通知多个观察者,比如发货系统、库存系统等等。

  1. 我们可以定义订单状态枚举OrderStatus、订单类Order、订单状态变化事件类:

    java
    /**
     * @description:
     * @author: black tea
     * @date: 2023/3/25 22:47
     */
    public enum OrderStatus {
    
        /**
         * 交付
         */
        DELIVERED,
        /**
         * 支付
         */
        PAID;
    
    }
    
    /**
     * @description:
     * @author: black tea
     * @date: 2023/3/25 22:47
     */
    @Data
    public class Order {
    
        private OrderStatus status;
    }
    
    /**
     * @description: 订单状态变化事件类
     * @author: black tea
     * @date: 2023/3/25 22:49
     */
    public class OrderStatusChangeEvent extends ApplicationEvent {
    
        private Order order;
    
        public OrderStatusChangeEvent(Object source, Order order) {
            super(source);
            this.order = order;
        }
    
        public Order getOrder() {
            return order;
        }
    }

    其中,Order表示订单类,OrderStatusChangeEvent表示订单状态变化事件类,继承自ApplicationEvent

  2. 然后,我们定义一个订单服务类:

    java
    /**
     * @description: 订单服务类
     * @author: black tea
     * @date: 2023/3/25 22:50
     */
    @Service
    @RequiredArgsConstructor
    public class OrderService {
    
        private final ApplicationEventPublisher publisher;
    
        public void updateOrderStatus(Order order, OrderStatus newStatus){
            // 更新订单状态
            order.setStatus(newStatus);
            // 发布订单状态变化事件
            publisher.publishEvent(new OrderStatusChangeEvent(this, order));
        }
    
    }

    其中,ApplicationEventPublisher是Spring框架提供的事件发布器,可以用来发布事件。当订单状态发生改变时,我们调用publisher.publishEvent()方法发布一个OrderStatusChangeEvent事件。

  3. 接下来,我们定义多个观察者:

    java
    /**
     * @description: 发货系统
     * @author: black tea
     * @date: 2023/3/25 22:52
     */
    @Slf4j
    @Component
    public class DeliverySystem implements ApplicationListener<OrderStatusChangeEvent> {
    
        @Override
        public void onApplicationEvent(OrderStatusChangeEvent event) {
            Order order = event.getOrder();
            if (order.getStatus() == OrderStatus.DELIVERED) {
                // 发货
                log.info("发货操作");
            }
        }
    }
    
    /**
     * @description: 库存系统
     * @author: black tea
     * @date: 2023/3/25 22:53
     */
    @Component
    @Slf4j
    public class InventorySystem implements ApplicationListener<OrderStatusChangeEvent> {
    
        @Override
        public void onApplicationEvent(OrderStatusChangeEvent event) {
            Order order = event.getOrder();
            if (order.getStatus() == OrderStatus.PAID) {
                // 扣减库存
                log.info("扣减库存操作");
            }
        }
    }

    其中,DeliverySystem表示发货系统,InventorySystem表示库存系统,它们都实现了ApplicationListener<OrderStatusChangeEvent>接口,表示它们监听OrderStatusChangeEvent事件。

  4. 单元测试使用:

    java
      @Autowired
        private OrderService orderService;
    
        /**
         * 测试 观察者模式
         */
        @Test
        void testObserverPattern(){
            log.debug("测试 观察者模式");
    
            Order order = new Order();
            // 通知监听系统
            orderService.updateOrderStatus(order, OrderStatus.DELIVERED);
            // 通知监听系统
            orderService.updateOrderStatus(order, OrderStatus.PAID);
        }
  5. 输出结果:

    java
    c.b.b.BehavioralPatternsApplicationTests : 测试 观察者模式
    c.b.b.o.event.DeliverySystem             : 发货操作
    c.b.b.o.event.InventorySystem            : 扣减库存操作

    当订单状态发生改变时,OrderService会发布一个OrderStatusChangeEvent事件,所有监听该事件的观察者都会收到通知,根据订单状态进行相应的操作。

  GitHub学习地址:java-design-patterns/localization/zh/observer at master · iluwatar/java-design-patterns (github.com)

状态模式(State Pattern)

  状态模式是一种设计模式,它允许对象在其内部状态发生改变时改变其行为,从而使对象看起来像是改变了其类。这种模式可以将状态的判断和处理从业务逻辑中分离出来,从而使得代码更加清晰,易于维护和扩展。

  在状态模式中,一个对象的行为取决于其内部状态,当状态发生改变时,对象的行为也会相应地改变。状态模式包含三个角色:状态接口、具体状态和上下文。状态接口定义了所有具体状态所需实现的方法,具体状态实现了这些方法以反映其特定的行为,上下文包含一个当前状态对象,并将请求委托给当前状态对象处理。

  状态模式可以应用于许多场景,例如状态机、工作流、游戏等等。它可以帮助我们实现更灵活、更可扩展的系统,并且可以让我们更好地理解和管理对象之间的状态转换。

如何使用

  下面来写一个电梯实际案例:

  1. 定义状态接口(State)和具体状态类:

    java
    /**
     * @description: 电梯状态接口
     * @author: black tea
     * @date: 2023/3/26 15:16
     */
    public interface ElevatorState {
    
        Logger log = LoggerFactory.getLogger(ElevatorState.class);
        /**
         * 开门
         */
        default void openDoor(){
            log.info(("电梯门打开了!"));
        }
    
        /**
         * 关门
         */
        default void closeDoor(){
            log.info(("电梯门关闭了!"));
        }
    
        /**
         * 上升
         */
        default void goUp(){
            log.info(("电梯正在上行中..."));
        }
    
        /**
         * 下降
         */
        default void goDown(){
            log.info(("电梯正在下行中..."));
        }
    
    }
    
    /**
     * @description: 打开电梯状态类
     * @author: black tea
     * @date: 2023/3/26 15:18
     */
    @Slf4j
    @Component
    public class OpenState implements ElevatorState {
    
        @Override
        public void openDoor() {
            log.info("电梯门已经打开了,无需再次打开!");
        }
    }
    
    /**
     * @description:  关闭电梯状态类
     * @author: black tea
     * @date: 2023/3/26 15:25
     */
    @Slf4j
    @Component
    public class CloseState implements ElevatorState {
    
        @Override
        public void closeDoor() {
            log.info("电梯门已经关闭了,无需再次关闭!");
        }
    }
    
    /**
     * @description: 上升电梯状态类
     * @author: black tea
     * @date: 2023/3/26 15:26
     */
    @Slf4j
    @Component
    public class UpState implements ElevatorState {
    
        @Override
        public void openDoor() {
            log.info("电梯正在上行中,无法打开门!");
        }
    
        @Override
        public void closeDoor() {
            log.info("电梯正在上行中,无法关闭门!");
        }
    
        @Override
        public void goDown() {
            log.info("电梯转为下行状态!");
        }
    }
    
    /**
     * @description: 下降电梯状态类
     * @author: black tea
     * @date: 2023/3/26 15:28
     */
    @Slf4j
    @Component
    public class DownState implements ElevatorState {
    
        @Override
        public void openDoor() {
            log.info("电梯正在上行中,无法打开门!");
        }
    
        @Override
        public void closeDoor() {
            log.info("电梯正在上行中,无法关闭门!");
        }
    
        @Override
        public void goUp() {
            log.info("电梯转为上行状态!");
        }
    
    }
  2. 定义电梯上下文类(ElevatorContext):

    java
    /**
     * @description: 电梯上下文类
     * @author: black tea
     * @date: 2023/3/26 15:29
     */
    @Slf4j
    @Component
    public class ElevatorContext {
    
        private final Map<String, ElevatorState> stateMap;
    
        private ElevatorState state;
    
        public ElevatorContext(Map<String, ElevatorState> stateMap) {
            this.stateMap = stateMap;
            // 默认状态为closeState
            setState(CloseState.class);
        }
    
        public void setState(Class<? extends ElevatorState> cls) {
            String simpleName = cls.getSimpleName();
            String mapKey = StrUtil.lowerFirst(simpleName);
            Optional<ElevatorState> stateOptional =
                    Optional.ofNullable(stateMap.get(mapKey));
            this.state = stateOptional
                    .orElseThrow(()-> ExceptionUtil.wrapRuntime("找不到具体的" + simpleName + "电梯状态类"));
        }
    
        public void open() {
            state.openDoor();
            setState(OpenState.class);
        }
    
        public void close() {
            state.closeDoor();
            setState(CloseState.class);
        }
    
        public void up() {
            state.goUp();
            setState(UpState.class);
        }
    
        public void down() {
            state.goDown();
            setState(DownState.class);
        }
    }

    注入状态对象,使用@Autowired注解,使用默认状态为closeState。

  3. 单元测试使用:

    java
     	@Autowired
        private ElevatorContext elevatorContext;
    
        /**
         * 测试 状态模式
         */
        @Test
        void testStatePattern(){
            log.debug("测试 状态模式");
    
            elevatorContext.open();
            elevatorContext.close();
            elevatorContext.up();
            elevatorContext.down();
        }

  GitHub学习地址:java-design-patterns/localization/zh/state at master · iluwatar/java-design-patterns (github.com)

策略模式(Strategy Pattern)

  策略模式是一种行为型设计模式,它定义了一组算法,将每个算法封装在独立的类中,并使它们可以互换。该模式使得算法可以独立于使用它的客户端而变化,从而提高了灵活性和可维护性。

  具体而言,策略模式将算法的实现从客户端代码中分离出来,使得客户端代码只需要知道如何使用这些算法即可,而不需要关心具体的实现细节。这样,当需要改变算法时,只需要修改相应的策略类即可,而不需要修改客户端代码。同时,策略模式还支持动态切换算法,使得客户端可以在运行时选择不同的算法,从而实现更加灵活的行为。

  总之,策略模式通过将算法的实现与客户端代码分离,提高了代码的可维护性和灵活性,是一种常用的设计模式。

如何使用

  支付方式的策略案例。

  1. 定义一个支付策略接口和实现两个支付策略类(AliPayStrategy和WeChatPayStrategy):

    java
    /**
     * @description: 支付策略接口
     * @author: black tea
     * @date: 2023/3/26 17:57
     */
    public interface PayStrategy {
    
        /**
         * 支付
         * @param amount 金额
         */
        void pay(BigDecimal amount);
    }
    
    /**
     * @description: 支付宝支付策略
     * @author: black tea
     * @date: 2023/3/26 17:58
     */
    @Component
    @Slf4j
    public class AliPayStrategy implements  PayStrategy {
    
        @Override
        public void pay(BigDecimal amount) {
            log.info("支付宝支付,金额{}", amount);
        }
    }
    
    /**
     * @description: 微信支付策略
     * @author: black tea
     * @date: 2023/3/26 17:59
     */
    @Slf4j
    @Component
    public class WeChatPayStrategy implements PayStrategy {
    
        @Override
        public void pay(BigDecimal amount) {
            log.info("微信支付,金额{}", amount);
        }
    }
  2. 创建一个支付上下文(PayContext):

    java
    /**
     * @description: 支付上下文
     * @author: black tea
     * @date: 2023/3/26 18:00
     */
    @Component
    @RequiredArgsConstructor
    public class PayContext {
    
        private final Map<String, PayStrategy> payStrategyMap;
    
        private PayStrategy payStrategy;
    
        public void setPayStrategy(String payType) {
            Optional<PayStrategy> payStrategyOptional = getPayStrategyByKey(payType);
            this.payStrategy = payStrategyOptional
                    .orElseThrow(()-> ExceptionUtil.wrapRuntime("找不到具体类型为【" + payType + "】的支付策略类"));
        }
    
        public void setPayStrategy(Class<? extends PayStrategy> cls) {
            String simpleName = cls.getSimpleName();
            String mapKey = StrUtil.lowerFirst(simpleName);
            Optional<PayStrategy> payStrategyOptional = getPayStrategyByKey(mapKey);
            this.payStrategy = payStrategyOptional
                    .orElseThrow(()-> ExceptionUtil.wrapRuntime("找不到具体的【" + simpleName + "】支付策略类"));
        }
    
        private Optional<PayStrategy> getPayStrategyByKey(String mapKey){
            return Optional.ofNullable(payStrategyMap.get(mapKey));
        }
    
        public void pay(BigDecimal amount){
            payStrategy.pay(amount);
        }
    
    }
  3. 单元测试使用:

    java
    	@Autowired
        private PayContext payContext;
    
        /**
         * 测试 策略模式
         */
        @Test
        void testStrategyPattern(){
            log.debug("测试 策略模式");
    
            BigDecimal amount = new BigDecimal("99.90");
            // 支付宝支付
            payContext.setPayStrategy("aliPayStrategy");
            payContext.pay(amount);
            // 微信支付
            payContext.setPayStrategy(WeChatPayStrategy.class);
            payContext.pay(amount);
        }

  GitHub学习地址:java-design-patterns/localization/zh/strategy at master · iluwatar/java-design-patterns (github.com)

模板方法模式(Template Method Pattern)

  模板方法模式是一种行为型设计模式,它定义了一个算法的骨架,将一些步骤的实现延迟到子类中。具体而言,模板方法模式通过定义一个抽象类,其中包含了一个或多个抽象方法和具体方法,这些方法组成了一个算法的骨架。其中,抽象方法由子类实现,具体方法则在抽象类中实现。

  模板方法模式的核心思想是“由父类控制子类”,即将算法的控制权交给父类,由父类定义算法的骨架,子类只需要实现其中的具体细节。这样可以避免代码重复,提高代码的复用性和可维护性。同时,模板方法模式还支持钩子方法,使得子类可以影响算法的执行过程,从而实现更加灵活的行为。

  总之,模板方法模式通过定义一个算法的骨架和具体实现,将算法的控制权交给父类,提高了代码的复用性和可维护性,是一种常用的设计模式。

如何使用

  这是一个付款的例子。

  1. 创建一个抽象类作为模板类,定义模板方法和抽象方法:

    java
    /**
     * @description: 付款模板类
     * @author: black tea
     * @date: 2023/3/26 21:28
     */
    @Slf4j
    public abstract class PaymentTemplate {
    
        public void makePayment() {
            validate();
            processPayment();
            sendNotification();
        }
    
        public abstract void validate();
    
        public abstract void processPayment();
    
        public void sendNotification() {
            log.info("已成功付款。");
        }
    }
  2. 创建具体的实现类,继承模板类,并实现抽象方法。

    java
    /**
     * @description: 银行卡付款
     * @author: black tea
     * @date: 2023/3/26 21:49
     */
    @Component
    @Slf4j
    public class CreditCardPayment extends PaymentTemplate {
    
        @Override
        public void validate() {
            log.info("正在验证信用卡信息...");
        }
    
        @Override
        public void processPayment() {
            log.info("处理信用卡付款...");
        }
    }
    
    /**
     * @description: PayPal支付
     * @author: black tea
     * @date: 2023/3/26 21:53
     */
    @Slf4j
    @Component
    public class PayPalPayment extends PaymentTemplate {
    
        @Override
        public void validate() {
            log.info("正在验证PayPal帐户信息...");
        }
    
        @Override
        public void processPayment() {
            log.info("处理PayPal付款...");
        }
    }
  3. 单元测试使用:

    java
      	@Autowired
        private List<PaymentTemplate> paymentTemplates;
    
        /**
         * 测试 模板方法策略
         */
        @Test
        void testTemplateMethodPattern(){
            log.debug("测试 模板方法策略");
    
            paymentTemplates.forEach(PaymentTemplate::makePayment);
        }

    这里我之所以使用List<PaymentTemplate>,是因为存在两个实现类的bean,所以使用集合进行接收,你也可以使用Map或是单个注入。

  GitHub学习地址:java-design-patterns/localization/zh/template-method at master · iluwatar/java-design-patterns (github.com)

访问者模式(Visitor Pattern)

  访问者模式是一种行为型设计模式,它将算法与对象结构分离开来,使得可以在不改变对象结构的前提下定义新的操作。具体而言,访问者模式定义了一个访问者类,其中包含了一系列访问方法,用于对不同类型的对象进行访问和操作。同时,访问者模式还定义了一个对象结构,其中包含了一组元素,这些元素可以接受访问者的访问。

  访问者模式的核心思想是“双重分派”,即在访问者模式中,访问者先选择合适的访问方法,然后再根据被访问的对象的类型选择合适的处理方式。这样可以使得算法与对象结构分离,从而实现更加灵活的行为。

  访问者模式的优点是可以在不修改被访问者的情况下增加新的操作,同时也可以将相关的操作集中在访问者中,使得代码更加清晰和可维护。缺点是增加新的被访问者或者新的访问者都需要修改代码,会增加代码的复杂度。

  总之,访问者模式通过定义一个访问者类和一个对象结构,将算法与对象结构分离,提高了代码的灵活性和可扩展性,是一种常用的设计模式。

如何使用

  处理一个订单结构对象的打印和计算总价的业务逻辑。

  1. 首先,我们定义一个OrderVisitor接口,它包含了访问不同类型订单项的方法:

    java
    /**
     * @description: 订单访问者接口
     * @author: black tea
     * @date: 2023/3/26 22:38
     */
    public interface OrderVisitor {
    
        /**
         * 访问 产品订单项
         * @param item 产品订单项
         */
        void visit(ProductOrderItem item);
    
        /**
         * 访问 折扣订单项目
         * @param item 折扣订单项目
         */
        void visit(DiscountOrderItem item);
    
        /**
         * 获取结果
         * @return Object
         */
        Object getResult();
    }
  2. 接下来,我们定义两个订单项类ProductOrderItem和DiscountOrderItem,它们都实现了一个accept方法,接受一个OrderVisitor对象:

    java
    /**
     * @description: 订单项目类
     * @author: black tea
     * @date: 2023/3/26 22:39
     */
    public interface OrderItem {
    
        /**
         * 接受 订单访问
         * @param visitor 订单访问对象
         */
        void accept(OrderVisitor visitor);
    }
    
    /**
     * @description: 产品订单项类
     * @author: black tea
     * @date: 2023/3/26 22:39
     */
    @Data
    public class ProductOrderItem implements OrderItem {
    
        /**
         * 产品名称
         */
        private final String name;
    
        /**
         * 产品单价
         */
        private final BigDecimal price;
    
        /**
         * 产品数量
         */
        private final int quantity;
    
        @Override
        public void accept(OrderVisitor visitor) {
            visitor.visit(this);
        }
    }
    
    /**
     * @description: 折扣订单项目
     * @author: black tea
     * @date: 2023/3/26 22:41
     */
    @Data
    public class DiscountOrderItem implements OrderItem {
    
        /**
         * 折扣,例如88折,填88
         */
        private final BigDecimal discount;
    
        @Override
        public void accept(OrderVisitor visitor) {
            visitor.visit(this);
        }
    }
  3. 然后,我们实现具体的OrderVisitor类,它实现了OrderVisitor接口,并定义了visit方法的具体实现:

    java
    /**
     * @description: 订单打印访问者类
     * @author: black tea
     * @date: 2023/3/26 22:42
     */
    public class OrderPrintVisitor implements OrderVisitor {
    
        private final StringBuilder builder = new StringBuilder();
    
        @Override
        public void visit(ProductOrderItem item) {
            builder.append(item.getQuantity()).append(" x ")
                    .append(item.getName()).append(": ")
                    .append(item.getPrice()).append("\n");
        }
    
        @Override
        public void visit(DiscountOrderItem item) {
            builder.append("折扣: ").append(item.getDiscount()).append("%\n");
        }
    
        @Override
        public String getResult() {
            return builder.toString();
        }
    }
    
    /**
     * @description: 订单价格访问者类
     * @author: black tea
     * @date: 2023/3/26 22:44
     */
    public class OrderPriceVisitor implements OrderVisitor {
    
        private BigDecimal totalPrice = BigDecimal.ZERO;
    
        @Override
        public void visit(ProductOrderItem item) {
            totalPrice = totalPrice.add(
                    item.getPrice()
                    .multiply(new BigDecimal(item.getQuantity()))
            );
        }
    
        @Override
        public void visit(DiscountOrderItem item) {
            BigDecimal divide = item.getDiscount().divide(new BigDecimal("100"), 2, BigDecimal.ROUND_DOWN);
            totalPrice = totalPrice.multiply(divide);
            totalPrice = totalPrice.setScale(2, BigDecimal.ROUND_DOWN);
        }
    
        @Override
        public BigDecimal getResult() {
            return totalPrice;
        }
    }
  4. 最后,我们定义一个OrderStructure类,它包含了一个OrderItem列表,并提供了一个accept方法,接受一个OrderVisitor对象:

    java
    /**
     * @description: 订单结构类
     * @author: black tea
     * @date: 2023/3/26 22:47
     */
    public class OrderStructure {
    
        private final List<OrderItem> items = new ArrayList<>();
    
        public void addItem(OrderItem item) {
            items.add(item);
        }
    
        public void accept(OrderVisitor visitor) {
            for (OrderItem item : items) {
                item.accept(visitor);
            }
        }
    }
  5. 单元测试,处理订单对象的打印和计算总价的业务逻辑,例如:

    java
     /**
         * 测试 访问者模式
         */
        @Test
        void testVisitorPattern(){
            log.debug("测试 访问者模式");
    
            OrderStructure order = new OrderStructure();
            order.addItem(new ProductOrderItem("手机", new BigDecimal("888.213"), 1));
            order.addItem(new DiscountOrderItem(new BigDecimal("88")));
    
            OrderPrintVisitor printVisitor = new OrderPrintVisitor();
            order.accept(printVisitor);
            String printResult = printVisitor.getResult();
    
            OrderPriceVisitor priceVisitor = new OrderPriceVisitor();
            order.accept(priceVisitor);
            BigDecimal totalPrice = priceVisitor.getResult();
    
            log.info(printResult + "总价: " + totalPrice);
    
        }

  GitHub学习地址:java-design-patterns/localization/zh/visitor at master · iluwatar/java-design-patterns (github.com)