(4).使用场景
优点:
- 扩展性好: 在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。
- 复用性好: 通过访问者来定义整个对象结构通用的功能,提高了复用程度。
- 分离无关行为: 通过访问者来分离无关的行为,把相关的行为封装在一起,构成一个访问者,这样每一个访问者的功能比较单一。
缺点:
- 对象结构变化很困难: 在访问这模式中,每增加一个新的元素类,都需要在每一个具体的访问者类总增加响应的具体操作,违背了"开闭原则"
- 违反可依赖导致原则: 访问者模式以来了具体类: 而没有依赖抽象类。
使用场景:
- 对象结构相对稳定,但操作算法经常变化的程序。
- 对象结构中的对象需要提供多种不同且不相关的操作,而且要避免让这些操作的变化影响对象的结构。
(6).扩展
访问者模式用到了一种双分派技术。
- 分派:
变量被声明时的类型叫做变量的静态类型
,有些人又把静态类型叫做明显类型;而变量所引用的对象的真实类型又叫做变量的实际类型
。比如:Map map=new HashMa,map
变量的静态类型时:map
,实际类型时:hashMap
。根据对象的类型而对方法进行的选择就是分派、分派分为两者: 静态分派和动态分派。 - 静态分派:
发生在编译期间
,分派根据静态类型信息发生。静态分派对于我门来说并不陌生,方法重载就是静态分派
。 - 动态分派:
发生在运动期间
,动态分派的置换某个方法。Java通过方法的重写支持动态分派
。
编译看左边,执行看右边
- 动态分派
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编译器在编译时期并不是总是知道那些代码会被执行,因为编译器仅仅知道对象的静态的类型,而不知道对象的真实类型
;而方法的调用则是根据对象的真实类型,而不是静态类型
。
- 静态分派
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); } }
这个结果可能出乎一些人的意料了,为什么呢?
重载方法的分派是根据静态类型进行的,这个分派过程在编译时期就完成了。
- 双分派
所谓双分派技术就是在选择一个方法的时候,不仅仅要根据消息接收者(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决斗的效果不理想(分低)可以让游戏恢复到决斗之前的状态。
要实现上述案列,有两种方式:
- “白箱备忘录模式”
- “黑箱备忘录模式”
- 白箱备忘录模式
备忘录角色对任何对象都提供一个接口,即宽接口,备忘录角色的内部所存储的状态就对多有对象公开。
发起人角色
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; } }