设计模式-行为型模式(详解)

模板方法

模板方法模式,它在一个抽象类中定义了一个算法(业务逻辑)的骨架,具体步骤的实现由子类提供,它通过将算法的不变部分放在抽象类中,可变部分放在子类中,达到代码复用和扩展的目的。

  • 复用: 所有子类可以直接复用父类提供的模板方法,即上面提到的不变的部分。
  • 扩展: 子类可以通过模板定义的一些扩展点就行不同的定制化实现。

我们来看一下示例代码就很清晰了

abstract class AbstractClass {   //模板类
    // 模板方法
    public final void templateMethod() {
        primitiveOperation1();
        primitiveOperation2();
        hook();
    }
    // 基本操作(抽象方法)
    protected abstract void primitiveOperation1();
    protected abstract void primitiveOperation2();
    // 钩子方法(可选的操作,提供默认实现)
    protected void hook() {}
}
class ConcreteClassA extends AbstractClass {  //具体方法A
    @Override
    protected void primitiveOperation1() {
        System.out.println("ConcreteClassA: primitiveOperation1");
    }
    @Override
    protected void primitiveOperation2() {
        System.out.println("ConcreteClassA: primitiveOperation2");
    }
}
class ConcreteClassB extends AbstractClass {  //具体方法B
    @Override
    protected void primitiveOperation1() {
        System.out.println("ConcreteClassB: primitiveOperation1");
    }
    @Override
    protected void primitiveOperation2() {
        System.out.println("ConcreteClassB: primitiveOperation2");
    }
    @Override
    protected void hook() {
        System.out.println("ConcreteClassB: hook");
    }
}

场景

应用场景:
例如支付场景,需要有支付宝、银行卡、微信支付多种方式,可以定义支付的骨架,骨架里包括一些获取订单、验证密码、执行支付、发送短信的逻辑,像验证密码这些公共的就由骨架来提供,执行支付就由子类来独立实现。

除此之外,支付场景,可能也会涉及到工厂模式,不同的支付方式,利用工厂模式不同的支付对象,再调用支付对象实现不同的功能。

策略模式

策略模式是一种行为型设计模式,提供一个策略接口,并独立实现各种具体策略,允许在运行时动态选择具体策略,从而实现更加灵活的代码结构。该模式的中心不是如何实现算法,而是如何组织、调用这些算法,方便调用方在针对不同场景灵活切换不同的策略。

// 策略的定义
public interface Strategy {
  void execute(User user); 
}
//具体策略A
public class AStrategy implements Strategy{
  void execute(User user) {
   	System.out.println("Executing Strategy A " + user.getName());
  }
}
//具体策略B
public class BStrategy implements Strategy{
  void execute(User user) {
   System.out.println("Executing Strategy B " + user.getName());
  }
}


// 策略集合的创建
public class StrategyFactory {
  private static final Map<String, Strategy> strategies = new HashMap<>();
  static {
    strategies.put("A", new AStrategy());
    strategies.put("B", new BStrategy());
  }
  public static Strategy getStrategy(OrderType type) {
    return strategies.get(type);
  } 
}

// 策略的使用
public class xxService {
  public void execute(User user) {
    String type = user.getType();
    Strategy strategy = StrategyFactory.getStrategy(type);
    return strategy.execute(user);
  }
}

可以看到,上述的代码通过使用策略模式,可以将不同的算法策略封装起来,并根据 user 的类型在运行时动态选择具体的算法策略,从而提高系统的灵活性和可扩展性。

可以用在例如多种支付方式的切换、不同排序算法的切换等多策略实现的场景。

责任链模式

责任链模式允许将多个对象连接成一条链,并且沿着这条链传递请求,让多个对象都有机会处理这个请求,请求会顺着链传递,直到某个对象处理它为止。

在很多场景都能看到责任链模式,比如日志的处理,不同级别不同输出。再比如 Spring 过滤器的 Chain 也是责任链模式。

下面是一个简单的日志处理示例代码。

  • 日志处理器有三种类型: 控制台日志处理器(ConsoleLogger)、文件日志处理器(FileLogger)、错误日志处理器(ErrorLogger)。
  • 日志处理器按照优先级进行处理,如果当前处理器不能处理,则将请求传递给下一个处理器。
// 责任链模式的抽象日志类
abstract class Logger {
    public static int INFO = 1;
    public static int DEBUG = 2;
    public static int ERROR = 3;
    protected int level;
    protected Logger nextLogger;   //日志中可以存下一个日志
    public void setNextLogger(Logger nextLogger) {
        this.nextLogger = nextLogger;
    }
    public void logMessage(int level, String message) {
        if (this.level <= level) {
            write(message);
        }
        if (nextLogger != null) {
            nextLogger.logMessage(level, message);
        }
    }
    protected abstract void write(String message);
}

// 具体处理器类:控制台日志处理器
class ConsoleLogger extends Logger {
    public ConsoleLogger(int level) {
        this.level = level;
    }
    @Override
    protected void write(String message) {
        System.out.println("Standard Console::Logger: " + message);
    }
}
// 具体处理器类:文件日志处理器
class FileLogger extends Logger {
    public FileLogger(int level) {
        this.level = level;
    }
    @Override
    protected void write(String message) {
        System.out.println("File::Logger: " + message);
    }
}
// 具体处理器类:错误日志处理器
class ErrorLogger extends Logger {
    public ErrorLogger(int level) {
        this.level = level;
    }
    @Override
    protected void write(String message) {
        System.out.println("Error Console::Logger: " + message);
    }
}

// 客户端代码
public class ChainPatternDemo {
    private static Logger getChainOfLoggers() {  //获取日志责任链
        Logger errorLogger = new ErrorLogger(Logger.ERROR);
        Logger fileLogger = new FileLogger(Logger.DEBUG);
        Logger consoleLogger = new ConsoleLogger(Logger.INFO);
        errorLogger.setNextLogger(fileLogger);  //存下一个日志
        fileLogger.setNextLogger(consoleLogger);   //存再下一个日志
        return errorLogger;  //最后返回
    }
    public static void main(String[] args) {
        Logger loggerChain = getChainOfLoggers();
        loggerChain.logMessage(Logger.INFO, "mianshiya.com");
        loggerChain.logMessage(Logger.DEBUG, "小程序:面试鸭");
        loggerChain.logMessage(Logger.ERROR, "网页端:mianshiya.com");
    }
}

观察者模式

观察者模式其实也称为发布订阅模式,它定义了对象之间的一种一对多的依赖关系,让多个观察者对象 同时监听某一个主题对象。当主题对象状态发生变化时,它会通知所有观察者对象。

发布订阅与观察者模式的区别**:发布订阅引入了第三方组件来维护发布者和订阅者的关系,他们之间不直接通信,做到了真正解耦。**

观察者模式的组成部分

  • Subject(主题/被观察者): 状态发生变化时,通知所有注册的观察者
  • Observer(观察者): 接收来自主题的更新通知,并进行相应的操作。
  • ConcreteSubject(具体主题): 实现具体的主题对象,保存需要被观察的状态。
  • ConcreteObserver(具体观察者): 实现具体的观察者对象,更新自己以与主题的状态同步

我们以一个新闻发布系统来理解下观察者模式。

1)定义接口

​ 新闻发布者(Subject)可以发布新闻,观察者(Observer)是订阅者,希望获取新闻更新

2)实现具体类

  • 具体新闻发布者(Concrete Subject)维护一个订阅者列表,并在发布新闻时通知他们
  • 具体观察者(Concrete Observer)如报纸、新闻网站等,实现更新方法来展示新闻

3)订阅和退订

​ 订阅者可以订阅(注册)或退订(注销)新闻发布者的新闻。

4)发布新闻

​ 当新闻发布者有新新闻时,"观察者更新自己的新闻内容。它通知所有订阅的观察者

5)客户端代码

​ 客户端代码创建新闻发布者和观察者对象,订阅者选择订新闻,并在接收到新闻时更新显示。

// 定义观察者接口
interface Observer {
    void update(String news);
}
// 定义主题接口
interface Subject {
    void attach(Observer o);
    void detach(Observer o);
    void notifyObservers();
}
// 具体新闻发布者
class NewsPublisher implements Subject {
    private List<Observer> observers = new ArrayList<>();

    public void attach(Observer o) {
        observers.add(o);
    }
    public void detach(Observer o) {
        observers.remove(o);
    }
    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update("新闻更新");
        }
    }
    public void publishNews() {
        // 假设这里是新闻发布逻辑,会通知发布者里面集合的观察者
        notifyObservers();
    }
}

// 具体观察者
class NewsPaper implements Observer {
    public void update(String news) {
        System.out.println("新报 " + news);
    }
}

// 客户端代码
public class Client {
    public static void main(String[] args) {
        NewsPublisher publisher = new NewsPublisher();  //发布者
        Observer newsPaper = new NewsPaper();   //观察者

        publisher.attach(newsPaper);     //导入观察者
        publisher.publishNews();        //发布新闻

        publisher.detach(newsPaper);    //移除观察者
        publisher.publishNews(); // 此时报纸订阅者不会接收到新闻
    }
}

例如消息队列、 Spring 内的监听器机制等都是其应用场景

迭代器模式

迭代器模式,它提供一种方法顺序访问一个集合对象中的各个元素,而又不暴露该对象(可能是数组、链表、树等等)的内部实现。

将遍历逻辑与集合对象的实现分离,提供一致的遍历方式,使得代码统一化,在不改变遍历代码的情况下就能替换底层集合实现。

像 Java 的java.util.Iterator接口和Iterable接口是迭代器模式的直接应用。所有集合类(如ArrayList、HashSet、LinkedList 等)都实现了 Iterable 接口,并提供了 iterator() 方法来获取迭代器,例如,遍历 ArrayList 中的元素:

List<String> list = new ArrayList<>();
list.add("one");
list.add("two");
list.add("three");

Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    System.out.println(iterator.next());
}

再比如 JDBC 的ResultSet接口遍历数据库査询结果,也是迭代器模式的实现。

迭代器模式的组成部分

  • lterator(迭代器接口): 定义访问和历元素的接口
  • Aggregate(聚合接口): 定义创建迭代器的接口
  • Concretelterator(具体迭代器): 实现迭代器接口,负责遍历聚合对象中的元素
  • ConcreteAggregate(具体聚合类): 实现聚合接口,返回一个具体的迭代器实例。

我们来简单实现一个迭代器

//定义迭代器接口
interface Iterator<T> {
    boolean hasNext();
    T next();
}
//定义聚合接口
interface Aggregate<T> {
    Iterator<T> createIterator();
}

//定义具体迭代器
class ConcreteIterator<T> implements Iterator<T> {
    private List<T> items;
    private int position = 0;  //当前遍历位置
    public ConcreteIterator(List<T> items) {
        this.items = items;
    }
    @Override
    public boolean hasNext() {    //如果进行集合遍历时位置<集合大小,则返回true
        return position < items.size();
    }
    @Override
    public T next() {  //获取当前遍历位置的下一个元素
        return items.get(position++);
    }
}
//定义具体聚合类
class ConcreteAggregate<T> implements Aggregate<T> {
    private List<T> items = new ArrayList<>();
    public void addItem(T item) {   //添加元素方法
        items.add(item);
    }
    @Override
    public Iterator<T> createIterator() {
        return new ConcreteIterator<>(items);
    }
}
//简单使用
public class Client {
    public static void main(String[] args) {
        ConcreteAggregate<String> aggregate = new ConcreteAggregate<>();
        aggregate.addItem("Item 1");    //添加集合元素
        aggregate.addItem("Item 2");
        aggregate.addItem("Item 3");

        Iterator<String> iterator = aggregate.createIterator(); //返回对应该集合的迭代器
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }
}

命令模式

命令模式(Command Pattern)是一种行为设计模式,它将请求封装成对象,使得请求参数化,便于对请求排队或记录请求日志,以及支持可撤销的操作。

简单看下以开关灯为例实现命令模式的代码,更容易理解上面那段含义:

// 命令接口
interface Command {
    void execute();
}
// 接收者
class Light {   
    public void on() {    //用于控制灯光的开关
        System.out.println("The light is on.");
    }
    public void off() {
        System.out.println("The light is off.");
    }
}

// 具体命令   开灯
class LightOnCommand implements Command {
    private Light light;
    public LightOnCommand(Light light) {
        this.light = light;
    }
    @Override
    public void execute() {
        light.on();
    }
}
// 具体命令   关灯
class LightOffCommand implements Command {
    private Light light;
    public LightOffCommand(Light light) {
        this.light = light;
    }
    @Override
    public void execute() {
        light.off();
    }
}
// 调用者,传入了开关命令,因此可以开关灯
class RemoteControl {
    private Command command;
    public void setCommand(Command command) {
        this.command = command;
    }
    public void pressButton() {
        command.execute();
    }
}


// 客户端代码
public class Client {
    public static void main(String[] args) {
        Light light = new Light();   
        Command lightOn = new LightOnCommand(light);  //开灯
        Command lightOff = new LightOffCommand(light);   //关灯

        RemoteControl remote = new RemoteControl();
        remote.setCommand(lightOn);     
        remote.pressButton();   //开灯

        remote.setCommand(lightOff);
        remote.pressButton();    //关灯
    }
}

状态模式

状态模式 允许对象在其内部状态发生改变时改变其行为,将状态的行为封装在独立的类中,并将这些状态对象组合在拥有状态的对象中,这样就可以在状态改变时切换状态对象,从而改变对象的行为

它主要是状态机的一种实现方式,状态机可分为: 状态、事件、动作三个部分。事件的触发就会导致状态的改变,并且可作出一定的动作(也可以没有动作,只有状态的改变)

// 定义状态接口
interface State {
    void handle(Context context);
}
// 上下文环境,状态模式的核心,它持有一个 State 对象,并提供了 setState 方法来改变当前状态
class Context {
    private State state;
    public Context(State state) {
        this.state = state;
    }
    public void setState(State state) {
        this.state = state;
    }
    public void request() {
        state.handle(this);
    }
}
// 具体实现类
//AB每个类在处理请求时都会打印一条消息,并且会改变上下文 Context 的状态为另一种状态
class ConcreteStateA implements State {
    @Override
    public void handle(Context context) {
        System.out.println("State A is handling the request.");
        context.setState(new ConcreteStateB());
    }
}
class ConcreteStateB implements State {
    @Override
    public void handle(Context context) {
        System.out.println("State B is handling the request.");
        context.setState(new ConcreteStateA());
    }
}


public class Client {
    public static void main(String[] args) {
        Context context = new Context(new ConcreteStateA());   //新建A状态环境
        context.request(); // State A is handling the request.   //请求A状态,然后设置当前状态为B
        context.request(); // State B is handling the request.
        context.request(); // State A is handling the request.
        context.request(); // State B is handling the request.
    }
}

中介者模式

中介模式通过引入了一个中介对象,来封装一组对象之间的交互,来避免对象之间的直接交互。通过引入一个中介者对象,使对象之间的关系变得简单且易于维护。

听起来不就是和现实生活中的中介一样嘛。

引入中介模式的原因:多对象交互可能会使得关系图很混乱,代码也不清晰,让多对象都和中介交互就能避免这点

image-20240928194712824

聊天室实现其实就是运用了中介模式。信息的传递都由服务器这个中介来做,所有用户都把消息发给服务器,不然如果点对点传输大家可以想象下有多复杂。我们可以简单的看下聊天室的实现:

interface ChatMediator {       //中介者接口
    void sendMessage(String message, User user);
    void addUser(User user);
}
class ChatMediatorImpl implements ChatMediator { //具体中介者
    private List<User> users;   //存储加入的用户
    public ChatMediatorImpl() {
        this.users = new ArrayList<>();
    }
    @Override
    public void addUser(User user) {
        this.users.add(user);
    }
    @Override
    public void sendMessage(String message, User user) {
        for (User u : this.users) {
            if (u != user) {
                u.receive(message);
            }
        }
    }
}

abstract class User {       //抽象用户
    protected ChatMediator mediator;
    protected String name;
    public User(ChatMediator mediator, String name) {
        this.mediator = mediator;
        this.name = name;
    }
    public abstract void send(String message);
    public abstract void receive(String message);
}

class UserImpl extends User {    //用户
    public UserImpl(ChatMediator mediator, String name) {
        super(mediator, name);
    }
    @Override
    public void send(String message) {
        System.out.println(this.name + " sends: " + message);
        mediator.sendMessage(message, this);
    }
    @Override
    public void receive(String message) {
        System.out.println(this.name + " receives: " + message);
    }
}
public class ChatClient {    //客户端
    public static void main(String[] args) {
        ChatMediator mediator = new ChatMediatorImpl();
        User user1 = new UserImpl(mediator, "Alice");
        User user2 = new UserImpl(mediator, "Bob");
        User user3 = new UserImpl(mediator, "Charlie");

        mediator.addUser(user1);   //用户加入中介
        mediator.addUser(user2);
        mediator.addUser(user3);

        user1.send("Hello, everyone!");
    }
}

访问者模式

访问者模式它将数据结构与操作分离,使得你可以在不改变数据结构的前提下定义新的操作。访问者模式通过将操作封装到独立的访问者对象中,使得新的操作可以很容易地添加到系统中。

例如对象序列化场景,比如需要将对象转成 JSON 或者 XML 等格式,利用访问者模式将对象的结构和序列化操作分离,这样就能很方便的扩展新的序列化格式。先看下示例代码:

//访问者接口(类比序列化接口)
interface Visitor {
    void visit(ElementA element);
    void visit(ElementB element);
}
//具体访问者类(类比具体序列化实现)
class ConcreteVisitor implements Visitor {
    @Override
    public void visit(ElementA element) {
        System.out.println("Processing ElementA: " + element.getName());
    }
    @Override
    public void visit(ElementB element) {
        System.out.println("Processing ElementB: " + element.getName());
    }
}

//需要对序列化的元素接口
interface Element {
    void accept(Visitor visitor);
}

// 具体被序列化的元素A
class ElementA implements Element {
    private String name;
    public ElementA(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}
// 具体被序列化的元素B
class ElementB implements Element {
    private String name;
    public ElementB(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}

//对象结构,包含很多元素
class ObjectStructure {
    private List<Element> elements = new ArrayList<>();

    public void addElement(Element element) {
        elements.add(element);
    }
    public void accept(Visitor visitor) {
        for (Element element : elements) {
            element.accept(visitor);
        }
    }
}


// 客户端代码
public class Client {
    public static void main(String[] args) {
        // 组装对象
        ObjectStructure objectStructure = new ObjectStructure();
        objectStructure.addElement(new ElementA("Element A1"));
        objectStructure.addElement(new ElementB("Element B1"));
        objectStructure.addElement(new ElementA("Element A2"));
        
        //访问者 (如果是序列化场景,ConcreteVisitor 可以当做 JSON 序列化)
        Visitor visitor = new ConcreteVisitor();
        objectStructure.accept(visitor);

        // 假设后面要替换 xml 序列化,仅需新建 xml 的访问者,然后传入到对象内部即可
        Visitor visitorXML = new XMLVisitor();
        objectStructure.accept(visitorXML);
    }
}

可以看到,将数据结构和操作分离开之后,如果要替换具体的操作,仅需新增一个操作即可,不需要修改任何数据结构,这就符合开闭原则,也保证了类的职责单一。

备忘录模式

备忘录模式指的是在不违背封装原则的前提下,捕获对象内部的状态,将其保存在外部,便于后面对象恢复之前的状态,使得系统更具灵活性和可维护性。

听来像不像保留个快照作为备份,后面基于这个快照进行恢复? 所以备忘录模式其实也叫快照模式。主要用于撤销、恢复等场景。

来看下示例代码,我先大致解释一下几个类的含义:

  • Memento,存储状态的备忘录
  • Originator,需要备份状态的对象类
  • Caretaker,管理者,仅保存备忘录
// 备忘录类
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;
        System.out.println("State set to: " + state);
    }
    public String getState() {
        return state;
    }
    public Memento createMemento() {
        return new Memento(state);
    }
    public void restoreMemento(Memento memento) {
        this.state = memento.getState();
        System.out.println("State restored to: " + state);
    }
}
//管理存储类
class Caretaker {
    private Memento memento;
    public Memento getMemento() {
        return memento;
    }
    public void setMemento(Memento memento) {
        this.memento = memento;
    }
}

//客户端类
public class Client {
    public static void main(String[] args) {
        Originator originator = new Originator();  //对象类
        Caretaker caretaker = new Caretaker();       //存储类

        originator.setState("State1");
        caretaker.setMemento(originator.createMemento()); //保存快照

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

        originator.restoreMemento(caretaker.getMemento());    //恢复快照
        System.out.println("Restored State: " + originator.getState());
    }
}

如果直接利用 set 方法修改内部状态,就违反了封装的原则,因为如果你暴露了 set 方法,则对象内部的状态很可能被别的业务调用修改了。而 restoreMemento 是一个很清晰的方法定义,即恢复之前的状态,不会被乱用。这也是备忘录模式的前提,不违反封装原则。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

空说

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值