23种设计模式之11种行为型模式

11种行为型设计模式

行为型设计模式用于处理对象之间的职责、责任和算法的分配。以下是一些常见的行为型设计模式:

  1. 策略模式(Strategy Pattern):定义一系列算法,将它们封装成独立的策略类,使得它们可以互相替换而不影响客户端。

  2. 观察者模式(Observer Pattern):定义一种一对多的依赖关系,使得当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知和自动更新。

  3. 模板方法模式(Template Method Pattern):定义一个算法的骨架,将一些步骤延迟到子类实现。这样可以确保算法的结构保持不变,但允许不同的子类提供具体实现。

  4. 命令模式(Command Pattern):将请求封装成一个对象,从而使得请求的发送者和接收者解耦。这允许我们根据需要参数化对象,将请求排队或记录日志,并支持撤销操作。

  5. 迭代器模式(Iterator Pattern):提供一种方法来访问聚合对象中的各个元素,而不需要暴露其内部表示。

  6. 责任链模式(Chain of Responsibility Pattern):将请求的发送者和接收者解耦,从而允许多个对象处理请求。请求在对象链中沿着链传递,直到有一个对象处理它。

  7. 中介者模式(Mediator Pattern):定义一个中介对象来封装一组对象之间的交互。中介者使得各个对象之间不需要显式地相互引用,从而减少耦合。

  8. 备忘录模式(Memento Pattern):允许在不破坏封装的前提下捕获一个对象的内部状态,并在需要时将其恢复。

  9. 状态模式(State Pattern):允许对象在其内部状态发生改变时改变其行为。对象看起来好像修改了其类。

  10. 访问者模式(Visitor Pattern):定义一种将操作从对象结构中分离出来的方式。通过在不改变对象的类的前提下,可以在不同的对象上执行不同的操作。

  11. 解释器模式(Interpreter Pattern):定义一种语言的文法规则,并为该语言创建一个解释器,以解释处理用户的输入。

责任链模式

  我们需要设计一个日志记录系统,希望根据日志的不同级别将日志消息发送到不同的目标(例如文件、数据库、控制台)。在这种情况下,我们可以使用责任链模式来处理不同级别的日志消息。

首先定义一个日志处理器接口和几个具体的日志处理器类:

// 日志处理器接口
interface LogHandler {
    void setNextHandler(LogHandler nextHandler);
    void handleLog(String message, LogLevel logLevel);
}

// 控制台日志处理器
class ConsoleLogHandler implements LogHandler {
    private LogHandler nextHandler;

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

    @Override
    public void handleLog(String message, LogLevel logLevel) {
        if (logLevel == LogLevel.CONSOLE) {
            System.out.println("Console Log: " + message);
        } else if (nextHandler != null) {
            nextHandler.handleLog(message, logLevel);
        }
    }
}

// 文件日志处理器
class FileLogHandler implements LogHandler {
    private LogHandler nextHandler;

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

    @Override
    public void handleLog(String message, LogLevel logLevel) {
        if (logLevel == LogLevel.FILE) {
            System.out.println("File Log: " + message);
        } else if (nextHandler != null) {
            nextHandler.handleLog(message, logLevel);
        }
    }
}

// 数据库日志处理器
class DatabaseLogHandler implements LogHandler {
    private LogHandler nextHandler;

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

    @Override
    public void handleLog(String message, LogLevel logLevel) {
        if (logLevel == LogLevel.DATABASE) {
            System.out.println("Database Log: " + message);
        } else if (nextHandler != null) {
            nextHandler.handleLog(message, logLevel);
        }
    }
}

定义 LogLevel枚举类型,用于表示日志的不同级别。

// 日志级别枚举
enum LogLevel {
    CONSOLE, // 控制台日志级别
    FILE,    // 文件日志级别
    DATABASE // 数据库日志级别
}

  在上述代码中,每个日志处理器可以处理特定级别的日志消息,如果无法处理,它会将请求传递给下一个处理器。

然后,我们在客户端使用责任链模式来处理不同级别的日志消息:

public class ChainOfResponsibilityLogDemo {
    public static void main(String[] args) {
        LogHandler consoleHandler = new ConsoleLogHandler();
        LogHandler fileHandler = new FileLogHandler();
        LogHandler databaseHandler = new DatabaseLogHandler();

        consoleHandler.setNextHandler(fileHandler);
        fileHandler.setNextHandler(databaseHandler);

        consoleHandler.handleLog("Console log message", LogLevel.CONSOLE);
        consoleHandler.handleLog("File log message", LogLevel.FILE);
        consoleHandler.handleLog("Database log message", LogLevel.DATABASE);
    }
}

  在这个示例中,我们创建了三个不同级别的日志处理器,并将它们连接起来形成一个处理链。当客户端提交不同级别的日志消息时,消息会按照链的顺序被不同的日志处理器处理,直到找到合适的处理器或者处理器链结束。

  责任链模式的设计思想是将请求和处理分离,使得多个对象都有机会处理请求,从而实现解耦和灵活性。这种模式适用于需要依次处理某种请求的情况,每个处理器都可以有自己的处理逻辑和条件。

命令模式

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

  1. 需要将请求发送者和接收者解耦: 当需要将请求发送者和接收者之间的关系解耦,使得它们可以独立变化时,命令模式是一个很好的选择。这使得请求发送者无需知道接收者的具体情况,只需要知道如何发送命令。

  2. 需要支持撤销和重做操作: 命令模式的一个重要特性是可以很容易地支持撤销和重做操作。通过存储命令的历史记录,可以实现对之前操作的撤销和重做。

  3. 需要将一组操作参数化: 命令模式可以将操作(命令)参数化,使得可以使用不同的参数来执行相同的操作。这在需要根据不同情况执行类似操作时非常有用。

  4. 需要支持排队执行请求: 命令模式可以很容易地支持将请求排队,以便按照顺序执行它们,或者实现一些调度操作。

  5. 需要支持日志和审计功能: 由于命令对象封装了操作和参数,因此可以很容易地记录操作日志和执行历史,以实现审计和日志记录功能。

  6. 需要实现回调机制: 在某些情况下,命令模式可以作为一种简单的回调机制的替代方案,从而实现异步或事件驱动的操作。

  需要注意的是,虽然命令模式能够提供很多优势,但在简单情况下可能会引入额外的复杂性。在设计中,应该根据实际情况来决定是否使用命令模式。

  让我们通过一个例子来演示如何使用命令模式来实现日志记录功能。

  我们有一个文本编辑器应用程序,我们希望能够记录用户对文本进行的操作,例如插入文本、删除文本等,并将这些操作记录到日志文件中。

  首先,我们定义一个命令接口和一些具体的命令类:

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

// 插入文本命令
class InsertTextCommand implements Command {
    private TextEditor textEditor;
    private String text;

    public InsertTextCommand(TextEditor textEditor, String text) {
        this.textEditor = textEditor;
        this.text = text;
    }

    @Override
    public void execute() {
        textEditor.insertText(text);
    }
}

// 删除文本命令
class DeleteTextCommand implements Command {
    private TextEditor textEditor;
    private String text;

    public DeleteTextCommand(TextEditor textEditor, String text) {
        this.textEditor = textEditor;
        this.text = text;
    }

    @Override
    public void execute() {
        textEditor.deleteText(text);
    }
}

  然后,我们定义一个文本编辑器类,用于执行文本操作并记录日志:

import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

// 文本编辑器类
class TextEditor {
    private StringBuilder content = new StringBuilder();
    private List log = new ArrayList<>();

    public void insertText(String text) {
        content.append(text);
        log.add("Inserted: " + text);
    }

    public void deleteText(String text) {
        int index = content.indexOf(text);
        if (index != -1) {
            content.delete(index, index + text.length());
            log.add("Deleted: " + text);
        }
    }

    public void saveLogToFile(String filename) throws IOException {
        try (FileWriter writer = new FileWriter(filename)) {
            for (String entry : log) {
                writer.write(entry + "\n");
            }
        }
    }
}

  在这个例子中,TextEditor 类维护了文本内容和日志记录列表。每次执行插入或删除操作时,会将操作记录添加到日志中。

  最后,我们在客户端使用命令模式来记录用户操作并保存日志:

public class CommandLogDemo {
    public static void main(String[] args) throws IOException {
        TextEditor textEditor = new TextEditor();
        Command insertCommand = new InsertTextCommand(textEditor, "Hello, ");
        Command deleteCommand = new DeleteTextCommand(textEditor, "Hello, ");

        // 执行命令并记录日志
        insertCommand.execute();
        deleteCommand.execute();

        // 保存日志到文件
        textEditor.saveLogToFile("log.txt");
    }
}

  在这个案例中,我们使用命令模式将文本操作封装成命令对象,并在命令的 execute 方法中记录操作到日志中。通过调用 saveLogToFile 方法,我们将操作日志保存到文件中,从而实现了审计和日志记录功能。

  这个例子演示了命令模式如何支持在操作执行时记录操作日志,以及如何在需要时将日志保存到文件中。这是一个常见的应用场景,用于系统监控、审计和故障排查等情况。

解释器模式

  我们讨论解释器模式时,最好的方式是通过一个实际的例子来展示其设计思想和用途。让我们考虑一个用于处理简单数学表达式的解释器模式示例。

  假设我们有一个简单的数学表达式语言,它支持对两个数字进行加法和减法操作。我们想要能够解析这些表达式并计算结果。

  首先,我们需要定义一些组成表达式的组件:

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

// 数字表达式
class NumberExpression implements Expression {
    private int value;

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

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

// 加法表达式
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 InterpreterPatternDemo {
    public static void main(String[] args) {
        Expression expression = new AddExpression(
            new NumberExpression(10),
            new SubtractExpression(
                new NumberExpression(5),
                new NumberExpression(2)
            )
        );

        int result = expression.interpret();
        System.out.println("Result: " + result); // Output: 7
    }
}

  在这个案例中,我们通过使用解释器模式来解析和计算简单的数学表达式。每个具体表达式类都实现了 Expression 接口,然后可以通过组合不同的表达式来构建复杂的表达式。

  解释器模式的设计思想是,将一个复杂的问题拆分成一系列简单的表达式,然后通过组合这些表达式来解决问题。在这个例子中,我们将加法和减法操作分别封装成不同的表达式类,然后通过组合这些表达式来构建和计算复杂的数学表达式。

  解释器模式的优势在于,它可以很容易地扩展支持新的表达式类型,并且可以实现更复杂的文法规则。它适用于构建自定义领域特定语言(DSL)、过滤器、查询语言等场景,其中需要解释和执行特定语法的表达式。

迭代器模式

  当我们需要遍历集合或数据结构中的元素时,迭代器模式就会派上用场。迭代器模式提供了一种通用的方式来访问集合中的元素,而不必暴露集合的内部结构。它将迭代逻辑从集合中分离出来,使得我们可以在不影响集合的情况下更改迭代方式。

迭代器模式由两个主要组件组成:迭代器和可迭代对象。

迭代器(Iterator):迭代器是一个接口,它定义了遍历集合元素的方法,包括获取下一个元素、判断是否还有下一个元素等操作。

可迭代对象(Iterable):可迭代对象是一个接口,它定义了返回迭代器的方法。任何实现了可迭代对象接口的类都可以提供一个用于遍历其元素的迭代器。

让我们通过一个简单的例子来理解迭代器模式:

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

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

// 可迭代对象接口
interface Iterable {
    Iterator iterator();
}

// 自定义集合类
class MyCollection implements Iterable {
    private List items = new ArrayList<>();

    public void addItem(T item) {
        items.add(item);
    }

    @Override
    public Iterator iterator() {
        return new MyIterator<>(items);
    }
}

// 自定义迭代器类
class MyIterator implements Iterator {
    private List items;
    private int position = 0;

    public MyIterator(List items) {
        this.items = items;
    }

    @Override
    public boolean hasNext() {
        return position < items.size();
    }

    @Override
    public T next() {
        T item = items.get(position);
        position++;
        return item;
    }
}

public class IteratorPatternDemo {
    public static void main(String[] args) {
        MyCollection collection = new MyCollection<>();
        collection.addItem("Item 1");
        collection.addItem("Item 2");
        collection.addItem("Item 3");

        Iterator iterator = collection.iterator();
        while (iterator.hasNext()) {
            String item = iterator.next();
            System.out.println("Item: " + item);
        }
    }
}

  在这个例子中,我们定义了一个自定义集合类 MyCollection,它实现了可迭代对象接口。我们还定义了一个自定义迭代器类 MyIterator,它实现了迭代器接口。

  通过使用迭代器模式,我们可以在不暴露集合内部结构的情况下遍历集合中的元素。这种分离使得我们可以更改迭代方式而不影响集合类。这有助于提高代码的可维护性和灵活性。

总结:

  迭代器模式适用于需要遍历集合或数据结构中的元素,并且希望将遍历逻辑与集合的内部结构分离的情况。它可以提供一种通用的方式来遍历不同类型的集合,使得代码更具可复用性和可扩展性。

中介者模式

  中介者模式旨在降低多个对象之间的耦合度,通过引入一个中介者对象来管理这些对象之间的通信。中介者模式使得对象之间的交互变得简单,而不是直接相互引用,从而使系统更加灵活和可维护。

让我们通过一个简单的聊天室案例来理解中介者模式:

首先,定义一个中介者接口和不同类型的聊天室成员类:

// 中介者接口
interface ChatMediator {
    void sendMessage(String message, User user);
}

// 聊天室成员类
class User {
    private String name;
    private ChatMediator mediator;

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

    public void sendMessage(String message) {
        mediator.sendMessage(message, this);
    }

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

    public String getName() {
        return name;
    }

}

再创建一个具体的中介者类来管理聊天室成员之间的通信:

// 具体中介者类
class ChatRoom implements ChatMediator {
    @Override
    public void sendMessage(String message, User user) {
        System.out.println(user.getName() + " sent message: " + message);
        // 在实际应用中,还可以将消息发送给其他用户
    }
}

在客户端使用中介者模式:

public class MediatorPatternDemo {
    public static void main(String[] args) {
        ChatMediator chatRoom = new ChatRoom();

        User user1 = new User("Alice", chatRoom);
        User user2 = new User("Bob", chatRoom);

        user1.sendMessage("Hi, Bob!");
        user2.sendMessage("Hello, Alice!");
    }
}

  在这个案例中,中介者模式的设计思想是,通过引入一个中介者 ChatMediator 来管理聊天室成员之间的通信。每个用户 User 都有一个中介者的引用,并通过中介者来发送和接收消息。这样,用户之间的通信不再直接相互引用,而是通过中介者来处理。

中介者模式的好处在于:

  1. 降低耦合度:对象之间的交互逻辑被封装在中介者中,使得对象之间的耦合度降低,易于维护和扩展。
  2. 简化对象间通信:通过中介者统一管理对象间的通信,减少了对象之间的直接相互引用,使得通信更加简单和集中。
  3. 支持集中控制:中介者模式允许在中介者中集中控制对象之间的交互,可以实现复杂的逻辑控制。
  4. 增加可扩展性:当系统需要新增或修改交互逻辑时,只需修改中介者类,而不需要修改其他对象。

  总之,中介者模式适用于需要降低对象之间耦合度、简化对象间通信、集中控制交互逻辑等情况。它可以使系统更加灵活、可维护和可扩展。

备忘录模式

  备忘录模式是一种行为型设计模式,它允许在不破坏封装的前提下捕获一个对象的内部状态,并在对象之间保存这个状态,从而在以后恢复对象到这个状态。备忘录模式通常用于需要记录和恢复对象状态的场景,比如撤销操作或者状态历史记录。

下面是一个使用Java代码展示备忘录模式的简单案例:

首先,定义备忘录类和原始对象类:

// 备忘录类
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 restoreStateFromMemento(Memento memento) {
        state = memento.getState();
    }
}

实现一个管理备忘录的类:

// 管理备忘录的类
class Caretaker {
    private List mementos = new ArrayList<>();

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

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

在客户端使用备忘录模式来保存和恢复对象的状态:

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

        originator.setState("State 1");
        caretaker.addMemento(originator.saveStateToMemento());

        originator.setState("State 2");
        caretaker.addMemento(originator.saveStateToMemento());

        originator.setState("State 3");
        caretaker.addMemento(originator.saveStateToMemento());

        System.out.println("Current State: " + originator.getState());

        originator.restoreStateFromMemento(caretaker.getMemento(1));
        System.out.println("Restored State: " + originator.getState());
    }
}

备忘录模式的优势在于:

  1. 封装状态: 备忘录模式允许将对象的状态封装在备忘录对象中,从而实现了状态的封装和隔离,不会影响到原始对象的封装和操作。

  2. 撤销和恢复: 备忘录模式可以用于实现撤销操作和恢复状态,用户可以通过保存多个备忘录对象来实现状态的历史记录,从而可以在需要时恢复到之前的某个状态。

  3. 不破坏封装: 备忘录模式通过将状态保存在备忘录对象中,避免了直接在原始对象中暴露状态信息,从而保持了对象的封装性。

备忘录模式的劣势在于:

  1. 资源消耗: 如果要保存大量的历史状态,可能会消耗大量的内存资源,特别是在需要频繁保存状态的情况下。

  2. 复杂性增加: 在某些情况下,实现备忘录模式可能会引入额外的复杂性,特别是当需要管理多个备忘录对象或者需要跟踪多个对象的状态时。

总之,备忘录模式在需要记录和恢复对象状态的场景中非常有用,它提供了一种有效的方式来实现撤销、恢复以及状态历史记录等功能。

  如果您的应用程序已经有数据库可以进行状态的保存和恢复,那么备忘录模式可能就不再是必需的。数据库可以提供更可靠的数据存储和恢复机制,备忘录模式通常用于在内存中保存对象状态的临时需求,比如支持撤销操作或者状态历史记录。

  使用备忘录模式的优势在于它提供了一种轻量级的状态保存和恢复方式,适用于那些需要临时性保存状态,而又不需要频繁访问数据库的情况。但如果您已经有了数据库来处理持久化数据,那么可以优先考虑使用数据库来实现状态的保存和恢复,而备忘录模式则可能不再是必要的选择。

  总之,根据您的具体应用需求和现有架构,决定是否使用备忘录模式是一个权衡和取舍的过程。如果数据库能够满足您的状态保存和恢复需求,那么可以优先考虑使用数据库来实现。如果您需要临时保存状态,并且数据库访问代价较高,备忘录模式可能会更加适合。

观察者模式

  观察者模式是一种行为型设计模式,它用于在对象之间建立一种一对多的依赖关系,使得一个主题对象的状态变化可以通知并自动更新依赖于它的多个观察者对象。观察者模式的设计思想在于解耦主题对象和观察者对象,使得它们可以独立地变化而互不影响。

以下是一个简单的例子来展示观察者模式的设计思想和优势:

社交媒体平台中的关注功能。在这个案例中,我们可以使用观察者模式来实现用户关注其他用户的功能。

首先,定义观察者接口和主题接口:

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

// 主题接口
interface Influencer {
    void addFollower(Follower follower);
    void removeFollower(Follower follower);
    void notifyFollowers(String message);
}

实现具体的观察者和主题类:

import java.util.ArrayList;
import java.util.List;

// 具体观察者类
class User implements Follower {
    private String name;

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

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

// 具体主题类
class SocialMediaInfluencer implements Influencer {
    private List followers = new ArrayList<>();
    private String name;

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

    @Override
    public void addFollower(Follower follower) {
        followers.add(follower);
    }

    @Override
    public void removeFollower(Follower follower) {
        followers.remove(follower);
    }

    @Override
    public void notifyFollowers(String message) {
        System.out.println(name + " posted: " + message);
        for (Follower follower : followers) {
            follower.update(name + " posted: " + message);
        }
    }
}

最后,我们在客户端使用观察者模式来模拟用户关注和社交媒体发布:

public class ObserverSocialMediaDemo {
    public static void main(String[] args) {
        SocialMediaInfluencer influencer = new SocialMediaInfluencer("John");
        User user1 = new User("Alice");
        User user2 = new User("Bob");

        influencer.addFollower(user1);
        influencer.addFollower(user2);

        influencer.notifyFollowers("New video is up!");

        influencer.removeFollower(user2);

        influencer.notifyFollowers("Live stream starting soon!");
    }
}

  在这个案例中,SocialMediaInfluencer 充当主题,表示社交媒体上的一个用户,而 User 充当观察者,表示关注这个用户的其他用户。当 SocialMediaInfluencer 发布新内容时,会通知所有关注者。

  通过这个案例,我们可以看到观察者模式的设计思想,它使得关注者和被关注者能够解耦,允许用户动态地关注和取消关注,同时保持关注者与被关注者之间的状态同步。这在社交媒体平台中的关注功能是非常有用的。

观察者模式的优势在于:

  1. 解耦和灵活性: 观察者模式将主题和观察者解耦,使得它们可以独立变化而不互相影响。当主题的状态发生变化时,可以动态地通知多个观察者,而不需要修改主题和观察者的代码。

  2. 可扩展性: 可以随时增加新的观察者,无需修改主题代码。同样,也可以新增主题,让已有观察者关注新的主题。

  3. 开放封闭原则: 观察者模式遵循开放封闭原则,允许在不修改现有代码的情况下添加新的观察者。

观察者模式的劣势在于:

  1. 可能引起性能问题: 当观察者过多或者主题的状态频繁变化时,通知所有观察者可能引起性能问题。

  2. 可能引发循环依赖: 如果观察者之间存在循环依赖,可能导致系统不稳定。

  总之,观察者模式在很多情况下是一种非常有用的设计模式,特别是在需要实现对象间的动态通知和状态同步的场景。它可以使系统更加灵活、解耦,但在使用时需要权衡好性能和设计结构。

状态模式

  状态模式是一种行为型设计模式,它允许对象在内部状态发生改变时改变它的行为,从外部看来好像是修改了它的类。状态模式的核心思想是将不同的状态封装成独立的类,每个状态类都实现了一个共同的接口,然后在上下文对象中维护一个指向当前状态的引用,根据不同状态的切换来改变对象的行为。

状态模式的主要参与者有:

  1. Context(上下文): 维护一个指向具体状态对象的引用,负责将客户端的请求委派给当前状态对象来处理。

  2. State(状态接口): 定义了一个共同的接口,包含了具体状态类需要实现的方法。

  3. ConcreteState(具体状态): 实现了状态接口,代表了具体的状态,负责处理相关的操作和状态转换。

让我们通过一个例子来更好地理解状态模式的设计思想和优势。

在线购物平台的订单处理系统。在这个案例中,订单可能有不同的状态,例如待支付、已支付、已发货、已完成等。每个状态下,订单的行为和处理逻辑都是不同的。我们可以使用状态模式来实现这个订单处理系统。

首先,定义状态接口和具体状态类:

// 状态接口
interface OrderState {
    void process(Order order);
}

// 具体状态类 - 待支付
class PendingPaymentState implements OrderState {
    @Override
    public void process(Order order) {
        System.out.println("Processing order with pending payment...");
        // 其他处理逻辑
    }
}

// 具体状态类 - 已支付
class PaidState implements OrderState {
    @Override
    public void process(Order order) {
        System.out.println("Processing order with paid payment...");
        // 其他处理逻辑
    }
}

// 具体状态类 - 已发货
class ShippedState implements OrderState {
    @Override
    public void process(Order order) {
        System.out.println("Processing order with shipped item...");
        // 其他处理逻辑
    }
}

// 具体状态类 - 已完成
class CompletedState implements OrderState {
    @Override
    public void process(Order order) {
        System.out.println("Processing order with completed status...");
        // 其他处理逻辑
    }
}

定义订单类 Order 和订单状态的切换方法:

// 环境类
class Order {
    private OrderState currentState;

    public Order() {
        currentState = new PendingPaymentState();
    }

    public void setState(OrderState state) {
        currentState = state;
    }

    public void process() {
        currentState.process(this);
    }
}

在客户端使用状态模式来操作订单:

public class StateOrderProcessingDemo {
    public static void main(String[] args) {
        Order order = new Order();

        order.process(); // Processing order with pending payment...

        order.setState(new PaidState());
        order.process(); // Processing order with paid payment...

        order.setState(new ShippedState());
        order.process(); // Processing order with shipped item...

        order.setState(new CompletedState());
        order.process(); // Processing order with completed status...
    }
}

  在这个案例中,状态模式使得订单状态和行为之间的关联更加清晰,每个状态的处理逻辑都被封装到独立的状态类中。当订单状态变化时,只需要切换状态并调用 process 方法即可,无需关心具体的状态逻辑。

  通过这个案例,我们可以更好地理解状态模式的优势,它允许我们将不同状态的行为逻辑进行解耦,使得代码更加模块化、易于维护和扩展。状态模式在处理具有多个状态和状态转换的情况下非常有用。

策略模式

一个购物折扣计算系统,其中涉及不同的折扣策略来计算购物车中商品的总价。

策略模式示例 - 折扣策略:

在购物折扣计算系统中,有不同的折扣策略,例如无折扣、满减折扣、固定折扣等。

// 策略接口
interface DiscountStrategy {
    double applyDiscount(double totalPrice);
}

// 具体策略类 - 无折扣
class NoDiscount implements DiscountStrategy {
    @Override
    public double applyDiscount(double totalPrice) {
        return totalPrice;
    }
}

// 具体策略类 - 满减折扣
class FlatDiscount implements DiscountStrategy {
    private double discountAmount;

    public FlatDiscount(double discountAmount) {
        this.discountAmount = discountAmount;
    }

    @Override
    public double applyDiscount(double totalPrice) {
        if (totalPrice >= discountAmount) {
            return totalPrice - discountAmount;
        }
        return totalPrice;
    }
}

// 具体策略类 - 固定折扣
class PercentageDiscount implements DiscountStrategy {
    private double percentage;

    public PercentageDiscount(double percentage) {
        this.percentage = percentage;
    }

    @Override
    public double applyDiscount(double totalPrice) {
        return totalPrice * (1 - percentage / 100);
    }
}

// 上下文类 - 购物车
class ShoppingCart {
    private DiscountStrategy discountStrategy;

    public void setDiscountStrategy(DiscountStrategy discountStrategy) {
        this.discountStrategy = discountStrategy;
    }

    public double calculateTotalPrice(double totalPrice) {
        return discountStrategy.applyDiscount(totalPrice);
    }
}

策略模式示例 - 使用不同的折扣策略:

public class StrategyPatternExample {
    public static void main(String[] args) {
        ShoppingCart cart = new ShoppingCart();
        
        // 无折扣
        cart.setDiscountStrategy(new NoDiscount());
        double totalPrice1 = cart.calculateTotalPrice(100.0);
        System.out.println("Total Price (No Discount): " + totalPrice1);
        
        // 满减折扣
        cart.setDiscountStrategy(new FlatDiscount(20.0));
        double totalPrice2 = cart.calculateTotalPrice(100.0);
        System.out.println("Total Price (Flat Discount): " + totalPrice2);
        
        // 百分比折扣
        cart.setDiscountStrategy(new PercentageDiscount(10.0));
        double totalPrice3 = cart.calculateTotalPrice(100.0);
        System.out.println("Total Price (Percentage Discount): " + totalPrice3);
    }
}

  在这个例子中,我们通过策略模式实现了不同的折扣策略来计算购物车中商品的总价。根据不同的折扣策略,购物车可以应用不同的算法,而无需更改购物车本身的代码。这种灵活性和可扩展性是策略模式的一个优势。

策略模式的优势包括:

优势:

  1. 灵活性: 策略模式允许在运行时动态地改变对象的行为,从而实现了算法的灵活替换。

  2. 解耦: 策略模式将具体算法封装到独立的策略类中,减少了策略和使用者之间的耦合,使得代码更加可维护和扩展。

  3. 可扩展性: 可以很方便地增加新的策略类,无需修改已有代码,符合开闭原则。

劣势:

  1. 增加类的数量: 对于简单的算法,可能会导致策略类的增多,增加了类的数量。

  2. 客户端负担: 客户端需要了解不同的策略,选择合适的策略,增加了客户端的负担。

策略模式对比状态模式的区别 

  1. 关注点不同:

    • 策略模式: 策略模式关注于在不同情况下选择不同的算法或策略来完成任务,它的重点在于选择合适的策略。
    • 状态模式: 状态模式关注于对象在不同状态下的行为变化,它的重点在于状态的切换和状态下的行为。
  2. 用途不同:

    • 策略模式: 策略模式通常用于实现同一种任务的不同算法,以便在不同情况下选择最合适的算法。
    • 状态模式: 状态模式通常用于对象具有多种状态,且状态之间的行为和逻辑不同,以实现状态之间的无缝转换。
  3. 关系不同:

    • 策略模式: 在策略模式中,上下文对象持有一个策略对象,根据需要切换不同的策略来实现任务。
    • 状态模式: 在状态模式中,上下文对象持有一个状态对象,状态对象之间的切换是由上下文对象控制的。
  4. 状态转换方式不同:

    • 策略模式: 策略模式中的策略之间通常没有直接关联,它们是独立的,不存在明确的状态转换。
    • 状态模式: 状态模式中的状态之间存在明确的转换关系,通常由上下文对象根据条件或事件触发状态转换。

总之,策略模式用于在不同算法之间选择,而状态模式用于管理对象在不同状态下的行为变化和状态转换。根据具体的需求和场景,选择适合的模式可以更好地实现目标。

当比较策略模式和状态模式时,一个很好的例子是在线购物系统中的订单状态管理。

策略模式示例 - 订单支付策略:

在在线购物系统中,订单有不同的支付策略,例如使用信用卡支付、支付宝支付、微信支付等。这些策略决定了订单的支付方式。

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

// 具体策略类 - 信用卡支付
class CreditCardPayment implements PaymentStrategy {
    @Override
    public void pay(double amount) {
        // 使用信用卡支付
        // ...
    }
}

// 具体策略类 - 支付宝支付
class AlipayPayment implements PaymentStrategy {
    @Override
    public void pay(double amount) {
        // 使用支付宝支付
        // ...
    }
}

// 具体策略类 - 微信支付
class WeChatPayment implements PaymentStrategy {
    @Override
    public void pay(double amount) {
        // 使用微信支付
        // ...
    }
}

// 上下文类 - 订单
class Order {
    private PaymentStrategy paymentStrategy;

    public void setPaymentStrategy(PaymentStrategy paymentStrategy) {
        this.paymentStrategy = paymentStrategy;
    }

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

状态模式示例 - 订单状态管理:

在在线购物系统中,订单有不同的状态,如待支付状态、已支付状态、已发货状态等。不同状态下订单的行为和响应也是不同的。

// 状态接口
interface OrderState {
    void processPayment(double amount);
    void shipOrder();
}

// 具体状态类 - 待支付状态
class PendingPaymentState implements OrderState {
    @Override
    public void processPayment(double amount) {
        // 处理支付
        // ...
    }

    @Override
    public void shipOrder() {
        // 无法发货,不处理
    }
}

// 具体状态类 - 已支付状态
class PaidState implements OrderState {
    @Override
    public void processPayment(double amount) {
        // 无需处理,已支付
    }

    @Override
    public void shipOrder() {
        // 发货,切换到已发货状态
    }
}

// 具体状态类 - 已发货状态
class ShippedState implements OrderState {
    @Override
    public void processPayment(double amount) {
        // 无需处理,已发货
    }

    @Override
    public void shipOrder() {
        // 无需处理,已发货
    }
}

// 上下文类 - 订单
class Order {
    private OrderState currentState;

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

    public void processPayment(double amount) {
        currentState.processPayment(amount);
    }

    public void shipOrder() {
        currentState.shipOrder();
    }
}

  在这两个例子中,我们可以看到策略模式和状态模式的不同。在策略模式中,我们根据支付策略选择订单的支付方式,每个策略封装了不同的支付逻辑。而在状态模式中,我们根据订单的状态来确定订单的行为,每个状态封装了不同的行为逻辑。这再次强调了策略模式关注于选择算法,而状态模式关注于状态下的行为。 

模板方法模式

  模板方法模式是一种行为型设计模式,其设计思想是定义一个算法的骨架,将算法中一些步骤的实现延迟到子类中。这样可以在不改变算法结构的情况下,允许子类为其中的一些步骤提供具体的实现。

  模板方法模式适用于一组算法有相似的流程,但其中的某些步骤可能不同或变化的情况。它通过在父类中定义模板方法,将具体的步骤延迟到子类中来实现,以达到复用和扩展的目的。

以下是一个简单的模板方法模式示例,以制作咖啡和茶为例:

// 抽象类 - 饮料
abstract class Beverage {
    final void prepareBeverage() {
        boilWater();
        brew();
        pourInCup();
        if (customerWantsCondiments()) {
            addCondiments();
        }
    }

    abstract void brew();
    abstract void addCondiments();

    void boilWater() {
        System.out.println("Boiling water");
    }

    void pourInCup() {
        System.out.println("Pouring into cup");
    }

    // 钩子方法
    boolean customerWantsCondiments() {
        return true;
    }
}

// 具体类 - 咖啡
class Coffee extends Beverage {
    @Override
    void brew() {
        System.out.println("Dripping coffee through filter");
    }

    @Override
    void addCondiments() {
        System.out.println("Adding sugar and milk");
    }
}

// 具体类 - 茶
class Tea extends Beverage {
    @Override
    void brew() {
        System.out.println("Steeping the tea");
    }

    @Override
    void addCondiments() {
        System.out.println("Adding lemon");
    }

    @Override
    boolean customerWantsCondiments() {
        // 覆盖钩子方法
        return false;
    }
}

public class TemplateMethodPatternExample {
    public static void main(String[] args) {
        Beverage coffee = new Coffee();
        coffee.prepareBeverage();

        System.out.println();

        Beverage tea = new Tea();
        tea.prepareBeverage();
    }
}

设计优势:

  1. 提供算法框架: 模板方法模式定义了算法的框架,使算法的结构更清晰,同时也保护了算法的核心步骤不被修改。

  2. 促进复用: 模板方法模式通过在父类中定义算法的共同部分,促使子类中的代码复用,减少重复性代码。

  3. 灵活性与扩展性: 子类可以根据需要重写模板方法中的某些步骤,从而灵活地扩展或变化算法的实现。

  4. 可维护性: 将算法的核心步骤集中在模板方法中,使维护和修改变得更容易,而不必在多个子类中进行修改。

  总而言之,模板方法模式是一种非常有用的设计模式,通过将算法的骨架和核心步骤定义在父类中,然后将具体实现延迟到子类中,可以提高代码的复用性、可维护性和扩展性。

访问者模式

  访问者模式是一种行为型设计模式,它用于在不改变现有类结构的情况下,定义对一组不同类型的对象进行操作的新方式。访问者模式将数据结构和操作分离,使得可以在不修改元素类的情况下增加新的操作。

访问者模式的设计优势主要体现在以下几个方面:

  1. 新增操作不影响元素类: 当需要添加新的操作或功能时,不需要修改已有的元素类,只需要创建一个新的访问者类来实现新的操作。

  2. 将操作集中管理: 访问者模式将各种不同的操作集中到访问者类中,使得代码结构更加清晰,易于维护和扩展。

  3. 支持多态性: 访问者模式利用了双重分派,即在运行时根据元素的实际类型动态调用正确的访问者方法,从而实现了多态性。

  4. 避免污染元素类: 通常情况下,如果直接在元素类中添加新的操作,可能会导致元素类变得臃肿。访问者模式避免了这种问题,保持了元素类的单一职责。

  5. 增加新元素类较容易: 当需要添加新的元素类时,只需要为新元素类创建一个对应的访问者方法,而不需要修改现有的访问者类。

  6. 可扩展性强: 通过增加不同的访问者类,可以轻松地为系统添加新的功能,而不会对已有的代码产生影响。

一个简单的例子:

interface Shape {
    void accept(Visitor visitor);
}

class Circle implements Shape {
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
    // 其他属性和方法...
}

class Rectangle implements Shape {
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
    // 其他属性和方法...
}

interface Visitor {
    void visit(Circle circle);
    void visit(Rectangle rectangle);
}

class AreaVisitor implements Visitor {
    @Override
    public void visit(Circle circle) {
        // 计算圆的面积
    }

    @Override
    public void visit(Rectangle rectangle) {
        // 计算矩形的面积
    }
}

class PerimeterVisitor implements Visitor {
    @Override
    public void visit(Circle circle) {
        // 计算圆的周长
    }

    @Override
    public void visit(Rectangle rectangle) {
        // 计算矩形的周长
    }
}

一个图形绘制应用,其中有不同类型的图形(如圆、矩形、三角形)和不同的操作(如计算面积、计算周长)。使用访问者模式,可以轻松地增加新的操作而不影响已有的图形类。

你可能感兴趣的:(设计模式,java)