跳至主要內容

行为型设计模式

red-velet原创计算机基础设计模式约 30660 字大约 102 分钟

行为型设计模式关注对象之间行为(通信和协作方式),以实现不同对象之间的交互和任务分配。这些模式涉及到对象之间的算法、责任分配和消息传递等行为。行为型设计模式的核心目的是提供一种灵活、可扩展和可维护的方式来管理对象之间的交互和行为。

一、行为型设计模式

行为型设计模式关注对象之间行为(通信和协作方式),以实现不同对象之间的交互和任务分配。这些模式涉及到对象之间的算法、责任分配和消息传递等行为。行为型设计模式的核心目的是提供一种灵活、可扩展和可维护的方式来管理对象之间的交互和行为。

总结:复用和拓展

一、观察者设计模式

1、介绍

观察者设计模式(Observer Pattern)是一种行为型设计模式,它建立了对象之间的一对多依赖关系,使得当一个对象的状态发生变化时,其相关依赖对象都能得到通知并自动更新。这种模式被广泛应用于事件驱动系统和发布-订阅系统中。

2、核心思想

观察者设计模式的核心思想:

  • 将对象之间的依赖关系解耦,使得主题(被观察者)和观察者之间可以独立变化。
  • 主题维护一份观察者列表,并在状态变化时通知观察者,观察者接收到通知后进行相应的处理。

3、组成和基本流程

观察者设计模式由以下几个关键组件组成:

  • Subject(主题):也称为被观察者或发布者,维护一份观察者列表,并提供方法用于添加(注册)、删除和通知观察者。
  • Observer(观察者):也称为订阅者,定义了接收和处理主题通知的方法。
  • ConcreteSubject(具体主题):具体主题是主题的具体实现类,负责维护具体的状态并在状态变化时通知观察者。
  • ConcreteObserver(具体观察者):具体观察者是观察者的具体实现类,实现了接收和处理主题通知的方法。

观察者设计模式的使用基本流程:

  1. 观察者通过将自身注册到主题中,成为主题的观察者。
  2. 当主题的状态发生变化时,它会遍历观察者列表,并调用每个观察者的通知方法。
  3. 每个观察者接收到通知后,执行相应的更新操作。

4、使用场景和具体案例

观察者设计模式适用于以下场景:

  • 当一个对象的变化需要影响其他对象,但你又不希望这些对象紧密耦合在一起时。
  • 当一个对象的状态变化会导致其他对象的行为变化,而且你希望能够动态地将对象加入或移除。

具体案例:

一个常见的业务场景是网络聊天的群聊功能。假设我们有一个网络聊天室应用程序,用户可以加入聊天群,并与其他用户进行实时聊天。

以下是一个简化的示例代码,展示了如何使用观察者设计模式实现网络聊天室:

在下列示例中:

  • 我们创建了一个名为ChatGroup的主题接口,其中定义了加入聊天群、退出聊天群和通知所有用户的方法。
  • 具体主题类QQGroup实现了该接口,并维护了一个用户列表。
  • 观察者接口ChatUser定义了接收消息和发送消息的方法。
  • 具体观察者类QQUser实现了该接口,每个用户都可以接收消息并发送消息给聊天室中的其他用户。
  • 在主类中,我们创建了一个QQGroup实例作为聊天室。然后,创建了几个QQUser实例,代表聊天室中的用户。接着,将这些用户加入聊天室,并执行了一次消息发送操作。
    • jack发送消息时,聊天室会调用notifyAllUser方法通知其他用户接收消息。每个用户收到消息后,会打印出相应的消息内容。

这个例子演示了观察者设计模式在网络聊天室中的应用。通过使用观察者模式,网络聊天室实现了用户之间的解耦,聊天室中的用户可以实时接收和发送消息,实现了群聊的功能。

// 主题接口
public interface ChatGroup {
    void joinChatGroup(ChatUser user);
    void exitChatGroup(ChatUser user);
    void notifyAllUser(String msg);
}

// 具体主题类
public class QQGroup implements ChatGroup {
    private List<ChatUser> users = new ArrayList<>();

    @Override
    public void joinChatGroup(ChatUser user) {
        users.add(user);
    }

    @Override
    public void exitChatGroup(ChatUser user) {
        users.remove(user);
    }

    @Override
    public void notifyAllUser(String msg) {
        for (ChatUser user : users) {
            user.receiverMsg(msg);
        }
    }
}

// 观察者接口
public interface ChatUser {
    void receiverMsg(String msg);

    void sendMsg(String msg);
}

// 具体观察者类
class QQUser implements ChatUser {
    private String name;
    private ChatGroup chatRoom;

    public QQUser(String name, ChatGroup chatRoom) {
        this.name = name;
        this.chatRoom = chatRoom;
    }

    @Override
    public void receiverMsg(String msg) {
        System.out.println(name + " 收到消息:" + msg);
    }

    @Override
    public void sendMsg(String msg) {
        System.out.println(name + " 发送消息:" + msg);
        //通知其他人
        chatRoom.notifyAllUser(msg);
    }
}
// 示例代码的主类
public class Main {
    public static void main(String[] args) {
        //创建QQ群
        ChatGroup chatGroup = new QQGroup();

        //宿舍四个室友创建qq
        ChatUser jack = new QQUser("jack", chatGroup);
        ChatUser rose = new QQUser("rose", chatGroup);
        ChatUser tom = new QQUser("tom", chatGroup);
        ChatUser jerry = new QQUser("jerry", chatGroup);

        //加入群聊
        chatGroup.joinChatGroup(jack);
        chatGroup.joinChatGroup(rose);
        chatGroup.joinChatGroup(tom);
        chatGroup.joinChatGroup(jerry);

        //今天晚上打算出去吃饭,jack问了大家晚上吃什么
        jack.sendMsg("今天晚上吃什么");
    }
}

5、发布订阅模式

发布-订阅模式是一种行为型设计模式,用于实现对象间的松耦合通信。在该模式中,存在一个消息总线或事件系统作为中介,发布者(Publisher)将消息发布到消息总线,而订阅者(Subscriber)订阅感兴趣的消息主题或事件类型。当有新的消息或事件发生时,消息总线会将消息传递给对应的订阅者

该模式的核心思想是解耦发布者和订阅者之间的直接依赖关系。发布者不需要知道订阅者的存在,而订阅者也不需要知道发布者的细节。通过引入消息总线或事件系统作为中介,发布者和订阅者之间的交互通过消息的发布和订阅来完成。

发布-订阅模式广泛应用于各种场景,例如消息队列系统、事件驱动系统、日志记录系统等。它提供了一种灵活、可扩展的通信机制,有助于构建松耦合、可维护的软件系统。

下列例子演示了一个简单的发布-订阅模式,明星发布消息,关注了明星的追星人可以接收到通过总线发送来的消息:

代码说明介绍:

上述代码是一个基于事件的发布-订阅模式的实现,以一个明星发布要开全球音乐巡演会的例子来说明。

  • 接口 Subscribe 定义了订阅者的行为,其中 onEvent() 方法用于处理收到的事件。
  • JackSubscribeRoseSubscribe 是具体的订阅者实现类,它们实现了 Subscribe 接口,并根据收到的事件进行相应的处理。
  • EventBus 是事件总线类,用于维护事件和订阅者之间的关系。它包含了注册订阅者、移除订阅者和发布事件的方法。
  • StartTopic 是发布者类,它拥有一个事件总线实例,并通过调用 worldConcert() 方法发布名为 "worldConcert" 的事件。

代码解析:

  1. main() 方法中,创建了 JackSubscribeRoseSubscribe 的实例,并创建了一个 EventBus 的实例。
  2. 使用 EventBusregisterSubscribe() 方法注册了订阅者,将它们与事件类型 "worldConcert" 关联起来。
  3. 创建了 StartTopic 的实例,并将 EventBus 实例传递给它。
  4. 调用 startTopicworldConcert() 方法发布了事件。
  5. EventBus 接收到事件后,根据事件类型找到相应的订阅者列表,并逐个调用订阅者的 onEvent() 方法处理事件。
  6. JackSubscribeRoseSubscribe 分别接收到事件,并输出相应的消息。

这个例子模拟了一个音乐演唱会的情景,当 StartTopicworldConcert() 方法被调用时,会发布一个 "worldConcert" 的事件,然后 JackSubscribeRoseSubscribe 作为订阅者接收到这个事件并进行处理,输出相应的消息。

通过发布-订阅模式,发布者和订阅者之间的解耦,使得发布

interface Subscribe {
    void onEvent(Map<String, Object> eventContext);
}
class JackSubscribe implements Subscribe {
    private String name;

    public JackSubscribe(String name) {
        this.name = name;
    }

    @Override
    public void onEvent(Map<String, Object> eventContext) {
        System.out.println(name + "收到发布订阅的消息:" + eventContext.get("tmp"));
    }
}
class RoseSubscribe implements Subscribe {
    private String name;

    public RoseSubscribe(String name) {
        this.name = name;
    }

    @Override
    public void onEvent(Map<String, Object> eventContext) {
        System.out.println(name + "收到发布订阅的消息:" + eventContext.get("tmp"));

    }
}
class EventBus {
    //维护事件和订阅者
    private Map<String, List<Subscribe>> subscribes = new HashMap<>(8);

    public void registerSubscribe(String eventType, Subscribe subscribe) {
        List<Subscribe> subscribeList = subscribes.get(eventType);
        if (subscribeList == null) {
            List<Subscribe> list = new ArrayList<>();
            //注册
            list.add(subscribe);
            subscribes.put(eventType, list);
        } else {
            //注册
            subscribeList.add(subscribe);
        }
    }

    public void removeSubscribe(String eventType, Subscribe subscribe) {
        List<Subscribe> subscribeList = subscribes.get(eventType);
        if (subscribeList != null) {
            subscribeList.remove(subscribe);
        }
    }

    public void publishEvent(String eventType, Map<String, Object> eventContext) {
        List<Subscribe> subscribeList = subscribes.get(eventType);
        for (Subscribe subscribe : subscribeList) {
            subscribe.onEvent(eventContext);
        }
    }
}
class StartTopic {
    private String name;
    private EventBus eventBus;

    public StartTopic(String name, EventBus eventBus) {
        this.name = name;
        this.eventBus = eventBus;
    }

    public void worldConcert(String name) {
        this.name = name;
        HashMap<String, Object> context = new HashMap<>();
        context.put("tmp", name + "要开全球巡回演唱会了!!!");
        eventBus.publishEvent("worldConcert", context);
    }
}
public class Main {
    public static void main(String[] args) {
        JackSubscribe jackSubscribe = new JackSubscribe("jack");
        RoseSubscribe roseSubscribe = new RoseSubscribe("rose");

        EventBus eventBus = new EventBus();
        eventBus.registerSubscribe("worldConcert", jackSubscribe);
        eventBus.registerSubscribe("worldConcert", roseSubscribe);

        StartTopic startTopic = new StartTopic("redvelet", eventBus);
        //发布事件
        startTopic.worldConcert("redvelet");
    }
}

6、观察者的进阶使用

异步非阻塞型:

前面例子的实现方式,是一种同步阻塞的实现方式。

  • 观察者和被观察者代码在同一个线程内执行,被观察者一直阻塞,直到所有的观察者代码都执行完成之后,才执行后续的代码。

当观察者模式需要在异步非阻塞的场景中使用时,可以通过使用线程池来实现异步通知。下面是一个经过格式化的示例代码:

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

// 观察者接口
interface Observer {
    void update(String message);
}

// 被观察者接口
interface Observable {
    void addObserver(Observer observer);
    void removeObserver(Observer observer);
    void notifyObservers(String message);
}

// 具体的被观察者类
class Subject implements Observable {
    private List<Observer> observers;
    private ExecutorService executorService;

    public Subject() {
        observers = new ArrayList<>();
        executorService = Executors.newCachedThreadPool();
    }

    @Override
    public void addObserver(Observer observer) {
        observers.add(observer);
    }

    @Override
    public void removeObserver(Observer observer) {
        observers.remove(observer);
    }

    @Override
    public void notifyObservers(String message) {
        for (Observer observer : observers) {
            executorService.submit(() -> observer.update(message));
        }
    }

    public void setMessage(String message) {
        notifyObservers(message);
    }
}

// 具体的观察者类
class ConcreteObserver implements Observer {
    private String name;

    public ConcreteObserver(String name) {
        this.name = name;
    }

    @Override
    public void update(String message) {
        System.out.println(name + " received message: " + message);
    }
}

public class Main {
    public static void main(String[] args) {
        Subject subject = new Subject();
        ConcreteObserver observer1 = new ConcreteObserver("Observer 1");
        ConcreteObserver observer2 = new ConcreteObserver("Observer 2");
        ConcreteObserver observer3 = new ConcreteObserver("Observer 3");

        subject.addObserver(observer1);
        subject.addObserver(observer2);
        subject.addObserver(observer3);

        subject.setMessage("Hello, observers!");

        // 等待异步任务完成
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

在这个示例中,我们使用了线程池 ExecutorService 来实现异步非阻塞的通知。每个观察者的更新操作都将作为一个任务提交给线程池,并在不同的线程中异步执行。这样可以确保性能敏感的场景不会因为观察者的通知而阻塞。

跨进程通信型:

前面的两个场景,不管是同步阻塞实现方式还是异步非阻塞实现方式,都是进程内的实现方式。

如果用户注册成功之后,我们需要发送用户信息给大数据征信系统, 而大数据征信系统是一个独立的系统,跟它之间的交互是跨不同进程的,那如何实现 一个跨进程的观察者模式呢?

  • 如果大数据征信系统提供了发送用户注册信息的 RPC 接口,我们仍然可以沿用之前 的实现思路,在 notifyObservers() 函数中调用 RPC 接口来发送数据。
  • 另一种就是基于消息队列(Message Queue, 比如 ActiveMQ)来实现。 当然,这种实现方式也有弊端,那就是需要引入一个新的系统(消息队列),增加了维护成本。不过,它的好处也非常明显。在原来的实现方式中,观察者需要注册到被观察者中,被观察者需要依次遍历观察者来发送消息。而基于消息队列的实现方式,被观察者和观察者解耦更加彻底,两部分的耦合更小。被观察者完全不感知观察者,同理,观察者也完全不感知被观察者。被观察者只管发送消息到消息队列,观察者只管从消息队列中读取消息来执行相应的逻辑。

7、总结

观察者设计模式的优点包括:

  • 解耦性:主题和观察者之间的松散耦合使得它们可以独立变化,增加了系统的灵活性和可扩展性。
  • 可维护性:通过使用观察者模式,可以更容易地维护和理解对象之间的关系。
  • 可重用性:主题和观察者可以在不同的场景中被重用,增加了代码的复用性。

观察者设计模式的缺点包括:

  • 如果观察者较多或处理逻辑较复杂,可能导致性能问题。
  • 观察者之间的通信是单向的,观察者之间无法直接通信,需要通过主题进行中转。

总的来说,观察者设计模式是一种非常有用的模式,它能够实现对象之间的松耦合、动态通信和状态同步,提高系统的可维护性和灵活性。通过合理的设计和使用,可以在事件驱动的系统中发挥重要作用。

二、模板设计模式

1、介绍

模板方法设计模式(Template Method Pattern),它定义了一个操作中的算法框架,将一些步骤的具体实现延迟到子类中。该模式允许在不改变算法结构的情况下,通过子类实现具体步骤,从而提供了一种代码复用和扩展的方法。

重点内容:

  • 定义了一个模板方法,其中包含了算法的基本骨架,但某些步骤的具体实现由子类决定。
  • 子类可以根据需要重写父类中的方法,以提供特定的实现。
    • 不变的放在模板里,变化的放在子类实现

2、核心思想

模板方法设计模式的核心思想在于定义一个抽象类,其中包含了算法的模板(即模板方法)。模板方法中会调用多个抽象方法,这些方法由子类负责实现。通过这种方式,可以确保算法的结构稳定,同时在子类中提供具体步骤的灵活实现。

3、组成和基本流程

模板方法设计模式主要由以下组成部分构成:

  1. AbstractClass(抽象类):定义了一个模板方法,其中包含了算法的基本骨架,以及若干抽象方法,这些抽象方法由子类负责实现。

  2. ConcreteClass(具体类):继承自抽象类,实现了在抽象类中定义的抽象方法,从而提供了对算法中特定步骤的具体实现。

基本流程如下:

  1. 定义抽象类,其中包含了一个模板方法和若干抽象方法。
  2. 子类继承抽象类,并实现其中的抽象方法。
  3. 在客户端中,通过实例化具体类对象,调用模板方法执行算法,具体步骤由子类实现。

示例代码:

public abstract class AbstractClass {
    // 模板方法,定义了算法的基本骨架
    public final void templateMethod() {
        step1();
        step2();
        step3();
    }

    // 抽象方法,由子类实现
    protected abstract void step1();
    protected abstract void step2();
    protected abstract void step3();
}

public class ConcreteClass extends AbstractClass {
    // 实现抽象方法
    protected void step1() {
        // 具体步骤实现
    }

    protected void step2() {
        // 具体步骤实现
    }

    protected void step3() {
        // 具体步骤实现
    }
}

4、使用场景和具体案例

模板方法设计模式适用于以下情况:

  • 当算法中的结构相对稳定,但其中某些步骤的具体实现可能会变化时,可以进行复用的功能。
  • 当希望在不改变算法整体结构的情况下,能够灵活地扩展和修改其中某些步骤的实现,可以进行拓展的功能。

当结合具体业务场景时,模板方法设计模式可以应用于以下情况:

  1. 数据导入处理:假设我们有一个数据导入系统,用于从不同来源(例如Excel文件、CSV文件、数据库等)导入数据,并将其存储到数据库中。整个导入过程有一定的稳定结构,如打开数据源、解析数据、数据校验、数据存储等。但不同的数据来源可能需要不同的数据解析和校验逻辑。我们可以将整个数据导入过程定义为一个模板方法,其中通用的处理步骤在抽象类中实现,而数据解析和校验的具体实现则由子类(不同数据来源对应的子类)来完成。

  2. 订单处理:考虑一个电商平台的订单处理系统,其中订单的创建、支付、物流等过程有一些共性步骤,如生成订单号、验证支付、发货等。但不同类型的订单(普通订单、预定订单、退货订单)可能在这些共性步骤上有所不同,比如退货订单需要额外进行退款处理。我们可以将订单处理过程抽象为模板方法,将共性步骤放在抽象类中实现,然后每种订单类型对应一个具体子类,实现特定类型订单的处理逻辑。

  3. 游戏关卡设计:在游戏开发中,不同关卡的设计可能有一些相似的元素,如敌人生成、任务目标、胜利条件等。但每个关卡的地图、敌人种类和数量、任务难度等可能会有所不同。我们可以将关卡设计抽象为模板方法,将共性的关卡元素放在抽象类中实现,然后每个具体关卡对应一个子类,实现特定关卡的设计。

  4. 流程审批系统:在企业中,审批流程通常有一定的通用结构,如提交申请、审批处理、审核记录等。不同类型的申请(请假申请、报销申请、加班申请等)可能在审批处理上有所不同,比如请假申请需要额外的日期计算,报销申请需要金额核对。我们可以将审批流程抽象为模板方法,将通用的审批步骤放在抽象类中实现,然后每种申请类型对应一个具体子类,实现特定申请的审批逻辑。

这些场景中,模板方法设计模式能够将稳定的处理流程与特定实现解耦,使得代码更加灵活、易于维护和扩展。同时,通过在抽象类中定义模板方法,可以促使开发人员在子类中实现特定步骤,提高代码的一致性和可读性。

4.1 复用

具体案例1:InputStream

在Java的标准库中,有一个经典的例子是java.io.InputStream类,它是所有输入流的抽象基类。这个类定义了一个模板方法read(),用于读取输入流中的数据。下面是一个简化的示例:

import java.io.IOException;

public abstract class InputStream {
    // 模板方法,定义了读取输入流中的数据的基本骨架
    public final void readData() throws IOException {
        open();
        while (!isEndOfStream()) {
            int data = readByte();
            process(data);
        }
        close();
    }

    // 抽象方法,由子类实现具体的输入流打开逻辑
    protected abstract void open() throws IOException;

    // 抽象方法,由子类实现具体的数据读取逻辑
    protected abstract int readByte() throws IOException;

    // 抽象方法,由子类实现具体的数据处理逻辑
    protected abstract void process(int data);

    // 具体方法,判断是否达到输入流的末尾
    protected boolean isEndOfStream() {
        // 具体判断逻辑
    }

    // 具体方法,由子类实现具体的输入流关闭逻辑
    protected void close() throws IOException {
        // 具体关闭逻辑
    }
}

// 具体类:FileInputStream
public class FileInputStream extends InputStream {
    // 实现抽象方法:打开文件输入流
    protected void open() throws IOException {
        // 具体的文件打开逻辑
    }

    // 实现抽象方法:读取文件中的字节数据
    protected int readByte() throws IOException {
        // 具体的读取字节逻辑
    }

    // 实现抽象方法:处理读取到的字节数据
    protected void process(int data) {
        // 具体的处理逻辑
    }

    // 重写父类方法:判断是否达到文件末尾
    protected boolean isEndOfStream() {
        // 具体判断文件末尾的逻辑
    }

    // 实现抽象方法:关闭文件输入流
    protected void close() throws IOException {
        // 具体的文件关闭逻辑
    }
}

在这个例子中,InputStream类是一个抽象类,它定义了一个模板方法readData(),该方法定义了读取输入流中数据的基本流程(打开流、读取数据、处理数据、关闭流)。具体的实现细节则交给子类去实现,子类必须提供具体的输入流的打开、读取、处理和关闭的逻辑。这样,我们可以通过继承抽象类并实现具体的方法,来创建不同类型的输入流,例如FileInputStreamByteArrayInputStream等,实现了具体的数据读取逻辑。

希望这个例子能够更好地展示模板方法设计模式的应用。再次向你表示抱歉,并感谢你的理解!

具体案例2:生成报告模板

假设我们有一个报告生成系统,其中包含不同类型的报告,如PDF报告、Word报告等。这些报告的生成过程有一定的共性,比如首先需要进行数据查询,然后根据查询结果填充报告内容,并最终导出为指定格式的文件。

使用模板方法设计模式,我们可以定义一个报告生成的抽象类,其中包含一个模板方法generateReport(),它定义了生成报告的基本骨架。然后,我们可以创建不同的具体报告类,如PDFReportWordReport,它们继承自抽象类,并实现其中的抽象方法。

// 抽象类:报告生成模板
public abstract class ReportTemplate {
    // 模板方法,定义报告生成的基本骨架
    public final void generateReport() {
        fetchData();
        fillContent();
        exportReport();
    }

    // 抽象方法,由子类实现具体的数据查询
    protected abstract void fetchData();

    // 抽象方法,由子类实现具体的报告内容填充
    protected abstract void fillContent();

    // 抽象方法,由子类实现具体的报告导出
    protected abstract void exportReport();
}

// 具体类:PDF报告
public class PDFReport extends ReportTemplate {
    protected void fetchData() {
        // 实现PDF报告的数据查询逻辑
    }

    protected void fillContent() {
        // 实现PDF报告的内容填充逻辑
    }

    protected void exportReport() {
        // 实现PDF报告的导出逻辑
    }
}

// 具体类:Word报告
public class WordReport extends ReportTemplate {
    protected void fetchData() {
        // 实现Word报告的数据查询逻辑
    }

    protected void fillContent() {
        // 实现Word报告的内容填充逻辑
    }

    protected void exportReport() {
        // 实现Word报告的导出逻辑
    }
}

通过这种方式,我们实现了报告生成过程的复用,因为不同类型的报告共用了相同的生成算法框架,但其中的具体步骤由子类实现,使得我们可以灵活地扩展和修改报告生成的某些步骤。

4.2 拓展

具体案例1:Java Servelt

当扩展到处理HTTP的GET、POST和PUT请求时,我们可以在抽象类MyServlet中增加对doPost()doPut()方法的支持。这样,我们可以在模板方法中处理通用的逻辑,并在具体的子类中实现特定请求类型的处理。

这段代码展示了一个简化的Java Servlet的示例。

  1. 定义了一个抽象类MyServlet,它继承自HttpServlet,这是Java Servlet的基类。MyServlet中有三个模板方法:doGet()doPost()doPut(),它们分别处理HTTP的GET、POST和PUT请求,并调用相应的抽象方法处理具体的请求逻辑。
  2. 抽象类中定义了三个抽象方法:handleGetRequest()handlePostRequest()handlePutRequest(),这些方法是由子类来实现的,用于处理特定类型的HTTP请求的逻辑。
  3. 创建了一个具体子类MyServletImpl,它继承自抽象类MyServlet。在MyServletImpl中,我们实现了父类中定义的三个抽象方法,分别处理GET、POST和PUT请求的逻辑。

这个示例中的MyServlet抽象类可以作为一个基类,用于处理不同类型的HTTP请求,而具体的子类如MyServletImpl则负责实现特定类型请求的处理逻辑。这种结构允许我们在一个抽象的框架中定义通用的请求处理流程,并在具体的子类中提供个性化的实现,实现了模板方法设计模式的思想:

public abstract class MyServlet extends HttpServlet {
    // 模板方法,处理HTTP请求并生成响应
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
        // 执行一些通用的处理逻辑

        // 调用抽象方法,由子类实现特定的处理逻辑
        handleGetRequest(request, response);

        // 执行一些通用的处理逻辑
    }

    // 模板方法,处理HTTP POST请求并生成响应
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
        // 执行一些通用的处理逻辑

        // 调用抽象方法,由子类实现特定的处理逻辑
        handlePostRequest(request, response);

        // 执行一些通用的处理逻辑
    }

    // 模板方法,处理HTTP PUT请求并生成响应
    protected void doPut(HttpServletRequest request, HttpServletResponse response) throws IOException {
        // 执行一些通用的处理逻辑

        // 调用抽象方法,由子类实现特定的处理逻辑
        handlePutRequest(request, response);

        // 执行一些通用的处理逻辑
    }

    // 抽象方法,由子类实现特定的GET请求处理逻辑
    protected abstract void handleGetRequest(HttpServletRequest request, HttpServletResponse response) throws IOException;

    // 抽象方法,由子类实现特定的POST请求处理逻辑
    protected abstract void handlePostRequest(HttpServletRequest request, HttpServletResponse response) throws IOException;

    // 抽象方法,由子类实现特定的PUT请求处理逻辑
    protected abstract void handlePutRequest(HttpServletRequest request, HttpServletResponse response) throws IOException;
}

// 具体类:MyServletImpl
public class MyServletImpl extends MyServlet {
    // 实现抽象方法:处理GET请求逻辑
    protected void handleGetRequest(HttpServletRequest request, HttpServletResponse response) throws IOException {
        // 具体的GET请求处理逻辑
    }

    // 实现抽象方法:处理POST请求逻辑
    protected void handlePostRequest(HttpServletRequest request, HttpServletResponse response) throws IOException {
        // 具体的POST请求处理逻辑
    }

    // 实现抽象方法:处理PUT请求逻辑
    protected void handlePutRequest(HttpServletRequest request, HttpServletResponse response) throws IOException {
        // 具体的PUT请求处理逻辑
    }
}

通过在抽象类中添加doPost()doPut()的支持,我们使得MyServlet能够处理HTTP的GET、POST和PUT请求。在具体的子类中,我们分别实现了handleGetRequest()handlePostRequest()handlePutRequest()方法,来处理相应的请求逻辑。

这个例子展示了模板方法设计模式在处理不同类型的HTTP请求时的应用。通过模板方法,我们可以复用通用的处理逻辑,同时为不同类型的请求提供灵活的特定处理实现。

5、总结

优点:

  • 模板方法设计模式提高了代码复用性,通过将算法的公共部分抽象到父类中,确保了这些公共步骤在所有子类中的一致性实现,避免了重复代码的出现。
  • 可以灵活地扩展和修改算法的某些步骤,无需改变整体结构。子类可以通过实现抽象方法来覆盖或添加特定步骤,实现个性化的功能扩展。

缺点:

  • 引入了抽象类和抽象方法,增加了代码的复杂性。在一些简单的场景下,使用模板方法可能会导致类的层次结构变得复杂,不利于代码的维护。
  • 如果算法的变化点过多,可能需要在抽象类中定义大量的抽象方法,增加了子类的实现复杂性和代码的理解难度。

总体而言,模板方法设计模式在许多情况下都能很好地提高代码的复用性和灵活性。在设计和开发过程中,我们需要根据实际情况来判断是否适合使用模板方法,并平衡代码的复杂性和灵活性。合理地应用模板方法设计模式可以使代码更加清晰、易于维护,并且降低了修改代码时引入错误的风险。

三、策略设计模式

1、介绍

策略设计模式(Strategy Pattern)是一种行为型设计模式,它允许在运行时选择算法的实现方式,从而使得算法的变化独立于使用算法的客户端。这种模式通过将不同的算法封装成独立的策略类,并在上下文类中持有一个策略对象,来实现灵活的算法替换和扩展。

2、核心思想

策略设计模式的核心思想是把算法的定义和使用分开,将算法的具体实现封装在策略类中,然后在上下文类中持有一个策略对象,通过调用策略对象的方法来执行具体的算法。这样,当需要改变算法时,只需要替换相应的策略对象,而无需修改上下文类的代码,实现了算法和客户端的解耦。

  • 个人理解,不就是多态吗,传入的策略是一个接口,可以传入所有实现了该策略的具体实现,根据传入策略不同,选择不同算法实现。

3、组成和基本流程

策略设计模式由以下几个关键组件组成:

  • Strategy(策略接口):策略接口定义了算法的统一接口,所有具体策略类都需要实现该接口。
  • ConcreteStrategy(具体策略):具体策略类是策略接口的实现类,每个具体策略类实现了一种具体的算法。
  • Context(上下文):上下文类持有一个策略对象,并在需要执行算法时调用策略对象的方法。

策略设计模式的基本流程:

  1. 上下文类持有一个策略接口对象。
  2. 客户端根据需求选择具体的策略,并将其传递给上下文类。
  3. 上下文类在需要执行算法时,调用策略接口的方法。
  4. 具体的策略类根据实现方式执行算法。

以下是一个使用策略设计模式的简单的示例:

允许用户输入不同的符号来选择不同的策略进行计算,以下是用于接受用户输入并根据输入选择策略的代码:

// 策略接口
interface Strategy {
    int execute(int m, int n);
}

// 具体策略-加法策略
class AddStrategy implements Strategy {
    @Override
    public int execute(int m, int n) {
        return m + n;
    }
}

// 具体策略-除法策略
class DivStrategy implements Strategy {
    @Override
    public int execute(int m, int n) {
        if (n == 0) {
            throw new IllegalArgumentException("除数不能为零!");
        }
        return m / n;
    }
}

class Computer {
    // 持有策略
    private Strategy strategy;

    public void setStrategy(Strategy strategy) {
        // 设置策略
        this.strategy = strategy;
    }

    public int calculator(int x, int y) {
        return strategy.execute(x, y);
    }
}

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        // 创建上下文
        Computer computer = new Computer();

        while (true) {
            System.out.println("请输入运算符号(+表示加法,/表示除法,q表示退出):");
            String input = scanner.nextLine();
            if (input.equalsIgnoreCase("q")) {
                break;
            }

            Strategy strategy;
            if (input.equals("+")) {
                strategy = new AddStrategy();
            } else if (input.equals("/")) {
                strategy = new DivStrategy();
            } else {
                System.out.println("无效输入。请输入+、/或q。");
                continue;
            }

            // 设置策略
            computer.setStrategy(strategy);

            // 获取运算数
            System.out.println("请输入第一个数字:");
            int x = scanner.nextInt();
            System.out.println("请输入第二个数字:");
            int y = scanner.nextInt();
            scanner.nextLine();

            // 使用
            int result = computer.calculator(x, y);
            System.out.println("计算结果: " + result);
        }

        System.out.println("再见!");
        scanner.close();
    }
}

这个示例非常清晰地展示了策略设计模式的优势,通过策略模式,我们可以轻松地实现算法的替换,而不必修改 Computer 类的代码。这样的设计使得代码更加灵活、可扩展和易于维护。

4、使用场景和具体案例

策略设计模式适用于以下场景:

  • 当一个类需要根据条件选择不同的算法时,可以使用策略模式来避免使用多个if-else语句。
  • 当一个类有多个相似的算法,并且客户端需要动态地选择其中一个时,可以使用策略模式来实现算法的灵活替换。

业务场景

我们的电商平台支持多种支付方式,包括支付宝支付、微信支付和信用卡支付。每种支付方式的支付逻辑不同,但是在用户提交订单时,我们并不知道用户会选择哪种支付方式。

策略设计模式案例:

首先,我们定义一个策略接口 PaymentStrategy,其中包含一个 pay 方法用于执行支付逻辑。然后,我们创建三个具体的支付策略类:AlipayStrategyWeChatPayStrategyCreditCardPayStrategy,分别实现了支付宝支付、微信支付和信用卡支付的具体逻辑。

接下来,我们创建一个 PaymentContext 类作为上下文类,它持有一个支付策略对象,并提供了 setPaymentStrategy 方法用于设置支付策略。在 PaymentContext 类中,我们通过调用支付策略对象的 pay 方法来执行具体的支付逻辑。

最后,在客户端代码中,我们可以根据用户选择的支付方式来设置不同的支付策略,并执行支付操作。

示例代码如下:

// 策略接口
interface PaymentStrategy {
    void pay(double amount);
}

// 具体策略-支付宝支付
class AlipayStrategy implements PaymentStrategy {
    @Override
    public void pay(double amount) {
        System.out.println("使用支付宝支付:" + amount + "元");
        // 具体的支付逻辑
    }
}

// 具体策略-微信支付
class WeChatPayStrategy implements PaymentStrategy {
    @Override
    public void pay(double amount) {
        System.out.println("使用微信支付:" + amount + "元");
        // 具体的支付逻辑
    }
}

// 具体策略-信用卡支付
class CreditCardPayStrategy implements PaymentStrategy {
    @Override
    public void pay(double amount) {
        System.out.println("使用信用卡支付:" + amount + "元");
        // 具体的支付逻辑
    }
}

// 上下文类
class PaymentContext {
    // 持有策略
    private PaymentStrategy paymentStrategy;

    public void setPaymentStrategy(PaymentStrategy paymentStrategy) {
        // 设置策略
        this.paymentStrategy = paymentStrategy;
    }

    public void makePayment(double amount) {
        paymentStrategy.pay(amount);
    }
}

// 客户端代码
public class Main {
    public static void main(String[] args) {
        // 创建上下文
        PaymentContext paymentContext = new PaymentContext();

        // 用户选择支付宝支付
        PaymentStrategy alipayStrategy = new AlipayStrategy();
        paymentContext.setPaymentStrategy(alipayStrategy);
        paymentContext.makePayment(100.0);

        // 用户选择微信支付
        PaymentStrategy weChatPayStrategy = new WeChatPayStrategy();
        paymentContext.setPaymentStrategy(weChatPayStrategy);
        paymentContext.makePayment(200.0);

        // 用户选择信用卡支付
        PaymentStrategy creditCardPayStrategy = new CreditCardPayStrategy();
        paymentContext.setPaymentStrategy(creditCardPayStrategy);
        paymentContext.makePayment(300.0);
    }
}

在这个案例中,策略设计模式帮助我们实现了不同支付方式的逻辑隔离和动态替换。当用户选择不同的支付方式时,我们可以灵活地设置不同的支付策略,而不必修改 PaymentContext 类的代码。这样的设计使得代码更加灵活、可扩展和易于维护。

在实际的电商平台中,支付逻辑可能更加复杂,涉及支付安全、订单状态管理等,使用策略设计模式能够更好地组织和管理这些复杂的支付流程。

6、总结

策略设计模式的优点包括:

  • 算法可互换:可以在运行时动态地选择算法,实现算法的灵活替换。
  • 避免多重条件判断:避免使用大量的if-else语句,增加代码的可读性和可维护性。
  • 提高可扩展性:可以方便地增加新的策略类,扩展系统的功能。

策略设计模式的缺点包括:

  • 增加类的数量:每个具体策略类都需要一个对应的类,如果策略较多,会增加类的数量。
  • 策略的选择逻辑:客户端需要选择合适的策略,如果策略选择逻辑复杂,会增加客户端的复杂性。

总的来说,策略设计模式是一种非常有用的模式,它可以将算法的定义和使用分开,实现算法的灵活替换,提高系统的可扩展性和可

维护性。在合适的场景下,使用策略设计模式可以使代码更加清晰、简洁、易于理解和维护。

7、结合工厂设计模式

使用策略设计模式和工厂设计模式可以有效消除代码中的 if-else 语句,使代码更加清晰、可维护和易于扩展。

首先,我们可以创建一个工厂类来根据用户输入返回相应的策略对象。然后,我们使用策略设计模式将不同的策略封装在不同的类中,从而实现算法的动态切换。

下面是一个示例代码:

// 策略接口
interface Strategy {
    int execute(int m, int n);
}

// 具体策略-加法策略
class AddStrategy implements Strategy {
    @Override
    public int execute(int m, int n) {
        return m + n;
    }
}

// 具体策略-除法策略
class DivStrategy implements Strategy {
    @Override
    public int execute(int m, int n) {
        if (n == 0) {
            throw new IllegalArgumentException("除数不能为零!");
        }
        return m / n;
    }
}

// 策略工厂
class StrategyFactory {
    public static Strategy createStrategy(String input) {
        if (input.equals("+")) {
            return new AddStrategy();
        } else if (input.equals("/")) {
            return new DivStrategy();
        } else {
            throw new IllegalArgumentException("无效输入。请输入+或/。");
        }
    }
}

class Computer {
    // 持有策略
    private Strategy strategy;

    public void setStrategy(Strategy strategy) {
        // 设置策略
        this.strategy = strategy;
    }

    public int calculator(int x, int y) {
        return strategy.execute(x, y);
    }
}

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        // 创建上下文
        Computer computer = new Computer();

        while (true) {
            System.out.println("请输入运算符号(+表示加法,/表示除法,q表示退出):");
            String input = scanner.nextLine();
            if (input.equalsIgnoreCase("q")) {
                break;
            }

            try {
                // 创建策略
                Strategy strategy = StrategyFactory.createStrategy(input);
                // 设置策略
                computer.setStrategy(strategy);

                // 获取运算数
                System.out.println("请输入第一个数字:");
                int x = scanner.nextInt();
                System.out.println("请输入第二个数字:");
                int y = scanner.nextInt();
                scanner.nextLine();

                // 使用
                int result = computer.calculator(x, y);
                System.out.println("计算结果: " + result);
            } catch (IllegalArgumentException e) {
                System.out.println(e.getMessage());
            }
        }

        System.out.println("再见!");
        scanner.close();
    }
}

在这个示例中,我们使用了策略设计模式将加法和除法封装在不同的策略类中,并通过工厂设计模式根据用户的输入动态创建相应的策略对象。这样,在主程序中,就不再需要使用繁琐的 if-else 语句,代码更加简洁、易读、易于扩展和维护。

您说得对,每个序列化器实现类中的判断确实会导致代码耦合,而且还会违反开闭原则(Open-Closed Principle),因为每次添加新的版本需要修改已有的实现类。

为了避免这种耦合,我们可以进一步优化设计。我们可以使用策略模式和工厂模式的组合来解耦代码并支持添加新的版本,而不需要修改现有的实现类。

首先,我们将序列化逻辑从序列化器实现类中分离出来,创建一个新的策略接口`DeserializationStrategy`用于处理反序列化逻辑。

```java
public interface DeserializationStrategy {
    <T> T deserialize(byte[] bytes, Class<T> clazz);
}
```

然后,我们为每个版本创建一个实现`DeserializationStrategy`的具体策略类。

```java
public class Version1DeserializationStrategy implements DeserializationStrategy {
    @Override
    public <T> T deserialize(byte[] bytes, Class<T> clazz) {
        // 实现版本1的反序列化逻辑
    }
}

public class Version2DeserializationStrategy implements DeserializationStrategy {
    @Override
    public <T> T deserialize(byte[] bytes, Class<T> clazz) {
        // 实现版本2的反序列化逻辑
    }
}

// 添加更多版本的策略类...
```

接下来,我们更新`Serializer`接口,将策略模式引入其中:

```java
public interface Serializer {
    byte[] serialize(Object object);

    <T> T deserialize(byte[] bytes, Class<T> clazz);

    void registerDeserializationStrategy(int version, DeserializationStrategy strategy);
}
```

我们将`registerDeserializationStrategy`方法用于注册不同版本对应的反序列化策略。

接着,我们修改每个序列化器实现类,让它们实现新的`Serializer`接口,并将策略模式的逻辑添加到其中。

```java
public class HessianSerializer implements Serializer {
    private Map<Integer, DeserializationStrategy> deserializationStrategies = new HashMap<>();

    @Override
    public byte[] serialize(Object object) {
        // 实现Hessian的序列化方法
    }

    @Override
    public <T> T deserialize(byte[] bytes, Class<T> clazz) {
        return deserialize(bytes, clazz, 1); // 默认版本1的反序列化方式
    }

    @Override
    public void registerDeserializationStrategy(int version, DeserializationStrategy strategy) {
        deserializationStrategies.put(version, strategy);
    }

    @Override
    public <T> T deserialize(byte[] bytes, Class<T> clazz, int version) {
        DeserializationStrategy strategy = deserializationStrategies.get(version);
        if (strategy != null) {
            return strategy.deserialize(bytes, clazz);
        } else {
            // 版本不兼容时,默认选择版本1的反序列化逻辑
            return deserialize(bytes, clazz);
        }
    }
}

// 同样更新Fastjson2Serializer
public class Fastjson2Serializer implements Serializer {
    // ... 其他代码 ...

    private Map<Integer, DeserializationStrategy> deserializationStrategies = new HashMap<>();

    @Override
    public byte[] serialize(Object object) {
        // 实现Fastjson2的序列化方法
    }

    @Override
    public <T> T deserialize(byte[] bytes, Class<T> clazz) {
        return deserialize(bytes, clazz, 1); // 默认版本1的反序列化方式
    }

    @Override
    public void registerDeserializationStrategy(int version, DeserializationStrategy strategy) {
        deserializationStrategies.put(version, strategy);
    }

    @Override
    public <T> T deserialize(byte[] bytes, Class<T> clazz, int version) {
        DeserializationStrategy strategy = deserializationStrategies.get(version);
        if (strategy != null) {
            return strategy.deserialize(bytes, clazz);
        } else {
            // 版本不兼容时,默认选择版本1的反序列化逻辑
            return deserialize(bytes, clazz);
        }
    }
}
```

在上述实现中,我们将策略模式应用到序列化器实现类中,通过`registerDeserializationStrategy`方法注册不同版本的反序列化策略,并在`deserialize`方法中根据版本号选择对应的策略进行反序列化操作。如果版本不兼容,仍然可以选择默认的版本1的反序列化方式。

最后,在主程序中,您可以使用这些序列化器,并注册不同版本的反序列化策略。

```java
public class Main {
    public static void main(String[] args) {
        Serializer hessianSerializer = new HessianSerializer();
        Serializer fastjsonSerializer = new Fastjson2Serializer();

        int version = 2; // 假设报文传入的版本号为2
        Serializer serializer = version == 2 ? fastjsonSerializer : hessianSerializer;

        // 注册不同版本的反序列化策略
        serializer.registerDeserializationStrategy(1, new Version1DeserializationStrategy());
        serializer.registerDeserializationStrategy(2, new Version2DeserializationStrategy());
        // 可以继续注册更多版本的策略...

        // 假设bytes为序列化后的字节数组
        byte[] bytes = // ...;

        // 假设clazz为目标类的Class对象
        Class<?> clazz = // ...;

        // 反序列化
        Message deserializedMessage = serializer.deserialize(bytes, clazz, version);
        System.out.println("Deserialized message: " + deserializedMessage);
    }
}
```

通过使用策略模式和工厂模式的组合,我们成功解耦了代码,并可以根据版本号注册不同的反序列化策略,使得代码更加灵活和可扩展。新的版本也可以很方便地添加到现有系统中,而不需要修改已有的实现类。

四、责任链设计模式

1、介绍

责任链设计模式(Chain of Responsibility Pattern)是一种行为型设计模式,它通过为请求创建一个接收者链,将多个处理器(或接收者)连接在一起,依次尝试处理请求。当一个请求进入责任链时,每个处理器将依次检查自己是否能够处理该请求,如果可以处理,则进行处理,如果不能处理,则将请求传递给下一个处理器。

  • 请求:不只是http请求,任何一个方法的调用也是请求

2、核心思想

责任链设计模式的核心思想是解耦请求的发送者和接收者,将多个处理器组成一个链条,并在运行时动态决定请求的处理顺序。当一个请求进入责任链时,责任链上的每个处理器都有机会处理该请求,直到有一个处理器能够处理它为止。这样,每个处理器只需要关心自己能否处理该请求,而无需关心其他处理器的存在。

3、组成和基本流程

责任链设计模式由以下几个要素组成:

  • Handler(处理器):定义一个处理请求的接口,并持有下一个处理器的引用。
  • ConcreteHandler(具体处理器):实现处理器接口,对请求进行实际处理。如果不能处理请求,则将请求传递给下一个处理器。
  • Client(客户端):创建责任链并将请求发送给责任链的头部。

基本流程:

  1. 客户端创建多个具体处理器,并将它们按照处理顺序连接成责任链。
  2. 当一个请求进入责任链时,责任链上的第一个处理器开始检查是否能够处理该请求。
  3. 如果能够处理,则进行处理并结束。
  4. 如果不能处理,则将请求传递给下一个处理器,继续检查,直到有一个处理器能够处理该请求。

以下是一个使用责任链设计模式的简单的示例:

// Handler(处理器)接口
interface Handler {
    void handleRequest(int request);
    void setNextHandler(Handler nextHandler);
}

// ConcreteHandler(具体处理器)类
class ConcreteHandlerA implements Handler {
    private Handler nextHandler;

    @Override
    public void handleRequest(int request) {
        if (request >= 0 && request < 10) {
            System.out.println("ConcreteHandlerA 处理请求: " + request);
        } else if (nextHandler != null) {
            nextHandler.handleRequest(request);
        }
    }

    @Override
    public void setNextHandler(Handler nextHandler) {
        this.nextHandler = nextHandler;
    }
}

class ConcreteHandlerB implements Handler {
    private Handler nextHandler;

    @Override
    public void handleRequest(int request) {
        if (request >= 10 && request < 20) {
            System.out.println("ConcreteHandlerB 处理请求: " + request);
        } else if (nextHandler != null) {
            nextHandler.handleRequest(request);
        }
    }

    @Override
    public void setNextHandler(Handler nextHandler) {
        this.nextHandler = nextHandler;
    }
}

class ConcreteHandlerC implements Handler {
    @Override
    public void handleRequest(int request) {
        if (request >= 20 && request < 30) {
            System.out.println("ConcreteHandlerC 处理请求: " + request);
        } else {
            System.out.println("没有处理器能够处理该请求: " + request);
        }
    }

    @Override
    public void setNextHandler(Handler nextHandler) {
        // ConcreteHandlerC 是责任链的末端,不需要持有下一个处理器的引用
    }
}

// Client(客户端)类
public class Main {
    public static void main(String[] args) {
        // 创建具体处理器
        Handler handlerA = new ConcreteHandlerA();
        Handler handlerB = new ConcreteHandlerB();
        Handler handlerC = new ConcreteHandlerC();

        // 构建责任链
        handlerA.setNextHandler(handlerB);
        handlerB.setNextHandler(handlerC);

        // 客户端发送请求
        int[] requests = {5, 15, 25, 35};
        for (int request : requests) {
            handlerA.handleRequest(request);
        }
    }
}

在上述示例中,我们创建了三个具体处理器类(ConcreteHandlerA、ConcreteHandlerB、ConcreteHandlerC),它们分别能处理不同范围的请求。客户端(Main类)创建了这三个处理器,并按照处理顺序连接成了一个责任链。

当客户端发送请求时,请求会从责任链的头部(handlerA)开始传递,每个处理器检查是否能够处理该请求,如果能够处理,则进行处理,否则将请求传递给下一个处理器。这样,只有能够处理该请求的处理器会进行处理,实现了请求的动态分发和处理。

请注意,ConcreteHandlerC 是责任链的末端,并不需要持有下一个处理器的引用,因为它没有后继处理器。当请求到达末端后,如果没有任何处理器能够处理该请求,则会输出"没有处理器能够处理该请求"。

4、使用场景和具体案例

责任链设计模式在以下情况下适用:

  1. 当一个请求需要被多个对象处理,但具体的处理对象在运行时才能确定时。责任链模式允许在运行时动态地确定请求的处理顺序,从而实现灵活的处理流程。

  2. 当希望避免请求发送者与接收者之间的耦合关系,或者希望动态地指定处理请求的对象时。责任链模式将请求发送者和接收者解耦,使得请求发送者无需知道是哪个接收者处理了请求,而接收者也无需知道请求的发送者是谁,从而增强了系统的灵活性和可扩展性。

具体案例

责任链设计模式在实际应用中有许多场景,以下是一些具体案例:

  1. Servlet Filter:在Java Web开发中,Servlet Filter是一种典型的责任链模式的应用。Filter用于在Servlet请求被处理之前或之后对请求和响应进行预处理和后处理。可以通过配置多个Filter,并按照一定的顺序将它们连接成责任链,使得请求依次经过各个Filter进行处理。

  2. Spring Interceptor:在Spring框架中,Interceptor也是一种责任链模式的应用。Interceptor用于在请求被处理之前或之后进行一些公共的处理逻辑,如权限验证、日志记录等。可以通过配置多个Interceptor,并按照一定的顺序将它们连接成责任链,从而实现对请求的预处理和后处理。

  3. MyBatis中的插件:MyBatis是一个流行的Java持久化框架,它允许用户编写自定义的插件来扩展其功能。插件就是一种责任链模式的应用,可以通过插件在SQL执行前后进行拦截和处理,实现一些自定义的功能,如日志记录、缓存处理等,一层一层的代理包装。

这些具体案例展示了责任链设计模式在实际项目中的灵活应用,通过将处理逻辑拆分成多个处理器,并将它们连接成责任链,可以实现更加灵活、可扩展和可维护的代码结构。

具体案例-链表实现

这是一个简单的责任链设计模式示例代码,模拟了一个处理器链(HandlerChain)和两个具体处理器(LoggingHandler和ValidationHandler)。这些处理器按照添加的顺序依次处理请求。

  • Handler 是抽象处理器类,定义了处理器的基本结构,并持有下一个处理器的引用。
  • LoggingHandler 是具体处理器类,负责对请求进行日志处理。
  • ValidationHandler 是具体处理器类,负责对请求进行数据校验。
  • HandlerChain 是处理器链类,持有处理器链的头部和尾部,并提供添加处理器和执行处理器链的方法。

Main 类中,创建了一个处理器链 chain,并向其添加了 LoggingHandlerValidationHandler。然后,通过调用 chain.doChain("登录请求") 来触发处理器链的执行,从而依次处理请求。

调用输出:

登录请求经过了日志处理器....打了进入日志
登录请求经过了日志处理器....进行的数据校验
public class Main {
    public static void main(String[] args) {
        HandlerChain chain = new HandlerChain();
        chain.addHandler(new LoggingHandler());
        chain.addHandler(new ValidationHandler());
        chain.doChain("登录请求");
    }
}

abstract class Handler {
    protected Handler next;

    public Handler() {
    }

    public Handler(Handler next) {
        this.next = next;
    }

    public abstract void handle(String product);
}

class LoggingHandler extends Handler {
    @Override
    public void handle(String product) {
        System.out.println(product + "经过了日志处理器....打了进入日志");
    }
}

class ValidationHandler extends Handler {
    @Override
    public void handle(String product) {
        System.out.println(product + "经过了日志处理器....进行的数据校验");
    }
}

class HandlerChain {
    protected Handler head;
    protected Handler tail;

    public void addHandler(Handler handler) {
        if (head == null && tail == null) {
            head = handler;
            tail = handler;
        }
        tail.next = handler;
        tail = handler;
    }

    public void doChain(String msg) {
        if (head != null) {
            Handler p = head;
            while (p != null) {
                p.handle(msg);
                p = p.next;
            }
        }
    }
}

此示例代码演示了责任链设计模式的基本实现方式,它允许灵活地添加、移除和调整处理器的顺序,从而实现不同的处理逻辑和扩展。在实际应用中,可以根据业务需求定义更复杂的处理器链,并实现更多的具体处理器来满足不同的需求。

具体案例-数组实现

在这个示例中,HandlerChain类持有一个List<Handler>,并提供了addHandler方法用于添加处理器到链中。doChain方法遍历链中的处理器,并依次调用它们的handle方法,从而实现责任链的执行。

public class Main {
    public static void main(String[] args) {
        HandlerChain chain = new HandlerChain();
        chain.addHandler(new LoggingHandler());
        chain.addHandler(new ValidationHandler());
        chain.doChain("登录请求");
    }
}

abstract class Handler {
    public Handler() {
    }

    public abstract void handle(String product);
}

class LoggingHandler extends Handler {
    @Override
    public void handle(String product) {
        System.out.println(product + "经过了日志处理器....打了进入日志");
    }
}

class ValidationHandler extends Handler {
    @Override
    public void handle(String product) {
        System.out.println(product + "经过了日志处理器....进行的数据校验");
    }
}

class HandlerChain {
    protected List<Handler> handlers;

    public HandlerChain() {
        this.handlers = new ArrayList<>();
    }

    public void addHandler(Handler handler) {
        handlers.add(handler);
    }

    public void doChain(String msg) {
        for (Handler handler : handlers) {
            handler.handle(msg);
        }
    }
}

5、总结

责任链设计模式的优点:

  • 解耦请求发送者和接收者,增强了代码的灵活性和可扩展性。
  • 动态地组织和分配处理器,简化了代码的维护和扩展。

责任链设计模式的缺点:

  • 可能会导致请求在责任链上无法被处理。
  • 需要遍历整个责任链,对性能有一定影响。

总体而言,责任链设计模式在一些特定的场景下能够带来很大的便利,但也需要谨慎使用,避免责任链过长或者过于复杂,其次责任链模式在工作中的应用非常广泛,它可以在很多场景中帮助我们实现灵活的请求处理和任务分配。

以下是一些常见的使用场景:

  1. 日志记录器:在应用程序中,我们可能需要将日志记录到不同的位置,如控制台、文件、数据库等。我们可以创建一个日志记录器链,每个记录器处理特定级别的日志,然后将请求传递给下一个记录器。这样,可以根据日志级别灵活地记录日志信息。

  2. Web应用中的过滤器和拦截器:在Web应用程序中,我们经常需要对请求进行预处理和后处理,如身份验证、授权、编码转换、请求日志记录等。过滤器和拦截器就是典型的使用责任链模式的场景,请求和响应在过滤器或拦截器链中依次传递,每个过滤器或拦截器执行特定的任务。

  3. 工作流引擎:在一个工作流引擎中,一个请求可能需要经过多个处理步骤,这些步骤可以看作是一个责任链。每个处理器处理请求的一个部分,然后将请求传递给下一个处理器,直到请求被完全处理。

  4. 软件审批流程:在企业软件开发过程中,代码审查、需求审批、文档审查等流程可能需要多个审批者按顺序审批。这种场景下,责任链模式能够确保每个审批者只关注自己的审批职责,并将审批请求传递给下一个审批者。

  5. 电子邮件处理:在一个电子邮件处理系统中,可能需要对不同类型的邮件进行不同的处理,如垃圾邮件过滤、自动回复、邮件归类等。在这种情况下,可以使用责任链模式来创建一个邮件处理链,每个处理器负责处理特定类型的邮件,然后将邮件传递给下一个处理器。

  6. 事件处理系统:在一个事件驱动的系统中,可能需要对不同类型的事件进行不同的处理。责任链模式可以用于创建一个事件处理器链,每个处理器负责处理特定类型的事件,并将事件传递给下一个处理器。这样可以确保系统的可扩展性和灵活性。

  7. 规则引擎:在某些业务场景下,可能需要按照一定的规则对数据进行处理。规则引擎是典型的使用责任链模式的场景。每个规则可以看作是一个处理器,对数据进行特定的处理,然后将数据传递给下一个规则,直至所有规则都被执行。

  8. 任务调度系统:在任务调度系统中,根据任务的优先级、类型和资源需求,可能需要将任务分配给不同的执行器。责任链模式可以确保每个执行器只关注自己可以处理的任务,并将其他任务传递给下一个执行器。

在这些场景中,责任链模式使得请求的处理和任务的分配变得灵活和可配置,同时保持了各个处理器之间的解耦,使得系统具有更好的扩展性和可维护性。因此,责任链模式是很多系统设计中常用的一种设计模式。

五、状态设计模式

1、介绍

状态设计模式(State Pattern),又称状态模式,是一种行为型设计模式。它允许一个对象在其内部状态发生改变时改变其行为,使得对象在不同状态下具有不同的行为。状态模式的关键在于将对象的状态封装成独立的类,并将请求委派给当前状态对象进行处理。这样可以避免在对象中使用大量的条件语句来判断状态,并将状态转换逻辑集中管理,使得代码更加清晰和易于维护。

2、核心思想

状态设计模式的核心思想是将对象的状态抽象成独立的类,每个状态类都实现了相同的接口,表示对象在该状态下的行为。对象内部维护一个状态对象,根据不同的状态委派请求给对应的状态类处理。当对象的状态发生改变时,可以动态地切换状态对象,从而改变对象的行为。

3、组成和基本流程

状态设计模式由以下几个要素组成:

  • Context(上下文):定义客户端感兴趣的接口,并持有一个具体状态的引用。
  • State(状态):定义一个接口来封装与Context的一个特定状态相关的行为。
  • ConcreteState(具体状态):实现State接口,每个具体状态类负责对应状态下的行为实现。
  • Client(客户端):通过Context类来与状态对象进行交互,实现状态切换。

基本流程:

  1. 客户端创建Context对象,并初始化为某个具体状态的对象。
  2. Context对象接收客户端的请求,并委派给当前具体状态对象处理。
  3. 具体状态对象根据当前状态处理请求,可能会导致状态发生改变。
  4. 如果状态发生改变,Context对象切换到新的具体状态对象进行处理。

以下是一个简单的状态设计模式代码案例:

// State(状态)接口
interface State {
    void handle();
}

// ConcreteState(具体状态)类
class ConcreteStateA implements State {
    @Override
    public void handle() {
        System.out.println("当前是状态A,执行状态A的行为");
    }
}

class ConcreteStateB implements State {
    @Override
    public void handle() {
        System.out.println("当前是状态B,执行状态B的行为");
    }
}

// Context(上下文)类
class Context {
    private State currentState;

    public Context() {
        currentState = new ConcreteStateA(); // 初始状态为状态A
    }

    public void setCurrentState(State currentState) {
        this.currentState = currentState;
    }

    public void request() {
        currentState.handle();
    }
}

// Client(客户端)类
public class Main {
    public static void main(String[] args) {
        Context context = new Context();
        context.request();

        context.setCurrentState(new ConcreteStateB());
        context.request();
    }
}

在上述示例中,我们定义了一个简单的状态设计模式。State接口定义了一个handle方法,表示当前状态下的行为;ConcreteStateAConcreteStateB是两个具体状态类,分别实现了State接口,表示状态A和状态B下的行为。Context类是上下文类,持有当前的状态对象,并根据当前状态委派请求给相应的状态对象处理。客户端(Main类)创建了Context对象,并初始状态为状态A,然后调用request方法来执行当前状态的行为。接着,客户端切换状态为状态B,再次调用request方法来执行状态B下的行为。

4、使用场景

状态设计模式适用于以下场景:

  • 当一个对象的行为取决于它的状态,并且该对象需要根据状态改变行为时。
  • 当一个对象有许多状态且状态之间的转换频繁发生时,使用状态模式可以避免大量的条件语句。
  • 当一个对象的行为包含大量与状态相关的代码,状态模式可以将不同状态的行为拆分成独立的类,使代码更加清晰和易于维护。

现在我们来看一个简单的Java示例。假设我们要模拟一个简易的电视遥控器,具有开启、关闭和调整音量的功能。如果我们不使用设计模式,编写出来的代码可能是这个样子的,我们需要针对电视机当前的状态为每一次操作编写判断逻辑:

public class TV {
    private boolean isOn;
    private int volume;

    public TV() {
        isOn = false;
        volume = 0;
    }

    public void turnOn() {
        // 如果是开启状态
        if (isOn) {
            System.out.println("TV is already on.");
        } else {
            isOn = true;
            System.out.println("Turning on the TV.");
        }
    }

    public void turnOff() {
        if (isOn) {
            isOn = false;
            System.out.println("Turning off the TV.");
        } else {
            System.out.println("TV is already off.");
        }
    }

    public void adjustVolume(int volume) {
        if (isOn) {
            this.volume = volume;
            System.out.println("Adjusting volume to: " + volume);
        } else {
            System.out.println("Cannot adjust volume, TV is off.");
        }
    }
}

public class Main {
    public static void main(String[] args) {
        TV tv = new TV();
        tv.turnOn();
        tv.adjustVolume(10);
        tv.turnOff();
    }
}

当然在该例子中我们的状态比较少,所以代码看起来也不是很复杂,但是状态如果变多了呢?比如加入换台、快捷键、静音等功能后呢?你会发现条件分支会急速膨胀,所以此时状态设计模式就要登场了:

//首先,我们定义抽象状态接口TVState,将每一个修改状态的动作抽象成个接口:
interface TVState {
    void turnOn();
    void turnOff();
    void adjustVolume(int volume);
}
//接下来,我们为每个具体状态创建类,实现 `TVState` 接口。例如,我们创建 `TVOnState` 和 `TVOffState` 类
// 在on状态下,去执行以下各种操作
class TVOnState implements TVState {
    @Override
    public void turnOn() {
        System.out.println("TV is already on.");
    }

    @Override
    public void turnOff() {
        System.out.println("Turning off the TV.");
    }

    @Override
    public void adjustVolume(int volume) {
        System.out.println("Adjusting volume to: " + volume);
    }
}

// 在关机的状态下执行以下的操作
class TVOffState implements TVState {
    @Override
    public void turnOn() {
        System.out.println("Turning on the TV.");
    }

    @Override
    public void turnOff() {
        System.out.println("TV is already off.");
    }

    @Override
    public void adjustVolume(int volume) {
        System.out.println("Cannot adjust volume, TV is off.");
    }
}
//接下来,我们定义上下文类 `TV`
class TV {
    // 当前状态
    private TVState state;

    public TV() {
        state = new TVOffState();
    }

    public void setState(TVState state) {
        this.state = state;
    }

    public void turnOn() {
        // 打开
        state.turnOn();
        // 设置为开机状态
        setState(new TVOnState());
    }

    public void turnOff() {
        // 关闭
        state.turnOff();
        // 设置为关机状态
        setState(new TVOffState());
    }

    public void adjustVolume(int volume) {
        state.adjustVolume(volume);
    }
}
//现在,我们可以在游戏中根据角色的状态来执行不同的行为,而不需要直接在 `Character` 类中进行大量的条件判断。
public class Main {
    public static void main(String[] args) {
        TV tv = new TV();
        tv.turnOn();
        tv.adjustVolume(10);
        tv.turnOff();
        tv.adjustVolume(10);
    }
}

这个例子展示了状态模式的基本结构和用法:

Turning on the TV.
Adjusting volume to: 10
Turning off the TV.
Cannot adjust volume, TV is off.

通过使用状态模式,我们可以更好地组织和管理与特定状态相关的代码。当状态较多时,这种模式的优势就会凸显出来,同时我们在代码时,因为我们会对每个状态进行独立封装,所以也会简化代码编写。

5、总结

状态设计模式通过将对象的状态封装成独立的类,并根据当前状态委派请求给相应的状态对象处理,实现了对象行为的动态改变。它可以消除大量的条件语句,使代码更加灵活和可维护。使用状态设计模式可以将复杂的对象行为拆分成独立的类,从而提高代码的可读性和可维护性。

优点

  • 将对象的状态抽象成独立的类,避免大量的条件语句,使代码更加清晰和易于维护。

  • 支持开闭原则,可以方便地添加新的状态类,而无需修改现有代码。

  • 当使用状态模式时,有以下几个具体的好处,让我用白话点来解释:

    1. 清晰简洁的代码结构: 状态模式让我们将每种状态的处理逻辑封装在独立的类中,避免了大量的条件语句,让代码结构更加清晰、简洁,易于理解和维护。

    2. 易于扩展和添加新功能: 通过将状态与上下文类分离,新增状态时只需添加新的状态类,不需要修改原有代码,符合开闭原则,使得扩展和添加新功能变得非常简单。

    3. 状态切换更加灵活: 上下文类可以轻松地切换不同的状态,只需在运行时设置不同的状态类,状态之间的切换变得灵活可控。

    4. 降低代码复杂度: 在处理复杂的状态逻辑时,状态模式能够将每个状态的处理逻辑独立起来,降低了代码的复杂度,让代码更易于维护和调试。

    5. 增强可读性: 状态模式可以让每个状态类只包含特定状态下的逻辑,使得代码的含义更加明确,增强了代码的可读性。

    总之,状态模式为处理具有不同状态的对象提供了一种优雅的解决方案,通过封装状态的处理逻辑,简化了代码结构,使得代码更易于扩展和维护,降低了代码的复杂度,增强了代码的可读性。这让开发人员可以更加专注于业务逻辑的实现,而不需要过多地关心状态切换和状态处理的细节。

缺点

  • 状态模式会增加类的数量,增加代码复杂性。
  • 在某些情况下,状态的转换可能较为复杂,需要仔细设计状态之间的转换逻辑。

总体而言,状态设计模式在一些需要根据状态改变行为的场景中是很有用的设计模式,它可以提高代码的可读性和可维护性,并支持系统的扩展。在使用状态模式时,应根据实际情况仔细设计状态之间的转换逻辑,避免状态的频繁转换导致性能问题。

六、迭代器设计模式

1、介绍

迭代器设计模式(Iterator Design Pattern)是一种行为型设计模式,它提供了一种顺序访问聚合对象(如列表、集合等)元素的方法,而无需暴露底层集合的表示方式。通过使用迭代器,可以遍历聚合对象,而不必了解其内部结构。

2、核心思想

迭代器设计模式的核心思想是将迭代逻辑从聚合对象中抽离出来,使得聚合对象可以专注于管理元素,而迭代逻辑由迭代器来实现。这样做的好处是在不影响聚合对象结构的情况下,能够灵活地遍历聚合对象中的元素。

3、组成和基本流程

  • 组成:迭代器设计模式由以下两个主要组件组成:

    • 迭代器(Iterator):定义访问和遍历元素的接口,通常包括hasNext()方法判断是否有下一个元素,以及next()方法获取下一个元素。

    • 具体迭代器(Concrete Iterator):实现迭代器接口,负责具体的遍历逻辑,维护遍历时的状态信息。

  • 基本流程:迭代器设计模式的基本流程如下:

    1. 定义聚合对象,并在聚合对象中实现一个方法用于返回迭代器对象。
    2. 定义迭代器接口,包含用于遍历聚合对象的方法。
    3. 在具体聚合对象中实现迭代器接口,定义具体迭代器类,实现遍历逻辑。
    4. 在客户端中,通过聚合对象的迭代器方法获取迭代器对象,然后使用迭代器遍历聚合对象的元素。

以下是一个按照组成和基本流程的代码案例:

// 迭代器接口
interface Iterator {
    boolean hasNext();
    Object next();
}

// 具体聚合对象
class ConcreteAggregate {
    private Object[] elements;
    
    public Iterator createIterator() {
        return new ConcreteIterator(this);
    }
}

// 具体迭代器
class ConcreteIterator implements Iterator {
    private ConcreteAggregate aggregate;
    private int currentIndex;
    
    public ConcreteIterator(ConcreteAggregate aggregate) {
        this.aggregate = aggregate;
        this.currentIndex = 0;
    }
    
    public boolean hasNext() {
        return currentIndex < aggregate.elements.length;
    }
    
    public Object next() {
        if (hasNext()) {
            return aggregate.elements[currentIndex++];
        }
        return null;
    }
}

// 客户端代码
public class Client {
    public static void main(String[] args) {
        ConcreteAggregate aggregate = new ConcreteAggregate();
        Iterator iterator = aggregate.createIterator();
        while (iterator.hasNext()) {
            Object element = iterator.next();
            // 处理元素逻辑
        }
    }
}

4、使用场景和具体案例

迭代器设计模式适用于以下场景:

  • 当需要遍历一个复杂对象结构,并且不希望暴露其内部表示时。
  • 当需要提供多种遍历方式,而又不希望在聚合对象中增加复杂的遍历逻辑时。
  • 当希望将遍历算法和遍历逻辑与聚合对象分离,使得代码更加清晰和易于维护。

在下面例子中,我们实现了一个简单的类似ArrayList的集合SuperArray,并且实现了迭代器设计模式:

  • SuperArray类表示一个简单的数组聚合对象,它封装了一个整型数组,并提供了添加元素、获取元素、获取长度和创建迭代器等方法。
  • ArrayIterator类是具体的迭代器类,它实现了Iterator接口,负责遍历SuperArray对象中的元素。
  • Main类的main()方法中,我们创建了一个SuperArray对象并添加了一些元素。然后,通过调用iterator()方法获取迭代器对象,使用迭代器遍历并输出SuperArray对象中的元素。
// 迭代器接口
interface Iterator {
    boolean hasNext();
    Object next();
}

// 聚合对象
class SuperArray {
    private int[] array;
    private int curr = -1;

    public SuperArray() {
        this(5);
    }

    public SuperArray(int capacity) {
        this.array = new int[capacity];
    }

    public int getLength() {
        return array.length;
    }

    public int add(int data) {
        // 判断容量、扩容
        // ...
        array[++curr] = data;
        return curr;
    }

    public int get(int index) {
        return array[index];
    }

    public Iterator iterator() {
        // 返回一个迭代器对象,迭代器持有当前对象实例
        return new ArrayIterator(this);
    }
}

// 具体迭代器
class ArrayIterator implements Iterator {
    // 维护一个聚合对象
    private SuperArray superArray;
    // 记录一个当前位置指针
    private int currIndex;

    public ArrayIterator(SuperArray concreteObject) {
        this.superArray = concreteObject;
        this.currIndex = 0;
    }

    @Override
    public boolean hasNext() {
        // 判断是否有下一个
        return currIndex < superArray.getLength();
    }

    @Override
    public Object next() {
        int length = superArray.getLength();

        if (currIndex < length) {
            // 返回当前元素,且指针下移
            return superArray.get(currIndex++);
        }
        return null;
    }
}

public class Main {
    public static void main(String[] args) {
        SuperArray superArray = new SuperArray();
        for (int i = 0; i < 5; i++) {
            superArray.add(i + 1);
        }
        // 创建并获得迭代器
        // 隐藏了底层实现(没有方法获取到 superArray 内具体的实现 - array,只能通过公共的方法访问),所以我们只需要关注迭代器结合
        Iterator iterator = superArray.iterator();
        while (iterator.hasNext()) {
            System.out.println("iterator.next() = " + iterator.next());
        }
    }
}

5、总结

优点

  • 将遍历算法与聚合对象分离,增加了代码的灵活性和可维护性。
  • 简化了聚合对象的接口,对外隐藏了内部表示,降低了对聚合对象的依赖。

缺点

  • 增加了迭代器类的数量,使得代码结构变得更加复杂。
  • 对于简单的聚合对象,使用迭代器模式可能会显得过于繁琐。

总结
迭代器设计模式提供了一种优雅的方式来遍历聚合对象,它将遍历算法与聚合对象解耦,使得代码更加灵活和易于维护。然而,在简单的情况下,使用迭代器模式可能会增加代码复杂性,因此在应用时需根据实际情况权衡利弊。

其它:

在使用迭代器内的remove()方法时,需要注意以下几点:

  1. 调用位置remove()方法只能在调用了next()方法之后再调用。例如,假设我们有一个迭代器iterator,如果在调用remove()之前没有调用iterator.next(),或者在同一个next()方法之后再次调用remove(),都将会抛出IllegalStateException异常。这是因为迭代器需要知道当前指针指向的元素,才能正确地执行删除操作。

  2. 单次迭代remove()方法在一次迭代中只能被调用一次。在每次调用next()方法后,我们只能调用一次remove()来移除上一个元素。如果在同一次迭代中多次调用remove(),将会导致IllegalStateException异常。这是为了避免迭代器状态的混乱,保持每次迭代操作的一致性。

  3. 验证元素:在调用remove()方法之前,必须先调用hasNext()next()方法来检查是否还有元素。如果在调用remove()之前没有检查元素,而迭代器已经遍历到了最后一个元素,将会导致remove()方法抛出NoSuchElementException异常。因此,在删除元素之前,我们应该先确保迭代器指针还没有到达最后一个元素。

  4. 并发修改:在使用迭代器遍历集合的同时,通过其他方式修改了集合的结构(比如直接调用集合的add()remove()等方法),会导致迭代器的状态不一致,进而可能引发ConcurrentModificationException异常。为了避免并发修改问题,建议在使用迭代器期间,不要使用集合的其他修改方法。如果需要在遍历过程中修改集合,应该使用迭代器的remove()方法来安全地删除元素。

综上所述,使用迭代器的remove()方法需要遵循特定的调用顺序,确保在正确的时机调用并避免多次调用。同时,在调用remove()方法之前要进行元素的验证,确保迭代器指针没有到达最后一个元素。另外,需要注意避免在使用迭代器时并发修改集合的结构,以免引发ConcurrentModificationException异常。遵循这些注意事项,可以确保在使用迭代器的remove()方法时能够正常而安全地移除集合中的元素。

七、访问者设计模式

说实话我也看不懂,理解不了,但是这个还是通过组合的方式实现的

1、介绍

访问者设计模式(Visitor Pattern)是一种行为型设计模式,用于在不修改已有类的情况下,对现有类的对象结构进行操作或添加新的操作。它允许在不改变对象结构的前提下,定义作用于该结构中元素的新操作,从而实现对元素的新功能扩展。

2、核心思想

访问者模式的核心思想是将数据结构与对数据的操作分离。

  • 通过引入访问者对象,使得数据结构中的元素可以接受不同类型的访问者对象,从而可以实现不同的操作。

3、组成和基本流程

访问者模式主要包括以下组成:

  • 抽象访问者(Visitor):定义访问元素对象的接口,通过这个接口,访问者可以访问所有具体元素。
  • 具体访问者(ConcreteVisitor):实现抽象访问者定义的接口,为每种具体元素对象提供具体的访问操作。
  • 抽象元素(Element):定义接受访问者对象的接口,通过这个接口,元素对象可以被访问者访问。
  • 具体元素(ConcreteElement):实现抽象元素定义的接口,提供接受访问者访问的具体实现。
  • 对象结构(Object Structure):包含元素对象的结构,可以是一个集合或其他数据结构。
  • 客户端(Client):通过访问者来访问对象结构中的元素。

基本流程如下:

  1. 定义抽象访问者接口,声明访问具体元素对象的方法。
  2. 定义抽象元素接口,声明接受访问者对象的方法。
  3. 定义具体元素类,实现抽象元素接口,并提供具体的实现。
  4. 定义具体访问者类,实现抽象访问者接口,并为每种具体元素对象提供具体的访问操作。
  5. 定义对象结构,用于存储具体元素对象。
  6. 在客户端中,创建具体元素对象并添加到对象结构中,然后创建具体访问者对象,调用访问者的访问方法对元素进行操作。

4、使用场景和具体案例

  • 当需要为一个对象结构中的元素添加新的操作,且不希望修改现有类的结构时,可以使用访问者模式。
  • 当需要对一个对象结构中的元素进行多种不相关的操作,且希望避免这些操作对元素类的污染时,可以使用访问者模式。

当我们谈论访问者设计模式时,可以把它比作一个旅行团的场景。假设你要带领一个旅行团游览一个大城市,这个城市里有很多不同的景点,比如博物馆、公园、购物中心等等。

在我们的旅行团场景中:

  • 可以将城市的景点看作是对象结构
  • 旅行团成员则是访问者。
  • 每个景点都可以接受访问者并提供一种或多种参观方式。

这样,我们就可以实现旅行团成员在不同景点间游览的行为,同时不需要修改景点的结构。

访问者设计模式示例
访问者设计模式示例
  1. 创建访问者接口(Visitor Interface):首先,我们需要定义一个访问者接口,它包含了旅行团成员可能会访问的不同景点的访问方法。例如,我们可以定义一个名为"Visitor"的接口,其中包含类似visitMuseum,visitPark,visitShoppingMall等方法。
  2. 创建具体访问者(Concrete Visitors):接下来,我们为旅行团成员的不同类型创建具体访问者类,实现访问者接口中的方法。例如,我们可以创建"Tourist"和"Guide"两个具体访问者类,它们分别表示普通旅游者和导游。
  3. 创建元素接口(Element Interface):定义一个元素接口,其中包含一个接受访问者的方法accept,该方法将具体的访问者作为参数。
  4. 创建具体元素(Concrete Elements):创建表示不同景点的具体元素类,这些类实现元素接口,并在accept方法中调用访问者的对应方法。例如,我们可以创建"Museum"、"Park"和"ShoppingMall"等具体元素类。
  5. 建立对象结构(Object Structure):创建一个对象结构,该结构存储所有景点的引用,并可以接受访问者。例如,我们可以创建一个"City"类,其中包含一个列表,存储所有景点(即具体元素)的引用。
  6. 实现游览过程:最后,我们将旅行团成员引导到不同的景点上,并让他们按照一定顺序访问这些景点。旅行团成员可以依次访问每个景点,通过调用accept方法,将自己作为访问者传递给城市的对象结构。城市对象结构在接受访问者时,会调用具体元素的accept方法,从而实现访问者对景点的访问。

当然,下面是用Java语言实现的示例代码,演示如何使用访问者设计模式来模拟旅行团游览大城市的场景:

// 访问者接口
interface Visitor {
    void visitMuseum(Museum museum);
    void visitPark(Park park);
    void visitShoppingMall(ShoppingMall shoppingMall);
}

// 具体访问者 - 普通旅游者
class Tourist implements Visitor {
    @Override
    public void visitMuseum(Museum museum) {
        System.out.println("Tourist is visiting the museum");
    }

    @Override
    public void visitPark(Park park) {
        System.out.println("Tourist is enjoying the park");
    }

    @Override
    public void visitShoppingMall(ShoppingMall shoppingMall) {
        System.out.println("Tourist is shopping at the mall");
    }
}

// 具体访问者 - 导游
class Guide implements Visitor {
    @Override
    public void visitMuseum(Museum museum) {
        System.out.println("Guide is explaining the museum history");
    }

    @Override
    public void visitPark(Park park) {
        System.out.println("Guide is giving information about the park");
    }

    @Override
    public void visitShoppingMall(ShoppingMall shoppingMall) {
        System.out.println("Guide is showing the best shops at the mall");
    }
}

// 元素接口
interface CityElement {
    void accept(Visitor visitor);
}

// 具体元素 - 博物馆
class Museum implements CityElement {
    @Override
    public void accept(Visitor visitor) {
        visitor.visitMuseum(this);
    }
}

// 具体元素 - 公园
class Park implements CityElement {
    @Override
    public void accept(Visitor visitor) {
        visitor.visitPark(this);
    }
}

// 具体元素 - 购物中心
class ShoppingMall implements CityElement {
    @Override
    public void accept(Visitor visitor) {
        visitor.visitShoppingMall(this);
    }
}

// 对象结构 - 城市
class City {
    private List<CityElement> cityElements = new ArrayList<>();

    public void addCityElement(CityElement cityElement) {
        cityElements.add(cityElement);
    }

    public void accept(Visitor visitor) {
        for (CityElement cityElement : cityElements) {
            cityElement.accept(visitor);
        }
    }
}

// 测试
public class Main {
    public static void main(String[] args) {
        City city = new City();
        city.addCityElement(new Museum());
        city.addCityElement(new Park());
        city.addCityElement(new ShoppingMall());

        Visitor tourist = new Tourist();
        Visitor guide = new Guide();

        System.out.println("Tourist's tour:");
        city.accept(tourist);

        System.out.println("\nGuide's tour:");
        city.accept(guide);
    }
}

在上面的Java代码中,我们使用了访问者设计模式来实现旅行团游览大城市的场景。其中,游客(Tourist)和导游(Guide)是具体的访问者,博物馆(Museum)、公园(Park)、购物中心(ShoppingMall)是具体的元素。City是对象结构,用于存储所有景点,并接受访问者的访问。

在测试部分,我们创建了一个City对象,并向其中添加了不同的景点。然后分别用游客和导游进行游览,观察他们在每个景点上执行的不同访问操作。

这样,我们就成功地使用访问者设计模式来模拟了旅行团游览大城市的场景。该设计模式可以使得景点的结构保持稳定,同时旅行团成员可以根据自己的类型执行不同的访问行为,而无需改变景点的代码。


访问者模式在现实世界中有很多应用场景,其中一个典型的例子是网站数据统计。假设我们有一个网站,网站上有不同类型的页面(例如首页、文章页、产品页等),而我们希望能够统计每种类型页面的访问量。

下面是一个用Java语言实现网站数据统计的访问者模式示例代码:

// 访问者接口
interface Visitor {
    void visit(HomePage homePage);
    void visit(ArticlePage articlePage);
    void visit(ProductPage productPage);
}

// 具体访问者 - 网站数据统计
class WebsiteStatsVisitor implements Visitor {
    private int homePageCount = 0;
    private int articlePageCount = 0;
    private int productPageCount = 0;

    @Override
    public void visit(HomePage homePage) {
        homePageCount++;
    }

    @Override
    public void visit(ArticlePage articlePage) {
        articlePageCount++;
    }

    @Override
    public void visit(ProductPage productPage) {
        productPageCount++;
    }

    public void displayStats() {
        System.out.println("Home Page Count: " + homePageCount);
        System.out.println("Article Page Count: " + articlePageCount);
        System.out.println("Product Page Count: " + productPageCount);
    }
}

// 元素接口
interface Page {
    void accept(Visitor visitor);
}

// 具体元素 - 首页
class HomePage implements Page {
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}

// 具体元素 - 文章页
class ArticlePage implements Page {
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}

// 具体元素 - 产品页
class ProductPage implements Page {
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}

// 对象结构 - 网站
class Website {
    private List<Page> pages = new ArrayList<>();

    public void addPage(Page page) {
        pages.add(page);
    }

    public void accept(Visitor visitor) {
        for (Page page : pages) {
            page.accept(visitor);
        }
    }
}

// 测试
public class Main {
    public static void main(String[] args) {
        Website website = new Website();
        website.addPage(new HomePage());
        website.addPage(new ArticlePage());
        website.addPage(new ProductPage());
        website.addPage(new ArticlePage());
        website.addPage(new HomePage());

        Visitor websiteStatsVisitor = new WebsiteStatsVisitor();
        website.accept(websiteStatsVisitor);

        ((WebsiteStatsVisitor) websiteStatsVisitor).displayStats();
    }
}

在上面的示例中,我们创建了一个网站,并向网站中添加了不同类型的页面(首页、文章页、产品页)。然后,我们创建了一个用于统计网站数据的访问者WebsiteStatsVisitor,它实现了访问者接口。当访问者访问各种页面时,会根据页面类型进行相应的计数。

在测试部分,我们模拟了一些页面的访问,并最终显示了每种类型页面的访问次数。

这个例子中,访问者模式将网站数据统计与网站的页面结构解耦,使得我们可以方便地新增其他的访问者来处理不同的操作,同时也能很容易地增加新的页面类型,而不需要修改已有的代码。这展示了访问者模式在真实业务场景中的应用。

6、总结

优点:

  • 可以在不修改现有类的情况下,增加新的操作和功能。
  • 将数据结构和操作分离,使得扩展新功能更加灵活。

缺点:

  • 增加了访问者类和元素类的数量,增加了系统复杂性。
  • 当元素类个数较少且变化不大时,访问者模式可能会显得过于繁琐。

应用建议:

访问者模式适用于需要在不改变数据结构的情况下,对数据结构中的元素进行操作的场景。它能够将不同的操作封装在具体的访问者中,从而实现对元素的功能扩展。但在使用时,应该根据具体情况权衡利弊,避免过度设计,保持代码的简洁和可维护性。

八、备忘录设计模式

1、介绍

备忘录设计模式(Memento Design Pattern)是一种行为型设计模式,它允许在不破坏封装性的前提下,捕获一个对象的内部状态,并在之后恢复该状态。这样,对象在后续的操作中可以回到之前的状态,提供了一种可撤销和恢复操作的机制。

补充知识:

"undo", "redo", 和 "todo" 是在软件开发中常见的术语,它们与备忘录设计模式有相关性。

  1. Undo(撤销)
    撤销是指在执行一个操作后,可以回到之前的状态。这意味着我们可以撤销前一步或多步操作,返回到过去的状态。例如,在文本编辑器中,当我们执行了一系列编辑操作,如果需要取消之前的某些操作,就可以使用撤销功能。

  2. Redo(重做)
    重做是指在执行了撤销操作后,可以再次执行这些被撤销的操作,恢复到之前的状态。例如,在文本编辑器中,如果我们执行了撤销操作,但之后发现撤销的部分是错误的,就可以使用重做功能重新执行之前的操作。

  3. Todo(待办事项)
    "Todo" 是 "to do" 的缩写,指待办事项列表。在软件开发中,特别是在项目管理或任务追踪中,我们通常会创建一个待办事项列表,用于记录未完成的任务、功能或问题。这样的列表帮助团队成员了解尚未完成的工作,并能够跟踪进度。

这些术语与备忘录设计模式的关系在于,"undo" 和 "redo" 是备忘录设计模式的实际应用场景。备忘录模式允许我们捕获对象的状态并在后续需要时恢复,从而实现了撤销和重做的功能。而 "todo" 列表则是一个待办事项列表,虽然与备忘录模式没有直接关联,但在软件开发中与项目管理密切相关。

2、核心思想

备忘录设计模式的核心思想是:

  • 将对象的状态保存在一个备忘录对象中
  • 通过在原对象和备忘录对象之间建立关联
  • 可以在之后根据备忘录对象恢复原对象的状态。

保存 ➡️ 恢复

3、组成和基本流程

组成:

  1. Originator(发起人):拥有需要保存和恢复状态的对象,并负责创建备忘录对象和从备忘录对象中恢复状态。
  2. Memento(备忘录):用于存储Originator对象的内部状态,并提供给Originator进行恢复操作的接口。
  3. Caretaker(负责人):负责保存备忘录对象,但不对备忘录对象的内容进行操作。主要作用是防止Originator以外的对象访问备忘录。

基本流程:

  1. Originator通过创建备忘录对象将自身状态存储在备忘录中。
  2. Originator可以在后续的操作中根据备忘录对象来恢复自身状态。

一个基本案例:

// 备忘录类
class Memento {
    private String state;

    public Memento(String state) {
        this.state = state;
    }

    public String getState() {
        return state;
    }
}

// 发起人类
class Originator {
    private String state;

    public void setState(String state) {
        this.state = state;
    }

    public String getState() {
        return state;
    }

    public Memento saveStateToMemento() {
        return new Memento(state);
    }

    public void undo(Memento memento) {
        state = memento.getState();
    }
}

// 负责人类
class Caretaker {
    private List<Memento> mementos = new ArrayList<>();

    public void saveMemento(Memento memento) {
        mementos.add(memento);
    }

    public Memento getMemento(int index) {
        return mementos.get(index);
    }
}

public class Main {
    public static void main(String[] args) {
        Originator originator = new Originator();
        Caretaker caretaker = new Caretaker();

        // 设置Originator的状态并保存到备忘录
        originator.setState("State 1");
        caretaker.saveMemento(originator.saveStateToMemento());

        // 修改Originator的状态并保存到备忘录
        originator.setState("State 2");
        caretaker.saveMemento(originator.saveStateToMemento());

        // 恢复Originator的状态到之前的备忘录
        originator.undo(caretaker.getMemento(0));
        System.out.println("Current State: " + originator.getState());

        // 再次恢复Originator的状态到之前的备忘录
        originator.undo(caretaker.getMemento(1));
        System.out.println("Current State: " + originator.getState());
    }
}

现在,我们使用了一个List来保存多个备忘录,然后通过指定备忘录的索引来恢复到相应的状态。运行这段代码将得到正确的输出:

Current State: State 1
Current State: State 2

4、使用场景和具体案例

  • 需要实现撤销和恢复功能,让对象能够回到之前的状态。
  • 需要保存对象状态的历史记录,以便可以在需要时进行查看或恢复。

下面以一个文本编辑器为例来演示备忘录设计模式的应用。 假设我们有一个简单的文本编辑器,支持输入文本、撤销和重做操作。在这个场景中,我们可以使用备忘录设计模式来实现撤销和重做功能。

// 备忘录类 - 文本状态
class TextMemento {
    private String text;

    public TextMemento(String text) {
        this.text = text;
    }

    public String getText() {
        return text;
    }
}

// 发起人类 - 文本编辑器
class TextEditor {
    private String text;
    private History history;

    public TextEditor(History history) {
        this.history = history;
    }

    public void setText(String text) {
        this.text = text;
    }

    public String getText() {
        return text;
    }

    public void save() {
        history.push(new TextMemento(text));
    }

    public void undo() {
        TextMemento memento = history.popLast();
        if (memento != null) {
            restore(memento);
        }
    }

    private void restore(TextMemento memento) {
        text = memento.getText();
    }
}

// 负责人类 - 历史记录管理
class History {
    private Stack<TextMemento> mementos = new Stack<>();

    public void push(TextMemento memento) {
        mementos.push(memento);
    }

    public TextMemento popLast() {
        if (mementos.isEmpty()) {
            return null;
        }
        return mementos.pop();
    }
}

public class Main {
    public static void main(String[] args) {
        History history = new History();
        TextEditor editor = new TextEditor(history);

        // 输入第一次文本并保存到历史记录
        editor.setText("Hello, world!");
        System.out.println("1 input Current Text: " + editor.getText());
        editor.save();

        // 修改文本并保存到历史记录
        editor.setText("Hello, world! Have a nice day!");
        System.out.println("2 input Current Text: " + editor.getText());
        editor.save();

        // 输入第二次文本并保存到历史记录
        editor.setText("Goodbye, world!");
        System.out.println("3 input Current Text: " + editor.getText());
        editor.save();

        // 撤销到之前的状态
        editor.undo();
        System.out.println("1 return input Current Text: " + editor.getText());

        // 再次撤销到之前的状态
        editor.undo();
        System.out.println("2 return Current Text: " + editor.getText());
    }
}

输出:

1 input Current Text: Hello, world!
2 input Current Text: Hello, world! Have a nice day!
3 input Current Text: Goodbye, world!
1 return input Current Text: Goodbye, world!
2 return Current Text: Hello, world! Have a nice day!

5、总结

优点:

  • 提供了可撤销和恢复状态的机制,增加了系统的灵活性和可维护性。
  • 将状态的保存和恢复逻辑封装在备忘录对象中,不影响Originator对象的封装性。

缺点:

  • 如果备忘录对象过多或状态较大,可能会占用较大的内存空间。

备忘录设计模式是一种功能强大的模式,适用于需要撤销和恢复操作的场景。合理地使用备忘录模式可以使系统更加灵活和可靠。

九、命令设计模式

1、介绍

命令设计模式是一种行为型设计模式,它允许将请求或操作封装为一个对象,从而使得我们可以将不同的请求参数化,延迟请求的执行,或者将请求放入队列中进行排队处理。通过使用命令模式,我们可以实现解耦请求的发送者和接收者,从而增加系统的灵活性和可扩展性。

2、核心思想

命令设计模式的核心思想是通过将请求封装成一个对象,使得请求的发送者和接收者解耦。具体来说,它包含以下几个关键组件:

  • Command(命令):声明了执行操作的接口,通常包含一个execute方法,用于执行具体的命令。
  • ConcreteCommand(具体命令):实现了Command接口,持有一个接收者对象,并在execute方法中调用接收者的相应操作。
  • Receiver(接收者):负责执行具体操作的对象。
  • Invoker(调用者):持有一个命令对象,并在需要执行命令时调用命令的execute方法。
  • Client(客户端):创建具体命令对象并设置其接收者,将命令对象传递给调用者。

3、组成和基本流程

  • 组成:
    • Command:定义命令接口,包含execute方法。
    • ConcreteCommand:实现Command接口,持有Receiver对象,执行具体命令。
    • Receiver:负责执行实际操作。
    • Invoker:持有Command对象,在需要执行命令时调用Command的execute方法。
    • Client:创建具体的Command对象并设置其接收者,传递给Invoker。
  • 基本流程:
    1. 客户端创建具体的命令对象,并设置其接收者。
    2. 客户端创建调用者对象,并将命令对象传递给调用者。
    3. 调用者在需要执行命令时,调用命令对象的execute方法。
    4. 命令对象调用接收者的具体操作来完成命令的执行。

下面是一个简单的命令设计模式的例子,以一个遥控器控制电灯为例:

// Command(命令)接口
interface Command {
    void execute();
}

// ConcreteCommand(具体命令):控制电灯开启的命令
class LightOnCommand implements Command {
    private Light light;

    public LightOnCommand(Light light) {
        this.light = light;
    }

    @Override
    public void execute() {
        light.turnOn();
    }
}

// ConcreteCommand(具体命令):控制电灯关闭的命令
class LightOffCommand implements Command {
    private Light light;

    public LightOffCommand(Light light) {
        this.light = light;
    }

    @Override
    public void execute() {
        light.turnOff();
    }
}

// Receiver(接收者):电灯类
class Light {
    public void turnOn() {
        System.out.println("Light is ON");
    }

    public void turnOff() {
        System.out.println("Light is OFF");
    }
}

// Invoker(调用者):遥控器类
class RemoteControl {
    private Command command;

    public void setCommand(Command command) {
        this.command = command;
    }

    public void pressButton() {
        command.execute();
    }
}

public class Main {
    public static void main(String[] args) {
        // 创建电灯和对应的命令对象
        Light light = new Light();
        Command lightOnCommand = new LightOnCommand(light);
        Command lightOffCommand = new LightOffCommand(light);

        // 创建遥控器并设置命令
        RemoteControl remoteControl = new RemoteControl();
        remoteControl.setCommand(lightOnCommand);

        // 按下按钮,执行命令
        remoteControl.pressButton();

        // 更改命令为关闭电灯,并再次按下按钮,执行命令
        remoteControl.setCommand(lightOffCommand);
        remoteControl.pressButton();
    }
}

在这个例子中,我们实现了一个简单的遥控器控制电灯的场景。其中,Command接口定义了执行命令的方法execute(),ConcreteCommand类LightOnCommand和LightOffCommand分别用于控制电灯开启和关闭。Receiver类Light表示电灯,它具体执行开启和关闭操作。而Invoker类RemoteControl持有Command对象,在需要执行命令时调用其execute方法。

通过命令设计模式,我们可以轻松地添加新的命令和接收者,实现不同的功能,并且可以支持撤销和重做操作。这样,我们可以将请求的发送者和接收者解耦,增加系统的灵活性和可扩展性。

4、使用场景和具体案例

命令设计模式适用于以下场景:

  • 需要将请求的发送者和接收者解耦,使得系统更加灵活和可扩展。
  • 需要支持请求的撤销、重做和排队等功能。
  • 需要将一系列操作参数化,以便在不同的上下文中执行相似的请求。

当结合真实业务场景时,命令设计模式可以用于实现购物车系统中的"添加商品到购物车"和"从购物车移除商品"等功能。我们来看看如何在Java中实现这个例子:

好的,我会将所有的代码整合到一个代码块内,只保留 Client 类为 public

// Command接口
interface Command {
    void execute();
}

// 添加商品命令类
class AddItemCommand implements Command {
    private ShoppingCart cart;
    private String item;

    public AddItemCommand(ShoppingCart cart, String item) {
        this.cart = cart;
        this.item = item;
    }

    public void execute() {
        cart.addItem(item);
    }
}

// 移除商品命令类
class RemoveItemCommand implements Command {
    private ShoppingCart cart;
    private String item;

    public RemoveItemCommand(ShoppingCart cart, String item) {
        this.cart = cart;
        this.item = item;
    }

    public void execute() {
        cart.removeItem(item);
    }
}

// 购物车类
class ShoppingCart {
    private List<String> items;

    public ShoppingCart() {
        this.items = new ArrayList<>();
    }

    public void addItem(String item) {
        items.add(item);
        System.out.println(item + " 已添加到购物车");
    }

    public void removeItem(String item) {
        if (items.contains(item)) {
            items.remove(item);
            System.out.println(item + " 已从购物车移除");
        } else {
            System.out.println(item + " 不在购物车中");
        }
    }
}

// 客户端类
public class Client {
    public static void main(String[] args) {
        // 创建购物车对象
        ShoppingCart cart = new ShoppingCart();

        // 创建添加商品和移除商品的命令
        Command addItemCommand = new AddItemCommand(cart, "商品A");
        Command removeItemCommand = new RemoveItemCommand(cart, "商品B");

        // 执行命令
        addItemCommand.execute();
        removeItemCommand.execute();
    }
}

这个代码块完整地展示了如何使用命令设计模式来实现购物车系统的功能,并且保持了结构的清晰性。

5、总结

命令设计模式是一种非常有用的设计模式,它可以帮助我们实现请求的封装和参数化,从而提高系统的灵活性和可扩展性。使用命令模式,我们可以将请求的发送者和接收者解耦,使得系统更加易于维护和扩展。虽然它增加了一些类的复杂性,但它带来的好处远远超过了这些额外的开销。

优点

  • 解耦请求的发送者和接收者,提高系统的灵活性和可扩展性。
  • 支持请求的撤销、重做和排队等功能,增加系统的功能性。
  • 可以将一系列操作参数化,实现更加灵活的命令组合。

缺点

  • 可能会引入过多的具体命令类,增加系统的复杂性。

在设计和实现时,我们应该注意将命令设计模式与其他设计模式结合使用,以便在特定场景中获得更好的效果。同时,合理地使用继承和组合等技术,可以减少命令模式的复杂性,使得系统更加易于维护和扩展。

6、和策略设计模式比较

命令设计模式(Command Pattern)关注的是将请求(命令)封装成一个对象,从而允许您参数化客户端与接收者之间的关系。它主要用于实现撤销/重做、队列请求、日志记录等功能。在命令模式中,客户端和接收者是解耦的,客户端不需要知道命令是如何被执行的,只需要调用命令对象的方法即可。

策略设计模式(Strategy Pattern)关注的是定义一系列算法或行为,将它们封装成独立的策略类,并使这些策略类可以相互替换。这样可以让客户端在运行时动态选择算法,而不需要修改客户端代码。策略模式主要用于在不同算法或行为之间进行灵活切换,以实现不同的业务逻辑。

主要区别如下:

  1. 关注点不同

    • 命令设计模式关注的是将请求封装成对象,以支持请求的参数化和解耦客户端与接收者。
    • 策略设计模式关注的是封装不同的算法或行为,并使得它们可以互相替换,从而动态地改变对象的行为。
  2. 角色不同

    • 命令模式的核心角色是命令(Command)对象、调用者(Invoker)和接收者(Receiver)。
    • 策略模式的核心角色是策略(Strategy)接口、具体策略(ConcreteStrategy)类和上下文(Context)。
  3. 应用场景不同

    • 命令模式适用于需要将请求封装为对象,支持撤销/重做、队列请求、日志记录等场景。
    • 策略模式适用于在运行时动态选择算法或行为的场景,以实现不同的业务逻辑。

虽然它们有相似之处,但根据不同的需求和应用场景,选择使用命令模式或策略模式可以更好地组织和设计代码。

十、解释器设计模式

1、介绍

解释器设计模式(Interpreter Pattern)是一种行为型设计模式,属于GOF设计模式中的一员。它通过定义语言的文法表示,以及解释预定义的表达式,来实现对特定问题领域的解释和处理。

2、核心思想

解释器设计模式的核心思想是创建一个解释器,用于解释特定问题领域的表达式和语句。它将每个文法规则表示为一个类,并定义解释方法,以便能够解释和处理特定的语法结构。

3、组成和基本流程

解释器设计模式的组成主要包括以下几个元素:

  1. 抽象表达式(Abstract Expression):定义解释器的接口,包含一个解释方法 interpret(),所有具体表达式都必须实现该接口。
  2. 终结符表达式(Terminal Expression):表示语法规则中的终结符,即不再包含其他表达式的表达式。它实现了抽象表达式的解释方法,并处理表达式的终结符部分。
  3. 非终结符表达式(Non-terminal Expression):表示语法规则中的非终结符,即包含其他表达式的表达式。它实现了抽象表达式的解释方法,并处理表达式的非终结符部分。

解释器设计模式的基本流程如下:

  1. 客户端构建语法树(由多个终结符表达式和非终结符表达式组成)。
  2. 客户端调用解释器的解释方法,对语法树进行解释。

我们将实现一个简单的数学表达式求值器,以演示解释器设计模式的应用

// 抽象表达式
interface Expression {
    int interpret();
}

// 终结符表达式
class NumberExpression implements Expression {
    private int number;

    public NumberExpression(int number) {
        this.number = number;
    }

    @Override
    public int interpret() {
        return number;
    }
}

// 非终结符表达式 - 加法
class AddExpression implements Expression {
    private Expression left;
    private Expression right;

    public AddExpression(Expression left, Expression right) {
        this.left = left;
        this.right = right;
    }

    @Override
    public int interpret() {
        return left.interpret() + right.interpret();
    }
}

// 非终结符表达式 - 减法
class SubtractExpression implements Expression {
    private Expression left;
    private Expression right;

    public SubtractExpression(Expression left, Expression right) {
        this.left = left;
        this.right = right;
    }

    @Override
    public int interpret() {
        return left.interpret() - right.interpret();
    }
}


public class Main {
    public static void main(String[] args) {
        // 构建数学表达式: 7 + (10 - 3)
        Expression left = new NumberExpression(7);
        Expression right = new SubtractExpression(new NumberExpression(10), new NumberExpression(3));
        Expression expression = new AddExpression(left, right);

        // 计算并输出结果
        int result = expression.interpret();
        System.out.println("计算结果: " + result); // 输出:计算结果: 14
    }
}

4、使用场景和具体案例

解释器设计模式适用于以下场景:

  • 当有一个语言需要解释执行,且该语言的文法规则相对稳定,可以使用解释器模式来解释和处理语法规则。
  • 当需要构建一个可以灵活配置的语法树,并且能够支持特定领域的解释和处理。

当结合真实业务场景时,解释器设计模式可以用于处理自定义的查询语言或规则引擎,从而实现对特定领域的解释和处理。一个常见的例子是实现一个简单的规则引擎,用于根据一组规则对输入数据进行评估和决策。

在这个例子中,我们将实现一个简单的规则引擎,用于根据用户输入的条件,对输入数据进行评估。我们将定义一些基本的规则,然后用户可以输入规则条件,规则引擎将根据这些条件对输入数据进行判断和处理。

1、介绍

我们将实现一个简单的规则引擎,用于根据用户输入的规则条件,对输入数据进行评估和处理。该规则引擎将支持基本的比较操作,例如大于、小于、等于等。

2、实现步骤

  1. 定义抽象表达式(Expression)接口:该接口定义了解释器的解释方法 interpret()

  2. 实现终结符表达式(TerminalExpression)类:该类表示规则引擎中的终结符,即用户输入的条件,它实现了抽象表达式接口,并根据输入条件进行解释和评估。

  3. 实现非终结符表达式(NonTerminalExpression)类:该类表示规则引擎中的非终结符,即基于多个终结符条件的组合条件,它实现了抽象表达式接口,并根据组合条件进行解释和评估。

  4. 使用客户端(Client)类:客户端代码创建规则引擎对象,并输入规则条件和待评估的数据,然后调用规则引擎进行解释和处理。

3、代码实现

以下是Java实现的简单规则引擎:

// 抽象表达式
interface Expression {
    boolean interpret(int data);
}

// 终结符表达式 - 大于
class GreaterExpression implements Expression {
    private int value;

    public GreaterExpression(int value) {
        this.value = value;
    }

    public boolean interpret(int data) {
        return data > value;
    }
}

// 终结符表达式 - 小于
class LessExpression implements Expression {
    private int value;

    public LessExpression(int value) {
        this.value = value;
    }

    public boolean interpret(int data) {
        return data < value;
    }
}

// 非终结符表达式 - And
class AndExpression implements Expression {
    private Expression expr1;
    private Expression expr2;

    public AndExpression(Expression expr1, Expression expr2) {
        this.expr1 = expr1;
        this.expr2 = expr2;
    }

    public boolean interpret(int data) {
        return expr1.interpret(data) && expr2.interpret(data);
    }
}

// 非终结符表达式 - Or
class OrExpression implements Expression {
    private Expression expr1;
    private Expression expr2;

    public OrExpression(Expression expr1, Expression expr2) {
        this.expr1 = expr1;
        this.expr2 = expr2;
    }

    public boolean interpret(int data) {
        return expr1.interpret(data) || expr2.interpret(data);
    }
}

// 客户端
public class Client {
    public static void main(String[] args) {
        // 创建规则引擎
        Expression rule1 = new AndExpression(new GreaterExpression(10), new LessExpression(20));
        Expression rule2 = new OrExpression(new GreaterExpression(30), new LessExpression(40));

        // 输入数据
        int data = 15;

        // 判断并输出结果
        boolean result1 = rule1.interpret(data);
        boolean result2 = rule2.interpret(data);

        System.out.println(data + " > 10 and " + data + " < 20: " + result1);
        System.out.println(data + " > 30 or " + data + " < 40: " + result2);
    }
}
4、解析和注释

上述代码实现了一个简单的规则引擎,其中 Expression 接口为抽象表达式,GreaterExpressionLessExpression 类为终结符表达式,表示大于和小于条件。AndExpressionOrExpression 类为非终结符表达式,用于组合多个终结符条件。

客户端代码创建了规则引擎对象 rule1rule2,然后输入数据 data 进行条件判断,并输出结果。在这个例子中,我们对输入数据 15 进行了两个规则条件的判断,分别是大于10并且小于20的结果为 true,以及大于30或小于40的结果为 false

通过这个简单的案例,我们可以看到解释器设计模式在实现规则引擎等特定领域的应用。它通过定义语言的文法表示,并解释预定义的表达式,使得我们能够根据不同的规则条件进行灵活的评估和处理。

5、总结

优点:

  • 灵活性:解释器设计模式可以动态地修改、扩展语法规则,因为每个文法规则都对应一个表达式类。
  • 可维护性:解释器模式将每个表达式规则封装在独立的类中,易于维护和理解。

缺点:

  • 复杂性:对于复杂的文法规则,可能需要创建大量的表达式类,导致代码复杂化。

总结:

解释器设计模式提供了一种灵活的方式来处理特定领域的语法规则和表达式。它适用于特定领域的解释执行,但对于复杂的文法规则,可能会导致代码复杂性增加。在使用时需要权衡其优缺点,并结合实际情况选择是否使用解释器模式来解决问题。

十一、中介设计模式

1、介绍

中介设计模式(Mediator Pattern)是一种行为型设计模式,属于GOF设计模式中的一员。它通过引入中介者来降低多个对象之间的直接通信复杂性,从而提高对象之间的松耦合性。

2、核心思想

中介设计模式的核心思想是引入一个中介者对象,用于协调多个相关对象之间的交互。多个对象不再直接相互通信,而是通过中介者对象进行消息传递,使得对象之间的通信更加灵活和可扩展。

3、组成和基本流程

中介设计模式的组成主要包括以下几个元素:

  1. 中介者(Mediator):定义一个接口用于与各个相关对象通信,它可以是接口或抽象类。

  2. 具体中介者(Concrete Mediator):实现中介者接口,负责协调各个相关对象的交互关系。

  3. 相关对象(Colleague):每个相关对象都知道中介者对象,并通过中介者来与其他相关对象通信。

中介设计模式的基本流程如下:

  1. 客户端创建中介者对象。
  2. 客户端创建相关对象,并将中介者对象传递给相关对象。
  3. 相关对象在需要与其他对象通信时,通过中介者对象进行消息传递。

在下面这个简单的案例中,我们将实现一个简单的飞机交通管制系统,演示中介者设计模式的应用。飞机交通管制系统将处理多个飞机之间的通信和交互,确保它们在空中安全地航行。

1、介绍

在飞机交通管制系统中,我们将实现一个中介者(TrafficMediator)接口,用于处理飞机之间的通信。每个飞机(Aircraft)都将参与交通管制系统,并通过中介者来与其他飞机通信,以避免碰撞和确保安全航行。

2、实现步骤

  1. 定义中介者(TrafficMediator)接口:该接口定义了飞机之间的通信方法。

  2. 实现具体的中介者类(AirTrafficController):该类实现了中介者接口,并负责处理飞机之间的通信和交互。

  3. 定义参与者(Aircraft)接口:该接口定义了飞机参与交通管制系统的方法。

  4. 实现具体的参与者类(ConcreteAircraft):该类实现了参与者接口,表示实际的飞机,可以发送和接收通信。

3、代码实现

以下是Java实现的简单飞机交通管制系统:

// 中介者接口
interface TrafficMediator {
    void sendMessage(String message, Aircraft aircraft);
}

// 具体的中介者类 - 空中交通管制
class AirTrafficController implements TrafficMediator {
    public void sendMessage(String message, Aircraft aircraft) {
        System.out.println("ATC: " + aircraft.getName() + " 发送消息: " + message);
    }
}

// 参与者接口
interface Aircraft {
    void send(String message);
    void receive(String message);
    String getName();
}

// 具体的参与者类 - 飞机
class ConcreteAircraft implements Aircraft {
    private String name;
    private TrafficMediator mediator;

    public ConcreteAircraft(String name, TrafficMediator mediator) {
        this.name = name;
        this.mediator = mediator;
    }

    public void send(String message) {
        System.out.println(name + " 发送消息: " + message);
        mediator.sendMessage(message, this);
    }

    public void receive(String message) {
        System.out.println(name + " 收到消息: " + message);
    }

    public String getName() {
        return name;
    }
}

为了测试代码,我们需要创建客户端代码来创建中介者和飞机对象,并模拟它们之间的通信。以下是一个简单的客户端代码示例:

public class Client {
    public static void main(String[] args) {
        // 创建空中交通管制作为中介者
        TrafficMediator atc = new AirTrafficController();

        // 创建飞机参与者
        Aircraft aircraft1 = new ConcreteAircraft("Flight-001", atc);
        Aircraft aircraft2 = new ConcreteAircraft("Flight-002", atc);

        // 飞机发送和接收消息
        aircraft1.send("请求降落许可。");
        aircraft2.send("已收到,准备降落。");
    }
}

运行上述客户端代码,输出结果如下:

Flight-001 发送消息: 请求降落许可。
ATC: Flight-001 发送消息: 请求降落许可。
Flight-002 发送消息: 已收到,准备降落。
ATC: Flight-002 发送消息: 已收到,准备降落。

在输出结果中,我们可以看到飞机成功发送了消息,并且中介者(空中交通管制)也成功接收到了这些消息。中介者负责将消息传递给目标飞机,并打印相应的消息内容。

这个例子是一个简单的模拟,它演示了中介者设计模式的应用。在实际应用中,中介者模式可以用于处理更复杂的对象间通信和交互,以实现更灵活和解耦的系统设计。

4、解析和注释

上述代码实现了一个简单的飞机交通管制系统,其中 TrafficMediator 接口为中介者,AirTrafficController 类为具体的中介者类,Aircraft 接口为参与者,ConcreteAircraft 类为具体的参与者类。

中介者模式的核心是 TrafficMediator 接口和 AirTrafficController 类。AirTrafficController 类实现了中介者接口的 sendMessage() 方法,用于接收消息并进行传递。ConcreteAircraft 类实现了参与者接口,表示飞机,可以发送消息并接收消息。

在客户端代码中,我们创建了一个空中交通管制作为中介者,并创建了两个飞机作为参与者。飞机可以通过发送消息来进行通信,中介者负责将消息传递给目标飞机。

通过这个简单的例子,我们可以看到中介者设计模式在实现飞机交通管制系统等复杂的对象间通信场景中的应用。它通过引入中介者来降低对象间的直接通信复杂性,从而提高系统的松耦合性和可扩展性。

4、使用场景和具体案例

中介设计模式适用于以下场景:

  • 当多个对象之间存在复杂的关联关系,导致对象之间的通信和交互复杂且难以维护时,可以使用中介者模式来简化对象之间的通信。
  • 当一个对象的行为依赖于其他多个对象时,可以引入中介者来减少对象之间的直接依赖,提高系统的灵活性和可扩展性。
    • 在 Spring MVC 框架中,DispatcherServlet 可以看作是一种中介者模式的中介。

中介者设计模式在真实业务场景中经常用于处理复杂的对象间通信和交互关系。一个典型的例子是聊天应用程序,其中多个用户之间进行实时聊天,中介者设计模式可以用于管理用户之间的消息传递。

在这个例子中,我们将实现一个简单的聊天应用程序,其中有多个用户参与聊天,每个用户可以发送消息给其他用户,中介者将负责将消息传递给目标用户。

1、介绍

在这个聊天应用程序中,我们将实现一个中介者(Mediator)接口,其中包含消息传递的方法。然后,我们将实现具体的中介者类 ChatRoom,用于实现消息传递逻辑。

2、实现步骤

  1. 定义中介者(Mediator)接口:该接口定义了消息传递的方法。

  2. 实现具体的中介者类(ChatRoom):该类实现了中介者接口,并负责实际处理用户之间的消息传递逻辑。

  3. 定义参与者(Participant)接口:该接口定义了用户参与聊天的方法。

  4. 实现具体的参与者类(User):该类实现了参与者接口,表示聊天应用中的用户,可以发送和接收消息。

3、代码实现

以下是Java实现的简单聊天应用程序:

// 中介者接口
interface Mediator {
    void sendMessage(String message, Participant sender);
}

// 具体的中介者类 - 聊天室
class ChatRoom implements Mediator {
    public void sendMessage(String message, Participant sender) {
        System.out.println(sender.getName() + " 发送消息: " + message);
    }
}

// 参与者接口
interface Participant {
    void send(String message);
    void receive(String message);
    String getName();
}

// 具体的参与者类 - 用户
class User implements Participant {
    private String name;
    private Mediator mediator;

    public User(String name, Mediator mediator) {
        this.name = name;
        this.mediator = mediator;
    }

    public void send(String message) {
        System.out.println(name + " 发送消息: " + message);
        mediator.sendMessage(message, this);
    }

    public void receive(String message) {
        System.out.println(name + " 收到消息: " + message);
    }

    public String getName() {
        return name;
    }
}

对于中介者设计模式的简单案例,我们无法实际运行测试,因为它是一个示例代码,不涉及实际的交互或外部依赖。这个例子主要用于说明中介者设计模式的结构和实现,而不是实际运行和交互。

为了运行测试,我们需要创建客户端代码来创建中介者、参与者对象,并模拟它们之间的通信。以下是一个简单的客户端代码示例,用于展示中介者设计模式的运行测试:

public class Client {
    public static void main(String[] args) {
        // 创建聊天室作为中介者
        Mediator chatRoom = new ChatRoom();

        // 创建用户参与者
        Participant user1 = new User("User1", chatRoom);
        Participant user2 = new User("User2", chatRoom);
        Participant user3 = new User("User3", chatRoom);

        // 用户发送和接收消息
        user1.send("Hello, everyone!");
        user2.send("Hi, User1!");
        user3.send("Hey, there!");
    }
}

在这个客户端代码中,我们创建了一个聊天室作为中介者,然后创建了三个用户参与者。用户通过调用 send() 方法来发送消息,中介者将消息传递给其他用户,并调用 receive() 方法来接收消息。输出结果显示每个用户成功发送了消息,并且其他用户成功接收到了这些消息。

User1 发送消息: Hello, everyone!
User2 发送消息: Hi, User1!
User3 发送消息: Hey, there!

这个例子是一个简单的模拟,它演示了中介者设计模式的应用。在实际应用中,中介者模式可以用于处理更复杂的对象间通信和交互,以实现更灵活和解耦的系统设计。

4、解析和注释

上述代码实现了一个简单的聊天应用程序,其中 Mediator 接口为中介者,ChatRoom 类为具体的中介者类,Participant 接口为参与者,User 类为具体的参与者类。

中介者模式的核心是 Mediator 接口和 ChatRoom 类。ChatRoom 类实现了中介者接口的 sendMessage() 方法,用于接收消息并进行传递。User 类实现了参与者接口,表示聊天应用中的用户,可以发送消息并接收消息。

在客户端代码中,我们创建了一个聊天室作为中介者,并创建了两个用户作为参与者。用户可以通过发送消息来进行聊天,中介者负责将消息传递给目标用户。

通过这个简单的例子,我们可以看到中介者设计模式在实现聊天应用程序等复杂的对象间通信场景中的应用。它通过引入中介者来降低对象间的直接通信复杂性,从而提高系统的松耦合性和可扩展性。

5、总结

优点:

  • 降低耦合:中介者设计模式通过引入中介者对象,将对象之间的直接通信转为通过中介者来进行,从而降低对象之间的耦合性。

  • 简化通信:中介者模式可以简化多个对象之间的通信和交互,使得系统的结构更加清晰。

缺点:

  • 中介者对象可能会变得复杂:随着系统中对象之间关联关系的增多,中介者对象可能变得复杂,导致中介者对象本身难以维护。

总结:

中介设计模式是一种有助于降低对象之间直接通信复杂性的设计模式。它适用于需要简化对象之间通信关系的场景。在使用中介者模式时,需要注意中介者对象的复杂性,并权衡其优缺点,确保在特定场景下使用中介者模式能够带来实际的优势。

6、和观察者对比

中介者设计模式和观察者设计模式是两种不同的设计模式,它们的关注点和应用场景有所不同。

中介者设计模式

  1. 关注点:中介者设计模式关注的是将多个对象之间的通信和交互逻辑集中处理,通过引入一个中介者对象,使得对象之间不需要直接相互通信,而是通过中介者进行通信。中介者模式旨在降低对象之间的耦合性,使得对象之间的通信更加灵活和解耦。

  2. 适用场景:中介者设计模式适用于对象之间存在复杂的交互和通信关系的场景,当对象之间的通信逻辑过于复杂且耦合性高时,可以引入一个中介者对象来简化和集中通信逻辑。

观察者设计模式

  1. 关注点:观察者设计模式关注的是对象之间的一对多依赖关系。当一个对象的状态发生变化时,所有依赖它的观察者对象都会得到通知并更新自己的状态。观察者模式旨在建立对象之间的松耦合关系,使得一个对象的变化可以通知到多个观察者对象。

  2. 适用场景:观察者设计模式适用于一个对象的状态变化需要通知多个观察者对象的场景。当一个对象的变化需要影响到多个其他对象时,可以使用观察者模式来建立对象之间的依赖关系。

区别

  1. 目的不同:中介者设计模式的目的是简化对象之间的通信和交互,降低耦合性,而观察者设计模式的目的是建立对象之间的依赖关系,使得一个对象的变化可以通知到多个观察者对象。

  2. 对象关系不同:中介者设计模式中,对象之间的通信是通过中介者对象进行的,对象之间不存在直接的关联。而观察者设计模式中,对象之间存在一对多的依赖关系,观察者对象直接订阅和监听被观察对象的状态变化。

  3. 侧重点不同:中介者设计模式侧重于集中处理对象之间的通信和交互逻辑,使得对象之间的通信更加简单和灵活。观察者设计模式侧重于建立对象之间的松耦合关系,使得一个对象的状态变化可以通知到多个观察者对象。

虽然中介者设计模式和观察者设计模式有不同的关注点和应用场景,但它们都是用于实现对象之间的解耦和通信,从而提高系统的灵活性和可维护性。根据具体的业务需求和设计目标,选择合适的设计模式来组织和管理对象之间的交互关系是很重要的。