Java实现十种行为型模式

(一) 模板方法模式

模板方法模式(Template Method Pattern): 又称之为模板模式, 在一个抽象类公开定义了执行它的方法的模板, 它的子类可以俺需要重写方法实现, 但调用将以抽象类中定义的方式进行. 属于行为型模式.

简单来说, 抽象类中的模板方法定义一个操作中的算法的骨架(如多个抽象方法的执行顺序), 而将一些步骤(抽象方法)延迟到子类中实现, 使得子类可以不改变一个算法的结构, 就可以定义该算法的某些特定的步骤.

举例: 玩电子游戏一般会经历三个步骤: 下载游戏 —> 开始游戏 —> 结束游戏. 可以采用模板模式思想来实现:

  • 玩游戏的三个步骤顺序是不可变(算法的骨架), 可将这个三个步骤定义一个抽象类的模板方法
  • 三个步骤中具体操作的是哪个游戏, 抽象类是无法预测的, 需要由具体的子类去实现

游戏抽象类: 定义三个步骤 和 玩游戏的模板方法

abstract class Game {
    // 下载游戏
    abstract void download();
    // 玩游戏中
    abstract void playing();
    // 游戏结束
    abstract void over();

    // final修饰 模板方法
    final void playGame() {
        download();
        playing();
        over();
    }
}

游戏实现类: 指定游戏的类型

class LOL extends Game {
    @Override
    void download() {
        System.out.println("LOL 下载中...");
    }

    @Override
    void playing() {
        System.out.println("LOL 游戏中...");
    }

    @Override
    void over() {
        System.out.println("LOL 结束");
    }
}

class DNF extends Game {
    @Override
    void download() {
        System.out.println("DNF 下载中...");
    }

    @Override
    void playing() {
        System.out.println("DNF 游戏中...");
    }

    @Override
    void over() {
        System.out.println("DNF 结束");
    }
}

测试:

public class TemplatePattern {
    System.out.println("-------- LOL 游戏 --------");
    Game LOL = new LOL();
    LOL.playGame();
    System.out.println("-------- DNF 游戏 --------");
    Game DNF = new DNF();
    DNF.playGame();
}

在这里插入图片描述

模板方法模式的钩子方法: 在模板方法模式中的抽象类中, 定义一个方法, 默认不做任何事情, 子类可以视情况来覆盖重写此方法. 该方法称之为 钩子方法 (比如: 可以用这个方法来判断模板方法中是否要执行某些步骤)

还是用上面游戏的例子来举例: 现在游戏抽象类类中的模板方法中执行了三步骤: 下载游戏 —> 开始游戏 —> 结束游戏. 但是, 有些游戏是不需要下载的(如: 4399小游戏), 因此我们需要 钩子方法 来判断是否要执行 下载游戏的步骤

abstract class Game {
    abstract void download();
    abstract void playing();
    abstract void over();

    // 钩子方法(默认返回true): 来判断是否要执行 下载游戏的步骤
    boolean isDownload() {
        return true;
    }

    // final修饰 模板方法
    final void playGame() {
        if (isDownload()) {
	        download();
        }
        playing();
        over();
    }
}

class Game4339 extends Game {

    // 覆盖重写isDownload钩子方法, 返回false: 取消下载游戏的步骤
    @Override
    boolean isDownload() {
        return false;
    }

    // 4339无需下载
    @Override
    void download() {
        System.out.println("4339小游戏无需下载");
    }

    @Override
    void playing() {
        System.out.println("4339 游戏中...");
    }

    @Override
    void over() {
        System.out.println("4339 结束");
    }
}

public class TemplatePattern {
    public static void main(String[] args) {
        System.out.println("-------- LOL 游戏 --------");
        Game LOL = new LOL();
        LOL.playGame();
        System.out.println("-------- DNF 游戏 --------");
        Game DNF = new DNF();
        DNF.playGame();
        System.out.println("-------- 4399 游戏 --------");
        Game game4339 = new Game4339();
        game4339.playGame();
    }
}

在这里插入图片描述
模板方法模式的注意事项和细节

  • 算法只存在于抽象的父类中模板方法, 当修改算法时, 子类会自动继承这些修改
  • 实现了最大化代码复用, 即统一了算法, 也提供了很大的灵活性. 将算法中的步骤推迟到子类实现
  • 抽象类中模板方法 需要用 final 关键字修饰, 防止子类重写模板方法
  • 缺点: 每个不同的实现都需要一个子类实现, 导致类的个数增加, 使得系统更加庞大


(二) 命令模式

在软件系统中, 我们经常需要向某些对象发送请求访问, 然后执行方法. 此时, “行为请求者” 与 “行为实现者” 通常呈现一种 “紧耦合”

命令模式(Command Pattern): 将一个请求封装成一个命令对象, 以便使用不同的参数来表示不同的请求, 并组合 “行为实现者” 对象 来执行真正的方法

命令模式封装 命令对象 , 请求的发送者通过对应的命令对象调用请求的执行者, 让对象之间的调用关系更灵活, 实现 请求的发送者 与 请求的执行者 解耦


举例: 随着5G时代的到来, 诞生基于物联网的一套智能家具: 通过一个App来控制所有只能家具的开关, 如: 第一组开关控制电灯, 第二组开关控制电视, 第三组开关控制音响…

在这里插入图片描述
命令模式中的角色

  • Invoker调用者角色: 相当于智能家具的App界面, 通过命令对象来调用接受者角色, 如: 点击了第二组开关(电视)的开按钮
  • Receiver接受者角色: 真正执行的功能, 如: 电灯角色的开关功能, 电视角色的开关功能…
  • Command命令角色: 通常为接口或实现类, 提供命令执行的抽象方法(撤销的方法)
  • ConcreteCommand命令接口实现对象: 组合一个接受者对象, 并将具体的命令绑定到具体的操作中

Receiver接受者角色(真正执行的功能)

/**
 * 电灯接受者类: 提供开和关的两个方法
 */
class Light {
    public void on() {
        System.out.println("打开电灯");
    }

    public void off() {
        System.out.println("关闭电灯");
    }
}

/**
 * 电视接受者类: 提供开和关的两个方法
 */
class TV {
    public void on() {
        System.out.println("启动电视");
    }

    public void off() {
        System.out.println("关闭电视");
    }
}

Command命令角色(通常为接口或实现类, 提供命令执行的抽象方法)

/**
 * 命令接口: 提供一个执行的抽象方法
 */
interface Command {
    void execute();
}

ConcreteCommand命令接口实现对象(组合一个接受者对象, 并将具体的命令绑定到具体的操作中)

/**
 * 开灯命令类: 实现Command命令接口, 提供一个电灯类的成员属性(为开灯命令execute方法执行提供 具体的执行者)
 */
class LightOnCommand implements Command {
    private Light light;

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

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

/**
 * 关灯命令类: 实现Command命令接口, 提供一个电灯类的成员属性(为关灯灯命令execute方法执行提供 具体的执行者)
 */
class LightOffCommand implements Command {
    private Light light;

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

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

/**
 * 启动电视命令类: 实现Command命令接口, 提供一个电视类的成员属性(为启动电视命令execute方法执行提供 具体的执行者)
 */
class TVOnCommand implements Command {
    private TV tv;

    public TVOnCommand(TV tv) {
        this.tv = tv;
    }

    @Override
    public void execute() {
        tv.on();
    }
}

/**
 * 关闭电视命令类: 实现Command命令接口, 提供一个电视类的成员属性(为关闭电视命令execute方法执行提供 具体的执行者)
 */
class TVOffCommand implements Command {
    private TV tv;

    public TVOffCommand(TV tv) {
        this.tv = tv;
    }

    @Override
    public void execute() {
        tv.off();
    }
}

Invoker调用者角色(智能家具App类)

class RemoteControlApp {

    private Command[] onCommand; // 开命令数组(开灯, 启动电视...)
    private Command[] offCommand; // 关命令数组(关灯, 关闭电视...)

	// 初始化命令数组的大小容量
    public RemoteControlApp(int commandSize) {
        this.onCommand = new Command[commandSize];
        this.offCommand = new Command[commandSize];
    }

	// 为每组开关设置对应的命令
    public void setCommand(int index, Command onCommand, Command offCommand) {
        this.onCommand[index] = onCommand;
        this.offCommand[index] = offCommand;
    }

    // 点击第index组的开的按钮
    public void on(int index) {
        this.onCommand[index].execute();
    }

	// 点击第index组的关的按钮
    public void off(int index) {
        this.offCommand[index].execute();
    }
}

测试:

public class CommandPattern {
    public static void main(String[] args) {
        // 创建电灯、电视实体类: 开和关功能的实际提供者
        Light light = new Light();
        TV tv = new TV();

        // 创建电灯、电视的开和关的命令类
        LightOnCommand lightOnCommand = new LightOnCommand(light);
        LightOffCommand lightOffCommand = new LightOffCommand(light);
        TVOnCommand tvOnCommand = new TVOnCommand(tv);
        TVOffCommand tvOffCommand = new TVOffCommand(tv);

        // 为每组开关设置对应的命令
        RemoteControlApp app = new RemoteControlApp(2);
        app.setCommand(0, lightOnCommand, lightOffCommand);
        app.setCommand(1, tvOnCommand, tvOffCommand);

        app.on(0);
        app.off(0);
        app.on(1);
        app.off(1);
    }
}

在这里插入图片描述
此时, 如果再需要提供一组音响的开关, 无需修改源代码. 只需要提供: 音响类(开和关功能的实际提供者), 然后添加到智能家具App类的对应命令数组中, 即可

命令模式的注意事项和细节

  • 发起请求的对象(调用者) 通过 命令对象 实现与执行请求的对象(接受者)解耦, 调用者只需要调用命令对象的excute()方法就可以是让接受者执行功能. 无需知道接受者对象的任何细节
  • 可以设计成一个命令队列, 把命令对象放到队列中, 就可以让多线程执行命令
  • 命令模式适用于: 触发-反馈机制、订单的撤销与恢复
  • 缺点: 导致这个应用中存在大量的具体命令类, 增加了系统的复杂度


(三) 访问者模式

访问者模式(Visitor Pattern): 是一种 数据结构数据操作 分离的设计模式, 解决 数据结构 和 数据操作 的耦合问题

  • 数据结构: 是一个存储元素(类)的容器集合
  • 数据操作: 对容器集合的元素(类)的操作

访问者模式: 将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式

访问模式的工作原理: 在 被访问的类 中对外提供一个 接待访问者的方法, 并在这个方法中把 被访问的类 传递给 访问者


举例: 在高中时期, 学生是分为两种类型: 理科学生、文科学生. 在一次年级考试成绩汇总中:

  • 理科年级主任希望知道: 对理科学生的 主课成绩 和 理科成绩, 而对文科学生只需要知道主课成绩, 以作对比
  • 文科年级主任正好相反: 对文科学生的 主课成绩 和 理科成绩, 而对理科学生只需要知道主课成绩, 以作对比

可以采用访问者模式思想来实现:

  • 访问者模式中的数据结构: 学生类集合
  • 访问者模式中的数据操作: 不同类型的年纪主任 对 学生成绩 的 不同汇总

访问者模式中角色:

  • Element: 抽象的被访问者, 通常为接口或抽象类, 定义被访问者的公共属性(学生的名称、主课成绩), 以及提供一个 接待(接受)访问者的抽象方法
  • ConcreteElement: 具体的被访问者(理科学生、文科学生), 实现或继承Element类. 定义自己的属性(理科学生的理科成绩、文科学生的文科成绩)
  • Visitor: 抽象的访问者, 通常为接口或者抽象类, 它为每一种ConcreteElement具体被访问者定义了访问的抽象方法, 因此访问者模式尽可能要求ConcreteElement个数稳定
  • ConcreteVisitor: 具体的访问者(文、理科年纪主任), 实现或继承Visitor类, 访问每一种ConcreteElement类, 并在访问时对数据(成绩属性)操作
  • ObjectStructure: 容器集合, 是一个包含元素角色(Element)的容器, 提供让访问者对象遍历容器中的所有元素的方法

Element(抽象的被访问者): 学生抽象类, 定义被访问者的公共属性

abstract class Student {
    private String name; // 学生名称
    private int Chinese; // 语文成绩
    private int English; // 英语成绩
    private int Math; // 数学成绩

    public Student(String name, int Chinese, int English, int Math) {
        this.name = name;
        this.Chinese = Chinese;
        this.English = English;
        this.Math = Math;
    }

    // 在 被访问的类 中对外提供一个 接待访问者的抽象方法
    public abstract void accept(Visitor visitor);
}

ConcreteElement(具体的被访问者): 理科学生、文科学生

class LiKeStudent extends Student {

    private int WuLi; // 物理成绩
    private int ShengWu; // 生物成绩
    private int HuaXue; // 化学成绩

    public LiKeStudent(String name, int Chinese, int English, int Math, int wuLi, int shengWu, int huaXue) {
        super(name, Chinese, English, Math);
        WuLi = wuLi;
        ShengWu = shengWu;
        HuaXue = huaXue;
    }
	
	// 重写接待访问者的方法, 并将自身对象this传递给访问者, 以便访问者统计理科学生的成绩
	@Override
    public void accept(Visitor visitor) {
        visitor.total(this);
    }
}

class WenKeStudent extends Student {

    // 文科成绩
    private int DiLi;
    private int ZhengZhi;
    private int LiShi;

    public WenKeStudent(String name, int Chinese, int English, int Math, int diLi, int zhengZhi, int liShi) {
        super(name, Chinese, English, Math);
        DiLi = diLi;
        ZhengZhi = zhengZhi;
        LiShi = liShi;
    }
    
	// 重写接待访问者的方法, 并将自身对象this传递给访问者, 以便访问者统计文科学生的成绩
	@Override
    public void accept(Visitor visitor) {
        visitor.total(this);
    }
}

Visitor(抽象的访问者): 它为每一种具体被访问者(文科、理科学生对象)定义了访问的抽象方法(统计成绩的方法)

interface Visitor {
	// 统计理科学生的抽象方法
    void total(LiKeStudent student);
    // 统计文科学生的抽象方法
    void total(WenKeStudent student);
}

理科年级主任具体访问者

class liKeVisitor implements Visitor {

	// 对理科学生统计 主课成绩 和 理科成绩
    @Override
    public void total(LiKeStudent student) {
        System.out.print(student.getName() + "的主课成绩 = " + (student.getChinese() + student.getMath() + student.getEnglish()));
        System.out.println(", 理科成绩 = " + (student.getWuLi() + student.getHuaXue() + student.getShengWu()));
    }

	// 对文科学生只统计 主课成绩
    @Override
    public void total(WenKeStudent student) {
        System.out.println(student.getName() + "的主课成绩 = " + (student.getChinese() + student.getMath() + student.getEnglish()));
    }
}

文科年级主任具体访问者

class WenKeVisitor implements Visitor {

	// 对文科学生统计 主课成绩 和 文科成绩
    @Override
    public void total(LiKeStudent student) {
        System.out.println(student.getName() + "的主课成绩 = " + (student.getChinese() + student.getMath() + student.getEnglish()));
    }

	// 对理科学生只统计 主课成绩
    @Override
    public void total(WenKeStudent student) {
        System.out.print(student.getName() + "的主课成绩 = " + (student.getChinese() + student.getMath() + student.getEnglish()));
        System.out.println(", 文科成绩 = " + (student.getDiLi() + student.getLiShi() + student.getZhengZhi()));
    }
}

ObjectStructure(对象结构角色): 维护着一个存储Student的集合

class ObjectStructure {

    // 提供存储Student元素的集合
    private List<Student> students = new ArrayList<>();

    // 添加Student元素
    public void attach(Student student) {
        students.add(student);
    }

    // 根据不同的访问者, 获取学生成绩
    public void totalCount(Visitor visitor) {
        for (Student student : students) {
            student.accept(visitor);
        }
    }
}

测试:

public class VisitorPattern2 {
    public static void main(String[] args) {
        LiKeStudent zhangSan = new LiKeStudent("理科学生: 张三", 80, 80, 80, 50, 50, 50);
        WenKeStudent liSi = new WenKeStudent("文科学生: 李四", 90, 90, 90, 60, 60, 60);

        ObjectStructure objectStructure = new ObjectStructure();
        objectStructure.attach(zhangSan);
        objectStructure.attach(liSi);

        System.out.println("=============理科年纪主任获取学生成绩==============");
        objectStructure.totalCount(new liKeVisitor());
        System.out.println();
        System.out.println("=============文科年纪主任获取学生成绩==============");
        objectStructure.totalCount(new WenKeVisitor());
    }
}

在这里插入图片描述
通过使用访问者模式实现了需求, 但并没有感觉到访问者模式的好处. 此时, 再来一个访问者(校长), 他需要看所有学生的汇总成绩(不分文理科), 我们只需添加一个访问者即可, 无需关心学生区分是文科生还是理科生, 因为对Visitor对不同类型的学生分开处理了

class XiaoZhangVisitor implements Visitor {

    @Override
    public void total(LiKeStudent student) {
        System.out.println(student.getName() + "的总成绩 = " + (student.getChinese() + student.getMath() + student.getEnglish() + student.getChinese() + student.getMath() + student.getEnglish()));
    }

    @Override
    public void total(WenKeStudent student) {
        System.out.println(student.getName() + "的总成绩 = " + (student.getChinese() + student.getMath() + student.getEnglish() + student.getDiLi() + student.getLiShi() + student.getZhengZhi()));
    }
}

public class VisitorPattern2 {
    public static void main(String[] args) {
        LiKeStudent zhangSan = new LiKeStudent("理科学生: 张三", 80, 80, 80, 50, 50, 50);
        WenKeStudent liSi = new WenKeStudent("文科学生: 李四", 90, 90, 90, 60, 60, 60);

        ObjectStructure objectStructure = new ObjectStructure();
        objectStructure.attach(zhangSan);
        objectStructure.attach(liSi);

        System.out.println("=============理科年纪主任获取学生成绩==============");
        objectStructure.totalCount(new liKeVisitor());
        System.out.println();
        System.out.println("=============文科年纪主任获取学生成绩==============");
        objectStructure.totalCount(new WenKeVisitor());
        System.out.println();
        System.out.println("=============校长获取学生成绩==============");
        objectStructure.totalCount(new XiaoZhangVisitor());
    }
}

在这里插入图片描述
访问者模式的注意事项和细节

  • 访问者模式能在不修改对象容器结构中的元素的情况下, 为容器中的元素添加新的功能(访问者)
  • 访问者符合单一职责原则, 访问者把对元素的某种操作行为封装在一起, 使得访问者的功能单一
  • 破坏类的封装性: 具体的元素对访问者公布细节, 在accept()方法中, 把本类对象传递给访问者
  • 违背开闭原则: 对新增元素类型, 需要在访问者中添加对应的新增元素的具体访问操作方法, .
  • 违背依赖导致原则: 访问者依赖的是具体元素, 而不是抽象类 total(LiKeStudent)、total(WenKeStudent)
  • 适用于一个系统中有比较稳定的数据结构(具体元素类型稳定不变), 但有经常变化的功能需求(对具体元素的操作需求)的场景


(四) 迭代器模式

迭代器模式(Iterator Pattern): 提供一种遍历容器中元素的同一接口, 用一致的方法遍历容器中的元素, 不需要知道容器对象的底层实现(数组, 集合…), 即: 不暴露其内部的结构

迭代器模式中的角色:

  • Iterator: 迭代器接口, 一般包含两个方法:
    • boolean hasNext(): 返回布尔值, 判断是否有下一个元素
    • Object next(): 获取当前元素
  • ConcreteIterator: 具体迭代器, 组合要遍历的对象容器, 根据对象容器的结构类型来实现 hasNext() 和 next() 方法
  • Aggregate: 抽象聚合接口, 提供一个返回 Iterator 对象的 crateIterator() 抽象方法
  • ConcreteAggregate: 具体聚合类, 组合存储对象的容器, 根据对象容器的结构类型来实现 crateIterator() 方法, 返回具体迭代器

举例: 一个班级有多个学生, 当不同的程序员来实现这个需求时, 可能选择的储存学生对象的容器是不同的: 可以是Array数组, 也有可能是List集合…

学生实体, 容器存储的元素对象

class Student {
    String name;

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

    public String getName() {
        return name;
    }
}

Iterator(迭代器接口)

interface Iterator {
    boolean hasNext(); // 返回布尔值, 判断是否有下一个元素
    Object next(); // 获取当前元素
}

ConcreteIterator(具体迭代器): 存储结构为 数组 和 List集合 的迭代器, 分别实现hasNext() 和 next() 方法

class ArrayStudentIterator implements Iterator {

    private Student[] students;
    private int currentIndex; // 当前遍历索引

    public ArrayStudentIterator(Student[] students) {
        this.students = students;
        this.currentIndex = 0;
    }

    @Override
    public boolean hasNext() {
        if (currentIndex > students.length - 1 || students[currentIndex] == null) {
            return false;
        }
        return true;
    }

    @Override
    public Object next() {
        return students[currentIndex++];
    }
}

// 存储结构为List集合的迭代器
class ListStudentIterator implements Iterator {

    private List<Student> students;
    private int currentIndex; // 当前遍历索引

    public ListStudentIterator(List<Student> students) {
        this.students = students;
        this.currentIndex = -1;
    }

    @Override
    public boolean hasNext() {
        if (currentIndex > students.size() - 2) {
            return false;
        }
        return true;
    }

    @Override
    public Object next() {
        return students.get(++currentIndex);
    }
}

Aggregate(抽象聚合接口): 提供一个返回 Iterator 对象的 crateIterator() 抽象方法

interface Aggregate {
    Iterator createIterator();
}

ConcreteAggregate(具体聚合类): 根据存储的容器类型不同, 返回不同的Iterator迭代器

class ArrayStudent implements Aggregate {
    private Student[] students;

	// 初始化数据
    public ArrayStudent() {
        students = new Student[5];
        for (int i = 0; i < 5; i++) {
            students[i] = new Student("学生" + (i + 1));
        }
    }

    @Override
    public Iterator createIterator() {
        return new ArrayStudentIterator(students);
    }
}

class ListStudent implements Aggregate {
    private List<Student> students;

	// 初始化数据
    public ListStudent() {
        students = new ArrayList<>();
        for (int i = 5; i < 10; i++) {
            students.add(new Student("学生" + (i + 1)));
        }
    }

    @Override
    public Iterator createIterator() {
        return new ListStudentIterator(students);
    }
}

测试:

public class IteratorPattern {
    public static void main(String[] args) {
        System.out.println("===========数组Iterator===============");
        ArrayStudent arrayStudent = new ArrayStudent();
        Iterator arrayIterator = arrayStudent.createIterator();
        while (arrayIterator.hasNext()) {
            System.out.println(((Student) arrayIterator.next()).getName());
        }

        System.out.println("===========集合Iterator===============");
        ListStudent listStudent = new ListStudent();
        Iterator ListIterator = listStudent.createIterator();
        while (ListIterator.hasNext()) {
            System.out.println(((Student) ListIterator.next()).getName());
        }

    }
}

在这里插入图片描述
迭代器模式的注意事项和细节

  • 提供一个统一的方法遍历对象, 隐藏了聚合的内部结构, 客户端只能通过迭代器遍历聚合元素, 而不知道聚合的具体组成
  • 每个聚合对象都要创建一个迭代器, 会生成大量的迭代器类.


(五) 观察者模式

观察者模式(Observer Pattern): 又称之为发布-订阅模式, 定义对象之间的一对多的依赖关系, 一的一方是被依赖的对象, 多的一方是依赖对象. 当被依赖对象的状态发送改变时, 需要通知依赖它的所有对象并自动更新

观察者模式中的角色:

  • Subject: 抽象主题接口, 定义注册(register)和删除(remove)观察者的抽象方法, 以及通知(notify)所有观察者的抽象方法
  • ConcreteSubject: 具体主题类, 被依赖的类(一的一方) . 内部维护着一个观察者对象集合
  • Observer: 抽象观察者接口, 定义一个更新(update)的抽象方法, 以供notify方法调用
  • ConcreteObserver: 具体观察者类, 实现Observer接口, 重写update方法

举例: 各大互联网公司的首页网站都会去访问气象局的提供的天气数据接口, 一旦气象局获取到新的天气数据, 要立即通知所有的使用它接口的网站更新到最新的天气数据

Subject主题接口 和 Observer观察者接口

interface Subject {
    void registerObserver(Observer observer); // 注册观察者

    void removeObserver(Observer observer); // 移除观察者

    void notifyObserver(); // 通知所有的已注册的观察者
}

interface Observer {
    void update(String weatherInfo); // 观察者更新天气的抽象方法
}

ConcreteSubject(气象局主题类)

class WeatherBureau implements Subject {

    // 天气信息
    private String weatherInfo;

    // 维护着已注册的观察者
    private List<Observer> observers = new ArrayList<>();

    // 设置最新的天气信息, 通知所有的已注册的观察者
    public void setNewWeather(String weatherInfo) {
        this.weatherInfo = weatherInfo;
        notifyObserver();
    }

    // 注册观察者
    @Override
    public void registerObserver(Observer observer) {
        observers.add(observer);
    }

    // 移除观察者
    @Override
    public void removeObserver(Observer observer) {
        observers.remove(observer);
    }

    // 通知所有的已注册的观察者
    @Override
    public void notifyObserver() {
        for (Observer o : observers) {
            o.update(this.weatherInfo);
        }
    }
}

ConcreteObserver: 百度、腾讯具体的观察者

class BaiduObserver implements Observer {

    @Override
    public void update(String weatherInfo) {
        System.out.println("腾讯官网" + weatherInfo);
    }
}

class TencentObserver implements Observer {

    @Override
    public void update(String weatherInfo) {
        System.out.println("百度官网" + weatherInfo);
    }
}

测试:

public class ObserverPattern {
    public static void main(String[] args) {
    	// 气象局主题
        WeatherBureau weatherBureau = new WeatherBureau();
        // 注册百度观察者
        weatherBureau.registerObserver(new BaiduObserver());
        // 注册腾讯观察者
        weatherBureau.registerObserver(new TencentObserver());
		// 气象局获取到最新天气信息, 方法内部会通知所有观察者更新
        weatherBureau.setNewWeather("最新天气消息: 晴转多云");
    }
}

在这里插入图片描述



(六) 中介者模式

中介者模式(Mediator Pattern): 提供一个中介者对象来封装一系列的对象交互, 或者说中介者对象处理不同类之间的通信(相互调用), 使得各个对象不需要显示地相互调用, 从而使得其耦合松散, 而且可以独立地改变他们之间的交互

中介者模式中的角色:

  • Mediator: 抽象中介者: 将原先各个类之间的相互依赖并调用对方的方法, 转换为 各个类只依赖中介者对象, 将调用的方法抽取到中介者对象中, 由具体的中介者对象实现 (各个类在中介者模式中称之为 同事类)
  • CollcreteMediator: 具体的中介者, 内部会维护所有的同事类(Map集合), 同时实现共有方法, 根据不同的同事类实现不同的逻辑
  • Colleague: 抽象的同事类, 组合依赖中介者对象, 通过中介者对象来实现共同方法
  • CollcreteMediator: 具体同事类, 无需知道(依赖调用)其他数据库同事类, 通过中介者对象来调用实现功能

举例: 为了保证线上系统的数据安全和快速读取, 一般会提供三个数据库:

  • 主数据库: 当主数据库数据更新时, 需要同步到 备份数据库 和 Redis数据库
  • Redis数据库: 快速读取, 当Redis数据库数据更新时, 需要同步到 备份数据库 和 主数据库
  • 备份数据库: 数据备份, 保证数据安全. 无法手动操作备份数据库

未采用中介者模式来实现此需求, 代码如下:
在这里插入图片描述

class MainMysql {
    private List<String> dataList = new ArrayList<>(); // 用集合表示数据库存储内容
    private Redis redis; // 组合Redis对象
    private BackupMysql backupMysql; // 组合BackupMysql 对象

    public void addData(String data) {
        dataList.add(data);
        System.out.println(data + " 添加到主数据库中, 主数据库存储元素: " + dataList);
        SyncData(data);
    }

	// 同步方法: 将主数据库的更新数据 同步到 Redis数据库 和 备份数据库
    public void SyncData(String data) {
        redis.getDataList().add(data);
        System.out.println(data + " 同步到Redis数据库中, Redis数据库存储元素: " + dataList);

        backupMysql.getDataList().add(data);
        System.out.println(data + " 同步到备份数据库中, 备份数据库存储元素: " + dataList);
    }
}

class Redis {
    private List<String> dataList = new ArrayList<>(); // 用集合表示数据库存储内容
    private MainMysql mainMysql; // 组合MainMysql 对象
    private BackupMysql backupMysql; // 组合BackupMysql 对象

    public void addData(String data) {
        dataList.add(data);
        System.out.println(data + " 添加到Redis数据库中, Redis数据库存储元素: " + dataList);
        SyncData(data);
    }
	
	// 同步方法: 将Redis数据库的更新数据 同步到 主数据库 和 备份数据库
    public void SyncData(String data) {
        mainMysql.getDataList().add(data);
        System.out.println(data + " 同步到主数据库中, 主数据库存储元素: " + dataList);

        backupMysql.getDataList().add(data);
        System.out.println(data + " 同步到备份数据库中, 备份数据库存储元素: " + dataList);
    }
}

class BackupMysql {
    private List<String> dataList = new ArrayList<>(); // 用集合表示数据库存储内容
}

测试:

public class MediatorPattern_Error {
    public static void main(String[] args) {
        MainMysql mainMysql = new MainMysql();
        Redis redis = new Redis();
        BackupMysql backupMysql = new BackupMysql();

        mainMysql.setBackupMysql(backupMysql);
        mainMysql.setRedis(redis);
        redis.setMainMysql(mainMysql);
        redis.setBackupMysql(backupMysql);

        mainMysql.addData("张三");
        System.out.println();
        redis.addData("李四");

    }
}

在这里插入图片描述
上述代码已实现了需求, 但出现 数据库类复杂且相互引用, 扩展性、维护性难度大 等问题

接下来使用中介者模式来实现需求: 将 “调用数据同步方法” 的操作交给中介者类, 所有数据库类只依赖中介者对象, 与其他数据库类无关联在这里插入图片描述

Mediator(抽象中介者): 将 “调用数据同步方法” 的操作交给中介者类, 并提供一个注册数据库同事对象的方法(将数据库同事对象添加到其子类的集合中)

abstract class Mediator {

    // 注册数据库对象, 将数据库同事对象添加到中介者维护的数据库对象集合
    public abstract void register(String databaseName, DatabaseColleague databaseColleague);

    // 同步数据的抽象方法
    public abstract void SyncData(String databaseName, String data);
}

ConcreteMediator(具体的中介者): 内部维护着所有数据库同事对象Map集合(通过数据库名称来区分数据库) 和 具体实现数据同步方法(通过不同的数据库名称, 来实现不同数据库的同步数据操作)

class ConcreteMediator extends Mediator {

    // 维护着所有数据库同事对象
    private Map<String, DatabaseColleague> databaseMap;

    public ConcreteMediator() {
         databaseMap = new HashMap<>();
    }

	// 将数据库同事对象添加到Map集合中, 通过 databaseName数据库名称来区分
    @Override
    public void register(String databaseName, DatabaseColleague databaseColleague) {
        databaseMap.put(databaseName, databaseColleague);
    }

	// 中介者 通过不同的数据库名称, 来实现不同数据库的同步数据操作
    @Override
    public void SyncData(String databaseName, String data) {
        if("MainMysql".equals(databaseName)) {
            DatabaseColleague redis = databaseMap.get("Redis");
            redis.getDataList().add(data);
            System.out.println(data + " 同步到Redis数据库中, Redis数据库存储元素: " + redis.getDataList());

            DatabaseColleague backupMysql = databaseMap.get("BackupMysql");
            backupMysql.getDataList().add(data);
            System.out.println(data + " 同步到备份数据库中, 备份数据库存储元素: " + backupMysql.getDataList());
        } else if ("Redis".equals(databaseName)) {
            DatabaseColleague mainMysql = databaseMap.get("MainMysql");
            mainMysql.getDataList().add(data);
            System.out.println(data + " 同步到主数据库中, 主数据库存储元素: " + mainMysql.getDataList());

            DatabaseColleague backupMysql = databaseMap.get("BackupMysql");
            backupMysql.getDataList().add(data);
            System.out.println(data + " 同步到备份数据库中, 备份数据库存储元素: " + backupMysql.getDataList());
        }
    }
}

Colleague(抽象数据库同事类): 组合中介者对象, 通过中介者对象来实现数据同步的方法

abstract class DatabaseColleague {
    private List<String> dataList; // 用集合表示数据库存储内容
    protected Mediator mediator; // 中介者对象, 通过中介者对象来实现数据同步的方法

    public DatabaseColleague(Mediator mediator, String databaseName) {
        this.mediator = mediator;
        mediator.register(databaseName, this);
        dataList = new ArrayList<>();
    }

	// 数据库新增数据的方法
    public abstract void addData(String databaseName, String data);

}

ConcreteColleague(具体的数据库同事类): 无需知道(依赖调用)其他数据库同事类, 通过中介者对象来调用实现功能

class MainMysqlColleague extends DatabaseColleague {

    public MainMysqlColleague(Mediator mediator, String databaseName) {
        super(mediator, databaseName);
    }

    @Override
    public void addData(String databaseName, String data) {
        super.getDataList().add(data);
        System.out.println(data + " 添加到主数据库中, 主数据库存储元素: " + super.getDataList());

        // 通过中介者对象来实现数据同步功能
        super.mediator.SyncData(databaseName, data);
    }
}

class RedisColleague extends DatabaseColleague {

    public RedisColleague(Mediator mediator, String databaseName) {
        super(mediator, databaseName);
    }

    @Override
    public void addData(String databaseName, String data) {
        super.getDataList().add(data);
        System.out.println(data + " 添加到Redis数据库中, Redis数据库存储元素: " + super.getDataList());

        // 通过中介者对象来实现数据同步功能
        super.mediator.SyncData(databaseName, data);
    }
}

class BackupMysqlColleague extends DatabaseColleague {
    public BackupMysqlColleague(Mediator mediator, String databaseName) {
        super(mediator, databaseName);
    }
}

测试:

public class MediatorPattern {
    public static void main(String[] args) {
    	// 创建中介者对象
        Mediator mediator = new ConcreteMediator();
        // 创建主数据库同事对象, 依赖中介者对象 和 数据库名称
        MainMysqlColleague mainMysql = new MainMysqlColleague(mediator, "MainMysql");
        // 创建Redis数据库同事对象, 依赖中介者对象 和 数据库名称
        RedisColleague redis = new RedisColleague(mediator, "Redis");
        // 创建备份数据库同事对象, 依赖中介者对象 和 数据库名称
        BackupMysqlColleague backupMysql = new BackupMysqlColleague(mediator, "BackupMysql");

        mainMysql.addData("MainMysql", "张三");
        System.out.println();

        redis.addData("Redis", "李四");
        System.out.println();
    }
}

在这里插入图片描述
中介者模式的注意事项和细节

  • 多个类相互耦合, 会形成网址结构, 使用中介者模式将网状结构分离成星型结构(只依赖中介者), 减少类间的依赖, 进行解耦, 符合迪米特法则
  • 中介者对象承担了主要逻辑责任, 使得对象变得过于复杂


(七) 备忘录模式

备忘录模式(Memento Pattern): 在不破坏封装性的前提下, 捕获一个对象的内部状态, 并在该对象之外保存这个状态, 以便在适当的时候恢复对象

备忘录模式中的角色:

  • Originator: 是一个需要保存状态属性的普通对象, 内部提供一个创建Memento备忘录对象的方法 和 恢复状态的方法
  • Memento: 备忘录对象, 真正负责存储的对象, 存储的内容是 Originator对象的内部状态成员属性
  • Caretaker: 守护者对象, 提供一个容器集合管理多个 Memento对象, 以及提供添加和获取Memento备忘录对象的方法

Memento备忘录对象: 将Originator对象要保存的状态信息 转换为 Memento备忘录对象

class Memento {
    private String state; // Originator对象要保存的状态信息

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

Originator: 一个需要保存状态属性的普通对象

class Originator {
    private String state; // 需要储存的状态信息

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

    // 创建Memento备忘录对象, 将Originator对象要保存的state状态信息 转换为 Memento备忘录对象
    public Memento crateMemento() {
        return new Memento(state);
    }

    // 通过Memento备忘录对象恢复状态
    public void recoverStateByMemento(Memento memento) {
        this.state = memento.getState();
    }
}

Caretaker守护者对象: 提供一个容器集合管理多个 Memento对象

class Caretaker {
    private List<Memento> mementos; // 通过集合对象来管理Memento对象

    public Caretaker() {
        this.mementos = new ArrayList<>();
    }

    // 添加Mement对象到集合
    public void add(Memento memento) {
        mementos.add(memento);
    }

    // 从集合中获取Memento对象
    public Memento get(int index) {
        return mementos.get(index);
    }
}

测试

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

        Originator originator = new Originator("状态1");
        caretaker.add(originator.crateMemento());
        System.out.println("第一次状态: " + originator.getState());

        originator.setState("状态2");
        caretaker.add(originator.crateMemento());
        System.out.println("第二次状态: " + originator.getState());

        originator.setState("状态3");
        caretaker.add(originator.crateMemento());
        System.out.println("第三次状态: " + originator.getState());

        System.out.println();
        originator.recoverStateByMemento(caretaker.get(0));
        System.out.println("恢复首次状态: " + originator.getState());

    }
}

在这里插入图片描述
备忘录模式的注意事项和细节

  • 给用户提供了一种可以恢复状态的机制, 同时对用户屏蔽了状态保存的细节
  • 备忘录模式的应用场景: 游戏存档、浏览器历史记录回退、数据库事务回滚、编辑器的 Ctrl + Z…
  • 缺点: 需要保存的成员属性过多, 或者频繁地保存状态, 会消耗一定量的内存空间


(八) 解释器模式

在大多数的编程语言中都支持: 运算表达式计算、正则表达式等, 它们都是采用解释器模式实现的, 工作原理大致是: 对一个算术表达式(a+b+c) 通过词法分析其形成词法单元, 而后这些词法单元再通过语法分析器构建语法分析数, 最终形成一个抽象的语法分析树

解释器模式(Interpreter Pattern): 是指 给定一个语言表达式, 定义它的文法(语法规则), 并定义一个解释器, 使得该解释器来解释执行语言表达式


以 四则运算表达式a + b - c 例子来理解 解释器模式:

  • 表达式 a + b - c 首先会转化成字符数组: [‘a’, ‘+’, ‘b’, ‘-’, ‘c’]
  • 字符数组中存在两种类型的字符: 变量(a,b,c) 和 运算符(+, -)
  • 我们需要分别对两种类型的字符分别解析处理

解释器模式模式中存在角色

  • AbstractExpression抽象的表达式: 用来存储表达式中的每一个字符(‘a’, ‘+’, ‘b’, ‘-’, ‘c’), 并提供一个解析处理字符的抽象方法interpret
  • TerminalExpression终结符表达式: 字面上的意思是 存储终结一个表达式的一种字符, 在四则运算表达式中指的是 a、b、c, 即 变量. 重写interpret解释方法(对变量如何处理)
  • NonTerminalExpression非终结符表达式: 刚好相反, 存储非终结一个表达式的一种字符, 在四则运算表达式中指的是 +、-, 即 运算符. 重写interpret解释方法(对运算符如何处理), 每个运算符都只和自己左右两个数字有关系, 但左右不一定都是数字, 也有可能是AbstractExpression表达式, 因此组合左右两个AbstractExpression对象, 最终会形成一个树(类似于二叉树)
  • Context环境上下文角色: 存储解释器之外的全局信息, 以及主要逻辑实现. 如: 接收存储一个表达式(a + b - c), 并将表达式 先转化成字符数组 然后再转化成对应的表达式对象(终结符, 非终结符对象), 最终存储成一个树

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值