MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Java行为型设计模式深入探讨

2021-05-273.3k 阅读

一、概述

在软件开发过程中,设计模式是经过反复验证的、用于解决特定问题的通用解决方案。行为型设计模式关注的是对象之间的交互和职责分配,通过合理的设计,使得系统中的对象能够协同工作,实现复杂的功能。Java作为一种广泛应用的编程语言,为实现各种行为型设计模式提供了良好的基础。下面将深入探讨Java中常见的行为型设计模式。

二、策略模式(Strategy Pattern)

2.1 概念

策略模式定义了一系列算法,将每个算法封装起来,并使它们可以相互替换。策略模式使得算法的变化独立于使用算法的客户。

2.2 结构

策略模式包含以下主要角色:

  • 抽象策略(Strategy):定义了一个公共接口,各种具体策略类实现这个接口。
  • 具体策略(ConcreteStrategy):实现抽象策略定义的接口,提供具体的算法实现。
  • 环境(Context):持有一个抽象策略类的引用,提供给客户端调用的接口,并根据需要在运行时切换具体策略。

2.3 代码示例

// 抽象策略
interface Strategy {
    int execute(int num1, int num2);
}

// 具体策略1:加法
class AddStrategy implements Strategy {
    @Override
    public int execute(int num1, int num2) {
        return num1 + num2;
    }
}

// 具体策略2:减法
class SubtractStrategy implements Strategy {
    @Override
    public int execute(int num1, int num2) {
        return num1 - num2;
    }
}

// 环境
class Context {
    private Strategy strategy;

    public Context(Strategy strategy) {
        this.strategy = strategy;
    }

    public int executeStrategy(int num1, int num2) {
        return strategy.execute(num1, num2);
    }
}

测试代码:

public class StrategyPatternDemo {
    public static void main(String[] args) {
        Context context = new Context(new AddStrategy());
        System.out.println("10 + 5 = " + context.executeStrategy(10, 5));

        context = new Context(new SubtractStrategy());
        System.out.println("10 - 5 = " + context.executeStrategy(10, 5));
    }
}

2.4 应用场景

  • 当一个系统需要动态地在几种算法中选择一种时,可使用策略模式。
  • 避免使用多重条件判断语句,将不同的行为封装到不同的策略类中。

三、观察者模式(Observer Pattern)

3.1 概念

观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。当主题对象状态发生变化时,会通知所有观察者对象,使它们能够自动更新。

3.2 结构

  • 主题(Subject):被观察的对象,持有一系列观察者的引用,并提供添加、删除观察者以及通知观察者的方法。
  • 观察者(Observer):定义了一个更新接口,当主题状态改变时,主题会调用这个接口通知观察者。
  • 具体主题(ConcreteSubject):实现主题接口,负责维护主题的状态,并在状态改变时通知观察者。
  • 具体观察者(ConcreteObserver):实现观察者接口,具体实现更新操作。

3.3 代码示例

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

// 主题
interface Subject {
    void registerObserver(Observer observer);
    void removeObserver(Observer observer);
    void notifyObservers();
}

// 具体主题
class WeatherData implements Subject {
    private List<Observer> observers;
    private float temperature;
    private float humidity;
    private float pressure;

    public WeatherData() {
        observers = new ArrayList<>();
    }

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

    @Override
    public void removeObserver(Observer observer) {
        int i = observers.indexOf(observer);
        if (i >= 0) {
            observers.remove(i);
        }
    }

    @Override
    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update(temperature, humidity, pressure);
        }
    }

    public void measurementsChanged() {
        notifyObservers();
    }

    public void setMeasurements(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        measurementsChanged();
    }
}

// 观察者
interface Observer {
    void update(float temperature, float humidity, float pressure);
}

// 具体观察者1:当前天气显示
class CurrentConditionsDisplay implements Observer {
    private float temperature;
    private float humidity;
    private Subject weatherData;

    public CurrentConditionsDisplay(Subject weatherData) {
        this.weatherData = weatherData;
        weatherData.registerObserver(this);
    }

    @Override
    public void update(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        display();
    }

    public void display() {
        System.out.println("Current conditions: " + temperature + "°C, " + humidity + "% humidity");
    }
}

测试代码:

public class ObserverPatternDemo {
    public static void main(String[] args) {
        WeatherData weatherData = new WeatherData();
        CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData);

        weatherData.setMeasurements(25, 60, 1013);
    }
}

3.4 应用场景

  • 当一个对象的状态改变需要通知其他对象,并且不知道具体有多少对象需要被通知时,可使用观察者模式。
  • 当一个抽象模型有两个方面,其中一个方面依赖于另一个方面,可使用观察者模式将这两者封装在独立的对象中,使它们可以各自独立地改变和复用。

四、模板方法模式(Template Method Pattern)

4.1 概念

模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中实现。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。

4.2 结构

  • 抽象类(AbstractClass):定义了模板方法,该方法包含了算法的骨架,调用一些抽象的或具体的方法。
  • 具体类(ConcreteClass):继承抽象类,实现抽象类中定义的抽象方法,提供具体的实现。

4.3 代码示例

// 抽象类
abstract class AbstractClass {
    // 模板方法
    public final void templateMethod() {
        primitiveOperation1();
        primitiveOperation2();
        concreteOperation();
        hook();
    }

    protected abstract void primitiveOperation1();
    protected abstract void primitiveOperation2();

    // 具体方法
    protected void concreteOperation() {
        System.out.println("This is a concrete operation.");
    }

    // 钩子方法,默认实现为空,子类可根据需要重写
    protected void hook() {}
}

// 具体类1
class ConcreteClass1 extends AbstractClass {
    @Override
    protected void primitiveOperation1() {
        System.out.println("ConcreteClass1's implementation of primitiveOperation1");
    }

    @Override
    protected void primitiveOperation2() {
        System.out.println("ConcreteClass1's implementation of primitiveOperation2");
    }

    @Override
    protected void hook() {
        System.out.println("ConcreteClass1's implementation of hook method");
    }
}

// 具体类2
class ConcreteClass2 extends AbstractClass {
    @Override
    protected void primitiveOperation1() {
        System.out.println("ConcreteClass2's implementation of primitiveOperation1");
    }

    @Override
    protected void primitiveOperation2() {
        System.out.println("ConcreteClass2's implementation of primitiveOperation2");
    }
}

测试代码:

public class TemplateMethodPatternDemo {
    public static void main(String[] args) {
        AbstractClass concreteClass1 = new ConcreteClass1();
        concreteClass1.templateMethod();

        AbstractClass concreteClass2 = new ConcreteClass2();
        concreteClass2.templateMethod();
    }
}

4.4 应用场景

  • 当多个子类有公共的行为,并且这些公共行为可以被提取到父类中,使用模板方法模式可以避免代码重复。
  • 当需要通过子类来扩展算法的某些步骤时,可使用模板方法模式。

五、迭代器模式(Iterator Pattern)

5.1 概念

迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露该对象的内部表示。

5.2 结构

  • 迭代器(Iterator):定义了访问和遍历元素的接口,如next()hasNext()等方法。
  • 具体迭代器(ConcreteIterator):实现迭代器接口,跟踪遍历的当前位置。
  • 聚合(Aggregate):定义创建迭代器对象的接口。
  • 具体聚合(ConcreteAggregate):实现聚合接口,返回一个具体迭代器的实例。

5.3 代码示例

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

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

// 具体迭代器
class ConcreteIterator implements Iterator {
    private List<Object> list;
    private int position = 0;

    public ConcreteIterator(List<Object> list) {
        this.list = list;
    }

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

    @Override
    public Object next() {
        if (this.hasNext()) {
            return list.get(position++);
        }
        return null;
    }
}

// 聚合
interface Aggregate {
    Iterator createIterator();
}

// 具体聚合
class ConcreteAggregate implements Aggregate {
    private List<Object> list = new ArrayList<>();

    public void addObject(Object obj) {
        list.add(obj);
    }

    public void removeObject(Object obj) {
        list.remove(obj);
    }

    @Override
    public Iterator createIterator() {
        return new ConcreteIterator(list);
    }
}

测试代码:

public class IteratorPatternDemo {
    public static void main(String[] args) {
        ConcreteAggregate aggregate = new ConcreteAggregate();
        aggregate.addObject("Item 1");
        aggregate.addObject("Item 2");
        aggregate.addObject("Item 3");

        Iterator iterator = aggregate.createIterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }
}

5.4 应用场景

  • 当需要访问一个聚合对象的内容而又不暴露其内部细节时,可使用迭代器模式。
  • 当需要为聚合对象提供多种遍历方式时,迭代器模式可以很方便地实现。

六、责任链模式(Chain of Responsibility Pattern)

6.1 概念

责任链模式使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。

6.2 结构

  • 抽象处理者(Handler):定义了处理请求的接口,同时包含一个后继者的引用。
  • 具体处理者(ConcreteHandler):实现抽象处理者的处理方法,判断自己是否能处理该请求,如果能则处理,否则将请求传递给后继者。
  • 客户(Client):向链上的具体处理者对象提交请求。

6.3 代码示例

// 抽象处理者
abstract class Handler {
    protected Handler successor;

    public void setSuccessor(Handler successor) {
        this.successor = successor;
    }

    public abstract void handleRequest(int request);
}

// 具体处理者1
class ConcreteHandler1 extends Handler {
    @Override
    public void handleRequest(int request) {
        if (request >= 0 && request < 10) {
            System.out.println("ConcreteHandler1 handled request " + request);
        } else if (successor != null) {
            successor.handleRequest(request);
        }
    }
}

// 具体处理者2
class ConcreteHandler2 extends Handler {
    @Override
    public void handleRequest(int request) {
        if (request >= 10 && request < 20) {
            System.out.println("ConcreteHandler2 handled request " + request);
        } else if (successor != null) {
            successor.handleRequest(request);
        }
    }
}

// 具体处理者3
class ConcreteHandler3 extends Handler {
    @Override
    public void handleRequest(int request) {
        if (request >= 20 && request < 30) {
            System.out.println("ConcreteHandler3 handled request " + request);
        } else if (successor != null) {
            successor.handleRequest(request);
        }
    }
}

测试代码:

public class ChainOfResponsibilityPatternDemo {
    public static void main(String[] args) {
        ConcreteHandler1 handler1 = new ConcreteHandler1();
        ConcreteHandler2 handler2 = new ConcreteHandler2();
        ConcreteHandler3 handler3 = new ConcreteHandler3();

        handler1.setSuccessor(handler2);
        handler2.setSuccessor(handler3);

        int[] requests = {5, 14, 22, 18, 3, 27, 20};

        for (int request : requests) {
            handler1.handleRequest(request);
        }
    }
}

6.4 应用场景

  • 当有多个对象可以处理一个请求,哪个对象处理该请求在运行时动态确定时,可使用责任链模式。
  • 当需要动态地指定一组对象处理请求,并且希望对请求的处理进行解耦时,责任链模式是一个不错的选择。

七、命令模式(Command Pattern)

7.1 概念

命令模式将一个请求封装为一个对象,从而使你可以用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。

7.2 结构

  • 命令(Command):定义了一个执行操作的接口,通常包含execute()方法。
  • 具体命令(ConcreteCommand):实现命令接口,绑定一个接收者对象,并调用接收者的相应方法来执行命令。
  • 接收者(Receiver):知道如何执行与请求相关的操作,具体命令对象会调用接收者的方法。
  • 调用者(Invoker):持有一个命令对象的引用,通过调用命令对象的execute()方法来触发请求。
  • 客户(Client):创建具体命令对象,并设置其接收者。

7.3 代码示例

// 接收者
class Receiver {
    public void action() {
        System.out.println("Receiver is performing an action.");
    }
}

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

// 具体命令
class ConcreteCommand implements Command {
    private Receiver receiver;

    public ConcreteCommand(Receiver receiver) {
        this.receiver = receiver;
    }

    @Override
    public void execute() {
        receiver.action();
    }
}

// 调用者
class Invoker {
    private Command command;

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

    public void executeCommand() {
        if (command != null) {
            command.execute();
        }
    }
}

测试代码:

public class CommandPatternDemo {
    public static void main(String[] args) {
        Receiver receiver = new Receiver();
        Command command = new ConcreteCommand(receiver);

        Invoker invoker = new Invoker();
        invoker.setCommand(command);
        invoker.executeCommand();
    }
}

7.4 应用场景

  • 当需要将请求的发送者和接收者解耦,使得发送者不需要知道接收者的具体信息时,可使用命令模式。
  • 当需要支持命令的撤销、恢复操作时,命令模式可以方便地实现。

八、备忘录模式(Memento Pattern)

8.1 概念

备忘录模式在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。

8.2 结构

  • 发起人(Originator):创建一个备忘录,用以记录当前时刻它的内部状态,并可使用备忘录恢复内部状态。
  • 备忘录(Memento):负责存储发起人对象的内部状态,并可以防止发起人以外的其他对象访问备忘录。
  • 管理者(Caretaker):负责保存备忘录,但不能对备忘录的内容进行操作或检查。

8.3 代码示例

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

// 管理者
class Caretaker {
    private Memento memento;

    public void saveMemento(Memento memento) {
        this.memento = memento;
    }

    public Memento getMemento() {
        return memento;
    }
}

测试代码:

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

        originator.setState("State1");
        originator.setState("State2");
        caretaker.saveMemento(originator.saveStateToMemento());
        originator.setState("State3");

        System.out.println("Current state: " + originator.getState());
        originator.getStateFromMemento(caretaker.getMemento());
        System.out.println("Restored state: " + originator.getState());
    }
}

8.4 应用场景

  • 当需要保存一个对象在某一时刻的状态,以便以后恢复到该状态时,可使用备忘录模式。
  • 当不希望外部对象直接访问对象的内部状态,而又需要保存和恢复该状态时,备忘录模式可以满足需求。

九、状态模式(State Pattern)

9.1 概念

状态模式允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。

9.2 结构

  • 抽象状态(State):定义了一个接口,封装了与Context的一个特定状态相关的行为。
  • 具体状态(ConcreteState):实现抽象状态接口,每个具体状态类都对应Context的一个具体状态,并实现与该状态相关的行为。
  • 环境(Context):持有一个抽象状态类的引用,定义了客户端感兴趣的接口,并根据当前状态委托状态对象处理请求。

9.3 代码示例

// 抽象状态
interface State {
    void handle(Context context);
}

// 具体状态1
class ConcreteState1 implements State {
    @Override
    public void handle(Context context) {
        System.out.println("Handling request in ConcreteState1");
        context.setState(new ConcreteState2());
    }
}

// 具体状态2
class ConcreteState2 implements State {
    @Override
    public void handle(Context context) {
        System.out.println("Handling request in ConcreteState2");
        context.setState(new ConcreteState1());
    }
}

// 环境
class Context {
    private State state;

    public Context() {
        state = new ConcreteState1();
    }

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

    public void request() {
        state.handle(this);
    }
}

测试代码:

public class StatePatternDemo {
    public static void main(String[] args) {
        Context context = new Context();
        context.request();
        context.request();
        context.request();
    }
}

9.4 应用场景

  • 当一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变行为时,可使用状态模式。
  • 当代码中包含大量与对象状态有关的条件语句时,将状态的判断逻辑转移到状态对象中,可以使代码更清晰、可维护性更高。

十、中介者模式(Mediator Pattern)

10.1 概念

中介者模式用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。

10.2 结构

  • 中介者(Mediator):定义了一个接口用于与各同事对象通信。
  • 具体中介者(ConcreteMediator):实现中介者接口,协调各同事对象之间的交互。
  • 同事(Colleague):定义了一个抽象类,所有具体同事类都继承自这个类,并持有中介者对象的引用,通过中介者对象与其他同事对象进行通信。
  • 具体同事(ConcreteColleague):继承同事抽象类,实现具体的业务逻辑,并在需要与其他同事通信时,通过中介者对象进行。

10.3 代码示例

// 中介者
interface Mediator {
    void send(String message, Colleague colleague);
}

// 具体中介者
class ConcreteMediator implements Mediator {
    private ConcreteColleague1 colleague1;
    private ConcreteColleague2 colleague2;

    public void setColleague1(ConcreteColleague1 colleague1) {
        this.colleague1 = colleague1;
    }

    public void setColleague2(ConcreteColleague2 colleague2) {
        this.colleague2 = colleague2;
    }

    @Override
    public void send(String message, Colleague colleague) {
        if (colleague == colleague1) {
            colleague2.notify(message);
        } else {
            colleague1.notify(message);
        }
    }
}

// 同事
abstract class Colleague {
    protected Mediator mediator;

    public Colleague(Mediator mediator) {
        this.mediator = mediator;
    }

    public abstract void notify(String message);
    public abstract void send(String message);
}

// 具体同事1
class ConcreteColleague1 extends Colleague {
    public ConcreteColleague1(Mediator mediator) {
        super(mediator);
    }

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

    @Override
    public void send(String message) {
        mediator.send(message, this);
    }
}

// 具体同事2
class ConcreteColleague2 extends Colleague {
    public ConcreteColleague2(Mediator mediator) {
        super(mediator);
    }

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

    @Override
    public void send(String message) {
        mediator.send(message, this);
    }
}

测试代码:

public class MediatorPatternDemo {
    public static void main(String[] args) {
        ConcreteMediator mediator = new ConcreteMediator();

        ConcreteColleague1 colleague1 = new ConcreteColleague1(mediator);
        ConcreteColleague2 colleague2 = new ConcreteColleague2(mediator);

        mediator.setColleague1(colleague1);
        mediator.setColleague2(colleague2);

        colleague1.send("Hello, Colleague2!");
        colleague2.send("Hi, Colleague1!");
    }
}

10.4 应用场景

  • 当一组对象之间的交互非常复杂,并且这些交互依赖于彼此的状态时,使用中介者模式可以将这些复杂的交互封装到中介者对象中,简化对象之间的关系。
  • 当需要通过一个中间对象来协调多个对象之间的交互,以降低对象之间的耦合度时,中介者模式是一个合适的选择。

十一、解释器模式(Interpreter Pattern)

11.1 概念

解释器模式给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。

11.2 结构

  • 抽象表达式(AbstractExpression):声明一个抽象的解释操作,这个接口为抽象语法树中所有的节点所共享。
  • 终结符表达式(TerminalExpression):实现与文法中的终结符相关联的解释操作,一个句子中的每个终结符需要一个具体的终结符表达式。
  • 非终结符表达式(NonterminalExpression):为文法中的非终结符实现解释操作,对文法中的每一条规则都需要一个非终结符表达式。
  • 环境(Context):包含解释器之外的一些全局信息。
  • 客户端(Client):构建一个抽象语法树,表示要解释的句子,并调用解释操作。

11.3 代码示例

import java.util.HashMap;
import java.util.Map;

// 抽象表达式
abstract class Expression {
    public abstract int interpret(Context context);
}

// 终结符表达式
class TerminalExpression extends Expression {
    private String variable;

    public TerminalExpression(String variable) {
        this.variable = variable;
    }

    @Override
    public int interpret(Context context) {
        return context.lookup(variable);
    }
}

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

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

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

// 环境
class Context {
    private Map<String, Integer> variables = new HashMap<>();

    public void assign(String variable, int value) {
        variables.put(variable, value);
    }

    public int lookup(String variable) {
        return variables.get(variable);
    }
}

测试代码:

public class InterpreterPatternDemo {
    public static void main(String[] args) {
        Context context = new Context();
        context.assign("x", 5);
        context.assign("y", 3);

        Expression expression = new AddExpression(
                new TerminalExpression("x"),
                new TerminalExpression("y")
        );

        System.out.println("x + y = " + expression.interpret(context));
    }
}

11.4 应用场景

  • 当有一个语言需要解释执行,并且可以将该语言中的句子表示为一个抽象语法树时,可使用解释器模式。
  • 当问题重复出现,并且可以用一种简单的语言来进行表达时,解释器模式可以提供一种灵活的解决方案。不过,由于解释器模式会增加系统的复杂性,所以在实际应用中需要谨慎使用。

通过对以上Java行为型设计模式的深入探讨,希望能够帮助开发者更好地理解和应用这些模式,从而在软件开发中构建出更加灵活、可维护和可扩展的系统。在实际项目中,应根据具体的需求和场景,选择合适的设计模式来优化代码结构和提升软件质量。