JAVA设计模式(十二)
二十一-访问者模式
有些集合对象存在多种不同元素,这些元素稳定且不易变化,每种元素存在多种不同的访问方式和处理方式。如竹子材料,造纸工厂将其用于造纸 ,手工艺作坊则将其用于编制桌椅板凳。对于每个流量明星,不同的人对其有不同的看法,有的喜欢,有的讨厌等。
访问者模式能把处理方法从数据结构中分离出来,并可以根据需要增加新的处理方法,且不用修改原来的程序代码与数据结构,这提高了程序的扩展性和灵活性。
1.访问者模式的定义与特点
1. 访问者模式定义:将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式。它将对数据的操作与数据结构进行分离,是行为类模式中最复杂的一种模式。
核心点在于分离元素对象的操作和访问方式,将每种操作和访问方式封装成访问者对象,通过访问者对象进行操作和访问。
2. 优点:
- 扩展性好。能够在不改变对象结构中的元素的情况下,为其添加新的操作或访问方式。
- 复用性好。可以通过访问者来定义整个对象结构通用的功能,从而提高代码的复用度。
- 灵活性好。访问者模式将数据结构与作用于结构上的操作解耦,使得操作集合可相对自由地演化而不影响系统的数据结构。
- 符合单一职责原则。访问者模式把相关的行为封装在一起,构成一个访问者,使每一个访问者的功能都比较单一。
3. 缺点:
- 增加新的元素类很困难。在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”。
- 破坏封装。访问者模式中具体元素对访问者公布细节,这破坏了对象的封装性。
- 违反了依赖倒置原则。访问者模式依赖了具体类,而没有依赖抽象类。
2. 模式的结构和实现
访问者包含五个基本的角色,抽象访问者角色,具体访问者角色,抽象元素角色,具体元素角色,对象结构角色。
1. 模式的结构
抽象访问者角色: 定义访问具体元素对象的接口,每个具体元素对象都有一个对应的访问方法visit()。该访问方法参数为具体元素对象。
具体访问者角色: 实现抽象访问者接口,并为每个具体元素对象的访问实现相应的功能。
抽象元素角色:定义一个接口,包含一个接收访问者角色的方法。
具体元素角色:实现抽象元素接口,并使用接收到的访问者对象访问具体元素本身(visitor.visit(this)),可能还包含一些独有的处理逻辑。
对象结构角色:包含一个元素角色的集合,提供一个让访问者遍历集合中所有具体元素的方法。通常包含添加元素对象方法,移除元素对象方法,(访问者)遍历元素对象方法。
结构图:
2.模式的实现
//抽象访问者
public interface AbstractVisitor {
//具体的元素对象
void visit(SpecificElementA elementA);
void visit(SpecificElementB elementB);
}
//具体访问者对象A
public class SpecificVisitorA implements AbstractVisitor{
//不同的访问者对象实现不同的访问逻辑
@Override
public void visit(SpecificElementA elementA) {
System.out.println("具体访问者对象A访问:"+elementA.getElementA());
}
@Override
public void visit(SpecificElementB elementB) {
System.out.println("具体访问者对象A访问:"+elementB.getElementB());
}
}
//具体访问者对象B
public class SpecificVisitorB implements AbstractVisitor{
//不同的访问者对象实现不同的访问逻辑
@Override
public void visit(SpecificElementA elementA) {
System.out.println("具体访问者对象B访问:"+elementA.getElementA());
}
@Override
public void visit(SpecificElementB elementB) {
System.out.println("具体访问者对象B访问:"+elementB.getElementB());
}
}
//抽象元素
public interface AbstractElement {
//声明一个接收方法,被接收的访问者对象作为入参
void accept(AbstractVisitor visitor);
}
//具体元素对象A
public class SpecificElementA implements AbstractElement{
@Override
public void accept(AbstractVisitor visitor) {
//访问者对象访问当前元素
visitor.visit(this);
}
public String getElementA(){
return "具体对象A";
}
}
//具体元素对象A
public class SpecificElementB implements AbstractElement{
@Override
public void accept(AbstractVisitor visitor) {
//访问者对象访问当前元素
visitor.visit(this);
}
public String getElementB(){
return "具体对象B";
}
}
//结构对象角色
public class ObjectStructure {
//元素对象集合
private List<AbstractElement> elementList = new ArrayList<>();
//添加元素对象方法
public void addElement(AbstractElement element){
this.elementList.add(element);
}
//移除元素对象方法
public void removeElement(AbstractElement element){
this.elementList.remove(element);
}
//接收一个具体访问者对象,遍历元素对象,使其接收不同的访问者对象,实现不同的访问结果
public void accept(AbstractVisitor visitor){
if (!CollectionUtils.isEmpty(elementList)){
elementList.forEach(e->e.accept(visitor));
}
}
}
//测试
@Test
public void visitor(){
ObjectStructure structure = new ObjectStructure();
structure.addElement(new SpecificElementA());
structure.addElement(new SpecificElementB());
AbstractVisitor visitorA = new SpecificVisitorA();
AbstractVisitor visitorB = new SpecificVisitorB();
structure.accept(visitorA);
System.out.println("-----------------------");
structure.accept(visitorB);
}
具体访问者对象A访问:具体对象A
具体访问者对象A访问:具体对象B
-----------------------
具体访问者对象B访问:具体对象A
具体访问者对象B访问:具体对象B
3.模式的应用实例
xx电视台开展一次街头采访,采访大众对明星的看法,张三喜欢刘亦菲,但是李四缺不喜欢。李四喜欢迪丽热巴,但是张三觉得迪丽热巴不是他的菜。
分析:明星为抽象元素,刘亦菲和迪丽热巴为具体元素,大众为抽象访问者,张三和李四为具体访问者。xx电视台为对象结构角色
//抽象访问者==大众
public interface AbstractVisitorUser {
//大众对迪丽热巴的看法
void visit(SpecificElmentDLRB dlrb);
//大众对刘亦菲的看法
void visit(SpecificElmentLYF lyf);
}
//具体访问者==李四
public class SpecificVisitorLisi implements AbstractVisitorUser{
@Override
public void visit(SpecificElmentDLRB dlrb) {
System.out.println("李四觉得"+dlrb.userName+"很一般");
}
@Override
public void visit(SpecificElmentLYF lyf) {
System.out.println("李四觉得"+lyf.userName+"很Nice");
}
}
//具体访问者==张三
public class SpecificVisitorZhangSan implements AbstractVisitorUser{
@Override
public void visit(SpecificElmentDLRB dlrb) {
System.out.println("张三很喜欢"+dlrb.userName);
}
@Override
public void visit(SpecificElmentLYF lyf) {
System.out.println("张三觉得"+lyf.userName+"不是他喜欢的类型");
}
}
//抽象元素==明星
public abstract class AbstractElmentStar {
protected String userName;
//明星包含一个大众对他的看法
public abstract void accept(AbstractVisitorUser visitorUser);
}
//具体元素对象==迪丽热巴
public class SpecificElmentDLRB extends AbstractElmentStar{
public SpecificElmentDLRB(String userName){
super.userName = userName;
}
@Override
public void accept(AbstractVisitorUser visitorUser) {
visitorUser.visit(this);
}
}
//具体元素对象==刘亦菲
public class SpecificElmentLYF extends AbstractElmentStar{
public SpecificElmentLYF(String userName){
super.userName = userName;
}
@Override
public void accept(AbstractVisitorUser visitorUser) {
visitorUser.visit(this);
}
}
//结构对象角色==xx电视台街头采访,询问民众对明星的看法
public class ObjectStructureInterview {
//明星对象集合
private List<AbstractElmentStar> starList = new ArrayList<>();
//添加明星方法
public void addStar(AbstractElmentStar star){
this.starList.add(star);
}
//移除明星方法
public void removeStart(AbstractElmentStar star){
this.starList.remove(star);
}
//采访大众,让大众对明星进行评价
public void accept(AbstractVisitorUser visitor){
if (!CollectionUtils.isEmpty(starList)){
starList.forEach(e->e.accept(visitor));
}
}
}
//街头采访测试
@Test
public void visitor(){
//电视台街头采访
ObjectStructureInterview interview = new ObjectStructureInterview();
//预备采访名单
interview.addStar(new SpecificElmentLYF("刘亦菲"));
interview.addStar(new SpecificElmentDLRB("迪丽热巴"));
//随机路人张三
AbstractVisitorUser zhangSan = new SpecificVisitorZhangSan();
//随机路人李四
AbstractVisitorUser liSi = new SpecificVisitorLisi();
interview.accept(zhangSan);
System.out.println("===============");
interview.accept(liSi);
}
张三觉得刘亦菲不是他喜欢的类型
张三很喜欢迪丽热巴
===============
李四觉得刘亦菲很Nice
李四觉得迪丽热巴很一般
3.模式的应用场景
- 对象结构相对稳定,但其操作算法经常变化的程序。
- 对象结构中的对象需要提供多种不同且不相关的操作,而且要避免让这些操作的变化影响对象的结构。
- 对象结构包含很多类型的对象,希望对这些对象实施一些依赖于其具体类型的操作。
4.访问者模式的扩展
与备忘录模式结合使用。因为访问者模式中的“对象结构”是一个包含元素角色的容器,当访问者遍历容器中的所有元素时,常常要用备忘录。
与组合模式结合使用。因为访问者(Visitor)模式中的“元素对象”可能是叶子对象或者是容器对象,如果元素对象包含容器对象,就必须用到组合模式。
二十二-备忘录模式
在现实生活中,我们往往会在不经意之间造成误操作,此时我们想恢复误操作前的状态,此时就需要备忘录模式。比如我们下象棋时,点错了之后,想要悔棋。在idea进行代码编写时,不小心删除了一些重要代码 ,此时有CTRL+Z帮助我们回归前一个状态。
1.模式的定义与特点
备忘录模式:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后当需要时能将该对象恢复到原先保存的状态。该模式又叫快照模式。
优点:
- 提供了一种可以恢复状态的机制。当用户需要时能够比较方便地将数据恢复到某个历史的状态。
- 实现了内部状态的封装。除了创建它的发起人之外,其他对象都不能够访问这些状态信息。
- 简化了发起人类。发起人不需要管理和保存其内部状态的各个备份,所有状态信息都保存在备忘录中,并由管理者进行管理,这符合单一职责原则。
缺点:
- 由于增加了备份,所以一定会造成内存占用过多的问题。
2.模式的结构和实现
备忘录模式包含发起者角色,备忘录角色,管理者角色。
1.备忘录模式的结构
- 发起者角色: 记录一个当前状态,提供一个创建备忘录方法和恢复状态方法,实现其他业务功能,可以访问备忘录的所有信息。
- 备忘录角色: 存储发起者的一个状态,在需要的时候提供该状态进行状态恢复。
- 管理者角色: 管理备忘录角色,当发起者需要恢复数据时,实现恢复逻辑,可以保存和访问备忘录,但是无法修改。
2.备忘录模式的实现
//发起人角色
@Data
public class Originator {
private String status;
//创建备忘录
public Memento createMemento(){
return new Memento(status);
}
//恢复状态
public void restoreMemento(Memento memento){
this.status = memento.getStatus();
}
}
//备忘录角色
@Data
public class Memento {
private String status;
public Memento(String status){
this.status = status;
}
}
//管理者角色
@Data
public class Caretaker {
private Memento memento;
public void setMemento(Memento memento){
this.memento = memento;
}
public Memento getMemento(){
return this.memento;
}
}
//测试
@Test
public void remark() throws CloneNotSupportedException {
Originator originator = new Originator();
originator.setStatus("初始状态");
System.out.println("发起者状态=="+originator.getStatus());
Caretaker caretaker = new Caretaker();
caretaker.setMemento(originator.createMemento());
originator.setStatus("就绪状态");
System.out.println("发起者状态=="+originator.getStatus());
originator.restoreMemento(caretaker.getMemento());
System.out.println("发起者状态=="+originator.getStatus());
}
发起者状态==初始状态
发起者状态==就绪状态
发起者状态==初始状态
3. 实例
用户去逛宠物店,选择多个宠物后,又想回到上一个选择的宠物,老板帮忙记录(仅用于简单示例)
//发起人角色 == 用户
@Data
public class OriginatorUser {
private MementoPet pet;
//让宠物店老板帮忙记住选择的宠物
public MementoPet createMenentoPet(){
return this.pet;
}
//返回上一个选择的宠物
public void reChoosePet(MementoPet pet){
this.pet = pet;
}
}
//备忘录角色==宠物
@Data
public class MementoPet {
private String petName;
private BigDecimal petPrice;
public MementoPet(String petName,BigDecimal petPrice){
this.petName = petName;
this.petPrice = petPrice;
}
}
//管理者角色==老板将你选的宠物记录在本子上
public class CaretakerPet {
//宠物备忘录集合
private List<MementoPet> petList = new ArrayList<>();
//添加选择的宠物备份
public void addMementoPet(MementoPet pet){
this.petList.add(pet);
}
//获取最近选择的一个宠物,获取后从集合移除该宠物
public MementoPet getMementoPet(){
if (!CollectionUtils.isEmpty(this.petList)){
MementoPet mementoPet = this.petList.get(this.petList.size() - 1);
this.petList.remove(mementoPet);
return mementoPet;
}
return null;
}
}
//测试
@Test
public void remark() throws CloneNotSupportedException {
//用户逛宠物店
OriginatorUser user = new OriginatorUser();
//宠物店老板为用户服务
CaretakerPet caretakerPet = new CaretakerPet();
//用户看中了一只加菲猫
user.setPet(new MementoPet("加菲猫",new BigDecimal(3880)));
System.out.println("用户选了"+user.getPet().getPetName()+",宠物价格为:"+user.getPet().getPetPrice());
//老板做一个记录
caretakerPet.addMementoPet(user.createMenentoPet());
//用户又看到了一只布偶猫
user.setPet(new MementoPet("布偶猫",new BigDecimal(4880)));
System.out.println("用户选了"+user.getPet().getPetName()+",宠物价格为:"+user.getPet().getPetPrice());
//老板又做一个记录
caretakerPet.addMementoPet(user.createMenentoPet());
//用户又看到了一只英短猫
user.setPet(new MementoPet("英短猫",new BigDecimal(5880)));
System.out.println("用户选了"+user.getPet().getPetName()+",宠物价格为:"+user.getPet().getPetPrice());
//此时用户又想看看上一个选的是什么猫
user.reChoosePet(caretakerPet.getMementoPet());
System.out.println("用户选了"+user.getPet().getPetName()+",宠物价格为:"+user.getPet().getPetPrice());
//此时用户又想看看上一个选的是什么猫
user.reChoosePet(caretakerPet.getMementoPet());
System.out.println("用户选了"+user.getPet().getPetName()+",宠物价格为:"+user.getPet().getPetPrice());
}
用户选了加菲猫,宠物价格为:3880
用户选了布偶猫,宠物价格为:4880
用户选了英短猫,宠物价格为:5880
用户选了布偶猫,宠物价格为:4880
用户选了加菲猫,宠物价格为:3880
3. 备忘录模式的应用场景
- 需要保存与恢复数据的场景,如玩游戏时的中间结果的存档功能。
- 需要提供一个可回滚操作的场景,如Word、记事本、Photoshop,Eclipse等软件在编辑时按Ctrl+Z组合键,还有数据库中事务操作。
4. 备忘录模式的扩展
备忘录模式和原型模式进行组合使用,Cloneable接口提供了克隆自身的方法,因此可以移除掉备忘录角色。
//发起人角色==组合原型模式
@Data
public class OriginatorPrototype implements Cloneable{
private String status;
//创建备忘录
public Memento createMemento(){
return new Memento(status);
}
public void restoreMemento(OriginatorPrototype memento){
this.status = memento.getStatus();
}
@Override
public OriginatorPrototype clone() throws CloneNotSupportedException {
return (OriginatorPrototype) super.clone();
}
}
//管理者角色==组合原型模式
public class CaretakerPrototype {
private OriginatorPrototype originatorPrototype;
public void setMemento(OriginatorPrototype originatorPrototype){
this.originatorPrototype = originatorPrototype;
}
public OriginatorPrototype getMemento(){
return this.originatorPrototype;
}
}
//测试
@Test
public void remark() throws CloneNotSupportedException {
OriginatorPrototype originatorPrototype = new OriginatorPrototype();
originatorPrototype.setStatus("初始状态");
System.out.println("当前状态===="+originatorPrototype.getStatus());
CaretakerPrototype caretakerPrototype = new CaretakerPrototype();
caretakerPrototype.setMemento(originatorPrototype.clone());
originatorPrototype.setStatus("就绪状态");
System.out.println("当前状态===="+originatorPrototype.getStatus());
originatorPrototype.restoreMemento(caretakerPrototype.getMemento());
System.out.println("当前状态===="+originatorPrototype.getStatus());
}
当前状态====初始状态
当前状态====就绪状态
当前状态====初始状态
- 本文标签: 设计模式
- 本文链接: https://www.tianyajuanke.top/article/62
- 版权声明: 本文由吴沛芙原创发布,转载请遵循《署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)》许可协议授权