110.【十万字带你深入学习23种设计模式】(二十五)

简介: 110.【十万字带你深入学习23种设计模式】
(4).使用场景

优点:

  1. 扩展性好: 在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。
  2. 复用性好: 通过访问者来定义整个对象结构通用的功能,提高了复用程度。
  3. 分离无关行为: 通过访问者来分离无关的行为,把相关的行为封装在一起,构成一个访问者,这样每一个访问者的功能比较单一。

缺点:

  1. 对象结构变化很困难: 在访问这模式中,每增加一个新的元素类,都需要在每一个具体的访问者类总增加响应的具体操作,违背了"开闭原则"
  2. 违反可依赖导致原则: 访问者模式以来了具体类: 而没有依赖抽象类。

使用场景:

  • 对象结构相对稳定,但操作算法经常变化的程序。
  • 对象结构中的对象需要提供多种不同且不相关的操作,而且要避免让这些操作的变化影响对象的结构。
(6).扩展

访问者模式用到了一种双分派技术。

  1. 分派: 变量被声明时的类型叫做变量的静态类型,有些人又把静态类型叫做明显类型;而变量所引用的对象的真实类型又叫做变量的实际类型。比如: Map map=new HashMa,map变量的静态类型时: map,实际类型时:hashMap。根据对象的类型而对方法进行的选择就是分派、分派分为两者: 静态分派和动态分派。
  2. 静态分派:发生在编译期间,分派根据静态类型信息发生。静态分派对于我门来说并不陌生,方法重载就是静态分派
  3. 动态分派: 发生在运动期间,动态分派的置换某个方法。Java通过方法的重写支持动态分派

编译看左边,执行看右边

  1. 动态分派
package com.tian;
public class Test {
    public static void main(String[] args) {
        Animal a = new Dog();
        a.execute();
        Animal a1 = new Cat();
        a1.execute();
    }
}
class Animal {
    public void execute() {
        System.out.println("Animal");
    }
}
class Dog extends Animal {
    @Override
    public void execute() {
        System.out.println("dog");
    }
}
class Cat extends Animal {
    @Override
    public void execute() {
        System.out.println("cat");
    }
}

Java编译器在编译时期并不是总是知道那些代码会被执行,因为编译器仅仅知道对象的静态的类型,而不知道对象的真实类型;而方法的调用则是根据对象的真实类型,而不是静态类型

  1. 静态分派
package com.tian;
class Animal {
}
class Dog extends Animal {
}
class Cat extends Animal {
}
class Execute {
    public void execute(Animal a) {
        System.out.println("Animal");
    }
    public void execute(Dog d) {
        System.out.println("dog");
    }
    public void execute(Cat c) {
        System.out.println("cat");
    }
}
public class Test {
    public static void main(String[] args) {
        Animal a = new Animal();
        Animal a1 = new Dog();
        Animal a2 = new Cat();
        Execute exe = new Execute();
        exe.execute(a);
        exe.execute(a1);
        exe.execute(a2);
    }
}

这个结果可能出乎一些人的意料了,为什么呢?

重载方法的分派是根据静态类型进行的,这个分派过程在编译时期就完成了

  1. 双分派

所谓双分派技术就是在选择一个方法的时候,不仅仅要根据消息接收者(receiver)的运行时区别,还要根据参数的运行时区别。

package com.tian;
class Animal {
    public void accept(Execute exe) {
        exe.execute(this);
    }
}
class Dog extends Animal {
    public void accept(Execute exe) {
        exe.execute(this);
    }
}
class Cat extends Animal {
    public void accept(Execute exe) {
        exe.execute(this);
    }
}
class Execute {
    public void execute(Animal a) {
        System.out.println("animal");
    }
    public void execute(Dog d) {
        System.out.println("dog");
    }
    public void execute(Cat c) {
        System.out.println("cat");
    }
}
public class Test {
    public static void main(String[] args) {
        Animal a = new Animal();
        Animal d = new Dog();
        Animal c = new Cat();
        Execute exe = new Execute();
        a.accept(exe);
        d.accept(exe);
        c.accept(exe);
    }
}

在上面代码中,客户端将Execute对象做为参数传递给Anmal类型的变量调用的方法,这里完成第次分派,这里是方法重写,所以是动态分派,也就是执行实际类型中的方法,同时也 将自己this作为参数传递进去,这里就完成了第二次分派,这里的Execute类中有多个重载的方法,而传递进行的是this就是具体的实际类型的对象

说到这里,我们已经明白双分派是怎么回事了,但是它有什么效果呢? 就是可以实现方法的动态绑定我们可以对上面的程序进行修改。

双分派实现动态绑定的本质,就是在重载方法委派的前面加上了继承体系中覆盖的环节,由于覆盖是动态的,所以重载就是动态的了

10.备忘录模式

(1).概述

备忘录模式提供了一种状态恢复的实现机制,使得用户可以方便的找回到一个特定的历史步骤,当新的状态无效或者存在问题的时候,可以使用暂时存储起来的备忘录状态进行恢复,很多软件都提供了撤销操作,如word、记事本,Photoshop、IDEA等软件在编辑时按下 CTRL+Z组合键能够撤销当前操作,使文档恢复之前的状态;还有浏览器的后退键、数据库事务管理中的回滚操作、玩游戏时的中间结果存档功能、数据库与操作系统的备份操作、棋类游戏中的悔棋功能等都属于这一类。

定义:

又叫快照模式,在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后当需要时能将该对象恢复到原先保存的状态。

(2).结构

备忘录模式的主要角色如下:

  • 发起人角色: 记录当前时刻的内部状态信息,提供创建备忘录恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息。
  • 备忘录角色: 负责人存储发起人的内部状态,在需要的时候提供这些内部状态给发起人。
  • 管理者角色: 对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改

备忘录有两个等效的接口:

  • 窄接口: 管理者对象(和其他发起人对象之外的任何对象)看到的是备忘录的窄接口,这个窄接口只允许它把备忘录对象传给其他的对象。
  • 宽接口: 与管理者看到的窄接口相反,发起人对象可以看到一个宽接口,这个宽接口允许它读取所有的数据,一边根据这些数据恢复这个发起人对象的内部状态。
(3).案列实现

游戏挑战BOSS

游戏中的某个场景,游戏角色有生命力、攻击力、防御力等数据,在打BOSS前和后一定会不一样的,我们允许玩家如果感觉与Boss决斗的效果不理想(分低)可以让游戏恢复到决斗之前的状态。

要实现上述案列,有两种方式:

  • “白箱备忘录模式”
  • “黑箱备忘录模式”
  1. 白箱备忘录模式

备忘录角色对任何对象都提供一个接口,即宽接口,备忘录角色的内部所存储的状态就对多有对象公开。

发起人角色

package com.jsxs.behavioralModel.memento.white_box;
/**
 * @Author Jsxs
 * @Date 2023/4/24 19:39
 * @PackageName:com.jsxs.behavioralModel.memento.white_box
 * @ClassName: GanmeRole
 * @Description: TODO  游戏角色类  (发起人角色)
 * @Version 1.0
 */
public class GameRole {
    private int vit;  //生命力
    private int atk; //攻击力
    private int def; //防御力
    // 初始化状态的方法
    public void initState(){
        this.vit=100;
        this.atk=100;
        this.def=100;
    }
    // 战斗后的方法
    public void fight(){
        this.vit=0;
        this.atk=0;
        this.def=0;
    }
    // 保存角色状态功能
    public RoleStateMemento saveState(){
        return new RoleStateMemento(vit,atk,def);
    }
    // 恢复角色状态功能
    public void recoverState(RoleStateMemento roleStateMemento){
        // 将备忘录中存储的状态赋值给当前对象的成员
       this.atk=roleStateMemento.getAtk();
       this.vit=roleStateMemento.getVit();
       this.def=roleStateMemento.getDef();
    }
    //展示状态功能
    public void stateDisplay(){
        System.out.println("角色生命力:"+vit);
        System.out.println("角色攻击力:"+atk);
        System.out.println("角色防御力:"+def);
    }
    public int getVit() {
        return vit;
    }
    public void setVit(int vit) {
        this.vit = vit;
    }
    public int getAtk() {
        return atk;
    }
    public void setAtk(int atk) {
        this.atk = atk;
    }
    public int getDef() {
        return def;
    }
    public void setDef(int def) {
        this.def = def;
    }
}

备忘录角色

package com.jsxs.behavioralModel.memento.white_box;
/**
 * @Author Jsxs
 * @Date 2023/4/24 19:43
 * @PackageName:com.jsxs.behavioralModel.memento.white_box
 * @ClassName: RoleStateMemento
 * @Description: TODO  备忘录角色
 * @Version 1.0
 */
public class RoleStateMemento {
    private int vit;  //生命力
    private int atk; //攻击力
    private int def; //防御力
    public RoleStateMemento(int vit, int atk, int def) {
        this.vit = vit;
        this.atk = atk;
        this.def = def;
    }
    public RoleStateMemento() {
    }
    public int getVit() {
        return vit;
    }
    public void setVit(int vit) {
        this.vit = vit;
    }
    public int getAtk() {
        return atk;
    }
    public void setAtk(int atk) {
        this.atk = atk;
    }
    public int getDef() {
        return def;
    }
    public void setDef(int def) {
        this.def = def;
    }
}

备忘管理角色

package com.jsxs.behavioralModel.memento.white_box;
/**
 * @Author Jsxs
 * @Date 2023/4/24 19:50
 * @PackageName:com.jsxs.behavioralModel.memento.white_box
 * @ClassName: RoleStateCaretaker
 * @Description: TODO  备忘录管理对象
 * @Version 1.0
 */
public class RoleStateCaretaker {
    // 声明备忘录角色
    private RoleStateMemento roleStateMemento;
    public RoleStateMemento getRoleStateMemento() {
        return roleStateMemento;
    }
    public void setRoleStateMemento(RoleStateMemento roleStateMemento) {
        this.roleStateMemento = roleStateMemento;
    }
}


相关文章
|
5天前
|
设计模式 前端开发 Java
设计模式之美学习(八):为何说要多用组合少用继承?如何决定该用组合还是继承?
设计模式之美学习(八):为何说要多用组合少用继承?如何决定该用组合还是继承?
|
6天前
|
设计模式 存储 算法
设计模式学习心得之五种创建者模式(2)
设计模式学习心得之五种创建者模式(2)
12 2
|
6天前
|
设计模式 安全 Java
设计模式学习心得之五种创建者模式(1)
设计模式学习心得之五种创建者模式(1)
7 0
|
6天前
|
设计模式 uml
设计模式学习心得之前置知识 UML图看法与六大原则(下)
设计模式学习心得之前置知识 UML图看法与六大原则(下)
10 2
|
6天前
|
设计模式 数据可视化 程序员
设计模式学习心得之前置知识 UML图看法与六大原则(上)
设计模式学习心得之前置知识 UML图看法与六大原则(上)
7 0
|
2月前
|
设计模式 安全 Java
【JAVA学习之路 | 基础篇】单例设计模式
【JAVA学习之路 | 基础篇】单例设计模式
|
2月前
|
设计模式 存储 前端开发
JS的几种设计模式,Web前端基础三剑客学习知识分享,前端零基础开发
JS的几种设计模式,Web前端基础三剑客学习知识分享,前端零基础开发
|
2月前
|
设计模式 安全 Java
【设计模式学习】单例模式和工厂模式
【设计模式学习】单例模式和工厂模式
|
2月前
|
设计模式 存储 前端开发
Java从入门到精通:2.2.1学习Java Web开发,了解Servlet和JSP技术,掌握MVC设计模式
Java从入门到精通:2.2.1学习Java Web开发,了解Servlet和JSP技术,掌握MVC设计模式
|
2月前
|
设计模式 算法 程序员
Python从入门到精通:2.1.3深入学习面向对象编程——设计模式的学习与实践
Python从入门到精通:2.1.3深入学习面向对象编程——设计模式的学习与实践