一、迭代器模式
1.1概述
迭代器模式是一种行为型设计模式,它允许在没有暴露其底层表现形式的情况下遍历集合对象。迭代器模式提供一种通用的遍历机制,可以遍历任何类型的集合,包括数组、列表、树等。通过这种模式,可以实现一种通用的遍历接口,从而减少了代码重复性,提高了代码的可复用性。
1.2结构
迭代器模式主要包含以下角色:
- 抽象聚合(Aggregate)角色:定义存储、添加、删除聚合元素以及创建迭代器对象的接口。
- 具体聚合(ConcreteAggregate)角色:实现抽象聚合类,返回一个具体迭代器的实例。
- 抽象迭代器(Iterator)角色:定义访问和遍历聚合元素的接口,通常包含 hasNext()、next() 等方法。可以包含一个工厂方法用于生成具体迭代器。
- 具体迭代器(Concretelterator)角色:实现抽象迭代器接口中所定义的方法,完成对聚合对象的遍历,记录遍历的当前位置。
1.3实现
【例】定义一个可以存储学生对象的容器对象,将遍历该容器的功能交由迭代器实现,涉及到的类如下:
package com.yanyu.Iterator;
public class Student {
private String name;
private String number;
public Student(String name, String number) {
this.name = name;
this.number = number;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", number='" + number + '\'' +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
}
抽象迭代器(Iterator)角色
package com.yanyu.Iterator;
public interface StudentIterator {
boolean hasNext();
Student next();
}
具体迭代器(Concretelterator)角色
package com.yanyu.Iterator;
import java.util.List;
public class StudentIteratorImpl implements StudentIterator {
private List<Student> list;
private int position = 0;
public StudentIteratorImpl(List<Student> list) {
this.list = list;
}
@Override
public boolean hasNext() {
return position < list.size();
}
@Override
public Student next() {
Student currentStudent = list.get(position);
position ++;
return currentStudent;
}
}
抽象聚合(Aggregate)角色
package com.yanyu.Iterator;
public interface StudentAggregate {
void addStudent(Student student);
void removeStudent(Student student);
StudentIterator getStudentIterator();
}
具体聚合(ConcreteAggregate)角色
package com.yanyu.Iterator;
import java.util.ArrayList;
import java.util.List;
public class StudentAggregateImpl implements StudentAggregate {
private List<Student> list = new ArrayList<Student>(); // 学生列表
@Override
public void addStudent(Student student) {
this.list.add(student);
}
@Override
public void removeStudent(Student student) {
this.list.remove(student);
}
@Override
public StudentIterator getStudentIterator() {
return new StudentIteratorImpl(list);
}
}
客户端类
package com.yanyu.Iterator;
public class Client {
public static void main(String[] args) {
// 创建聚合对象
StudentAggregateImpl aggregate = new StudentAggregateImpl();
// 添加元素
aggregate.addStudent(new Student("张三", "001"));
aggregate.addStudent(new Student("李四", "002"));
aggregate.addStudent(new Student("王五", "003"));
aggregate.addStudent(new Student("赵六", "004"));
// 遍历聚合对象
// 1. 获取迭代器对象
StudentIterator iterator = aggregate.getStudentIterator();
// 2. 遍历
while(iterator.hasNext()) {
// 3. 获取元素
Student student = iterator.next();
System.out.println(student.toString());
}
}
}
1.4优缺点
1,优点:
- 它支持以不同的方式遍历一个聚合对象,在同一个聚合对象上可以定义多种遍历方式。在迭代器模式中只需要用一个不同的迭代器来替换原有迭代器即可改变遍历算法,我们也可以自己定义迭代器的子类以支持新的遍历方式。
- 迭代器简化了聚合类。由于引入了迭代器,在原有的聚合对象中不需要再自行提供数据遍历等方法,这样可以简化聚合类的设计。
- 在迭代器模式中,由于引入了抽象层,增加新的聚合类和迭代器类都很方便,无须修改原有代码,满足 “开闭原则” 的要求。
2,缺点:
增加了类的个数,这在一定程度上增加了系统的复杂性。
1.5应用场景
适应的场景:
当集合背后为复杂的数据结构, 且你希望对客户端隐藏其复杂性时 (出于使用便利性或安全性的考虑), 可以使用迭代器模式。迭代器封装了与复杂数据结构进行交互的细节, 为客户端提供多个访问集合元素的简单方法。 这种方式不仅对客户端来说非常方便, 而且能避免客户端在直接与集合交互时执行错误或有害的操作, 从而起到保护集合的作用。
使用该模式可以减少程序中重复的遍历代码。重要迭代算法的代码往往体积非常庞大。 当这些代码被放置在程序业务逻辑中时, 它会让原始代码的职责模糊不清, 降低其可维护性。 因此, 将遍历代码移到特定的迭代器中可使程序代码更加精炼和简洁。
二、备忘录模式
2.1概述
又叫快照模式,在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。
备忘录模式提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤,当新的状态无效或者存在问题时,可以使用暂时存储起来的备忘录将状态复原,很多软件都提供了撤销(Undo)操作,如 Word、记事本、Photoshop、IDEA等软件在编辑时按 Ctrl+Z 组合键时能撤销当前操作,使文档恢复到之前的状态;还有在 浏览器 中的后退键、数据库事务管理中的回滚操作、玩游戏时的中间结果存档功能、数据库与操作系统的备份操作、棋类游戏中的悔棋功能等都属于这类。
2.2结构
备忘录模式的主要角色如下:
- 发起人(Originator)角色:记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息。
- 备忘录(Memento)角色:负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人。
- 管理者(Caretaker)角色:对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改。
备忘录有两个等效的接口:
- 窄接口:管理者(Caretaker)对象(和其他发起人对象之外的任何对象)看到的是备忘录的窄接口(narror Interface),这个窄接口只允许他把备忘录对象传给其他的对象。
- 宽接口:与管理者看到的窄接口相反,发起人对象可以看到一个宽接口(wide Interface),这个宽接口允许它读取所有的数据,以便根据这些数据恢复这个发起人对象的内部状态。
2.3实现
【例】游戏挑战BOSS
游戏中的某个场景,一游戏角色有生命力、攻击力、防御力等数据,在打Boss前和后一定会不一样的,我们允许玩家如果感觉与Boss决斗的效果不理想可以让游戏恢复到决斗之前的状态。
要实现上述案例,有两种方式:
- “白箱”备忘录模式
- “黑箱”备忘录模式
“白箱”备忘录模式
备忘录角色对任何对象都提供一个接口,即宽接口,备忘录角色的内部所存储的状态就对所有对象公开。类图如下
发起人
package com.yanyu.memory.whitebox;
//游戏角色类
public class GameRole {
private int vit; //生命力
private int atk; //攻击力
private int def; //防御力
//初始化状态
public void initState() {
this.vit = 100; // 初始化生命力为100
this.atk = 100; // 初始化攻击力为100
this.def = 100; // 初始化防御力为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.vit = roleStateMemento.getVit(); // 恢复生命力
this.atk = roleStateMemento.getAtk(); // 恢复攻击力
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; // 设置防御力
}
}
备忘录(Memento)角色
package com.yanyu.memory.whitebox;
//游戏状态存储类(备忘录类)
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 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; // 设置防御力
}
}
管理者(Caretaker)角色
package com.yanyu.memory.whitebox;
//角色状态管理者类
public class RoleStateCaretaker {
private RoleStateMemento roleStateMemento; // 保存角色状态的备忘录对象
// 获取角色状态的备忘录对象
public RoleStateMemento getRoleStateMemento() {
return roleStateMemento; // 返回角色状态的备忘录对象
}
// 设置角色状态的备忘录对象
public void setRoleStateMemento(RoleStateMemento roleStateMemento) {
this.roleStateMemento = roleStateMemento; // 设置角色状态的备忘录对象
}
}
客户端类
package com.yanyu.memory.whitebox;
//测试类
public class Client {
public static void main(String[] args) {
System.out.println("------------大战Boss前------------");
//大战Boss前
GameRole gameRole = new GameRole(); // 创建游戏角色对象
gameRole.initState(); // 初始化角色状态
gameRole.stateDisplay(); // 显示角色状态
//保存进度
RoleStateCaretaker roleStateCaretaker = new RoleStateCaretaker(); // 创建角色状态管理者对象
roleStateCaretaker.setRoleStateMemento(gameRole.saveState()); // 保存当前角色状态到备忘录对象
System.out.println("------------大战Boss后------------");
//大战Boss时,损耗严重
gameRole.fight(); // 模拟角色大战Boss,损耗状态
gameRole.stateDisplay(); // 显示角色损耗后的状态
System.out.println("------------恢复之前状态------------");
//恢复之前状态
gameRole.recoverState(roleStateCaretaker.getRoleStateMemento()); // 恢复之前保存的角色状态
gameRole.stateDisplay(); // 显示恢复后的角色状态
}
}
适合应用场景
- 如果你需要对一个复杂对象结构 (例如对象树) 中的所有元素执行某些操作, 可使用访问者模式。
访问者模式通过在访问者对象中为多个目标类提供相同操作的变体, 让你能在属于不同类的一组对象上执行同一操作。
- 可使用访问者模式来清理辅助行为的业务逻辑。
该模式会将所有非主要的行为抽取到一组访问者类中, 使得程序的主要类能更专注于主要的工作。
- 当某个行为仅在类层次结构中的一些类中有意义, 而在其他类中没有意义时, 可使用该模式。
你可将该行为抽取到单独的访问者类中, 只需实现接收相关类的对象作为参数的访问者方法并将其他方法留空即可。
“黑箱”备忘录模式
备忘录角色对发起人对象提供一个宽接口,而为其他对象提供一个窄接口。在Java语言中,实现双重接口的办法就是将备忘录类设计成发起人类的内部成员类。
将
RoleStateMemento
设为GameRole
的内部类,从而将RoleStateMemento
对象封装在GameRole
里面;在外面提供一个标识接口Memento
给RoleStateCaretaker
及其他对象使用。这样GameRole
类看到的是RoleStateMemento
所有的接口,而RoleStateCaretaker
及其他对象看到的仅仅是标识接口Memento
所暴露出来的接口,从而维护了封装型。类图如下:
窄接口
package com.yanyu.memory.blackbox;
public interface Memento {
}
发起人
package com.yanyu.memory.blackbox;
// 游戏角色类
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 Memento saveState() {
return new RoleStateMemento(vit, atk, def);
}
// 回复角色状态
public void recoverState(Memento memento) {
RoleStateMemento roleStateMemento = (RoleStateMemento) memento;
this.vit = roleStateMemento.getVit();
this.atk = roleStateMemento.getAtk();
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;
}
// 角色状态备忘录类
private class RoleStateMemento implements Memento {
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 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.yanyu.memory.blackbox;
//角色状态管理者类
public class RoleStateCaretaker {
private Memento memento;
public Memento getMemento() {
return memento;
}
public void setMemento(Memento memento) {
this.memento = memento;
}
}
客户端类
package com.yanyu.memory.blackbox;
public class Client {
public static void main(String[] args) {
System.out.println("------------大战Boss前------------");
//大战Boss前
GameRole gameRole = new GameRole();
gameRole.initState();
gameRole.stateDisplay();
//保存进度
RoleStateCaretaker roleStateCaretaker = new RoleStateCaretaker();
roleStateCaretaker.setMemento(gameRole.saveState());
System.out.println("------------大战Boss后------------");
//大战Boss时,损耗严重
gameRole.fight();
gameRole.stateDisplay();
System.out.println("------------恢复之前状态------------");
//恢复之前状态
gameRole.recoverState(roleStateCaretaker.getMemento());
gameRole.stateDisplay();
}
}
三、迭代器模式实验
任务描述
假设需要开发一个社交电商网站,在对该系统进行分析和设计时,开发人员发现经常需要对系统中的热门商品、热门博主等数据等进行遍历,为了复用这些遍历代码,开发人员设计了一个抽象的数据集合类
AbstractObjectList
,AbstractObjectList
类的子类ProductList
用于存储热门商品数据。本关任务:请用迭代器模式实现一个遍历热门商品的功能。熟悉自定义迭代器和语言自带迭代器的用法与区别。
实现方式
声明迭代器接口。 该接口必须提供至少一个方法来获取集合中的下个元素。 但为了使用方便, 你还可以添加一些其他方法, 例如获取前一个元素、 记录当前位置和判断迭代是否已结束;
声明集合接口并描述一个获取迭代器的方法。 其返回值必须是迭代器接口。 如果你计划拥有多组不同的迭代器, 则可以声明多个类似的方法;
为希望使用迭代器进行遍历的集合实现具体迭代器类。 迭代器对象必须与单个集合实体链接。 链接关系通常通过迭代器的构造函数建立;
在你的集合类中实现集合接口。 其主要思想是针对特定集合为客户端代码提供创建迭代器的快捷方式。 集合对象必须将自身传递给迭代器的构造函数来创建两者之间的链接;
检查客户端代码, 使用迭代器替代所有集合遍历代码。 每当客户端需要遍历集合元素时都会获取一个新的迭代器。
编程要求
根据提示,在右侧编辑器 Begin-End 内补充 “
Client.java
”中的代码,其它文件的代码不需要改变。测试说明
输入热门商品数量和商品名称,平台会对你编写的代码进行测试:
测试输入:
4
拖把
橘子
剃须刀
面膜
预期输出:拖把
橘子
剃须刀
面膜
--------------------
面膜
剃须刀
橘子
拖把
抽象迭代器
package step1;
//抽象迭代器
public interface AbstractInterator{
void next(); //移至下一个元素
boolean isLast(); //判断是否为最后一个元素
void previous(); //移至上一个元素
boolean isFirst(); //判断是否为第一个元素
Object getNextItem(); //获取下一个元素
Object getPreviousItem(); //获取上一个元素
}
具体迭代器
package step1;
import java.util.List;
// 抽象迭代器容器
public abstract class AbstractObjectList {
// 定义对象列表
protected List<Object> objects;
// 构造方法,接收一个对象列表
public AbstractObjectList(List objects) {
this.objects = objects;
}
// 添加对象到列表
public void addObject(Object obj) {
this.objects.add(obj);
}
// 从列表中移除对象
public void removeObject(Object obj) {
this.objects.remove(obj);
}
// 获取对象列表
public List getObjects() {
return this.objects;
}
// 声明创建迭代器对象的抽象工厂方法
public abstract AbstractInterator createIterator();
}
抽象聚合(Aggregate)角色
package step1;
import java.util.List;
//具体商品的容器
public class ProductList extends AbstractObjectList{
public ProductList(List products) {
super(products);
}
//实现创建迭代器对象的具体方法
@Override
public AbstractInterator createIterator() {
return new ProductIterator(this);
}
}
具体聚和角色
package step1;
import java.util.List;
// 具体商品的迭代器
public class ProductIterator implements AbstractInterator {
private List products; // 商品列表
private int cursor1; // 定义一个游标,用于记录正向遍历的位置
private int cursor2; // 定义一个游标,用于记录逆向遍历的位置
// 构造方法,接收一个商品列表
public ProductIterator(ProductList list) {
this.products = list.getObjects(); // 获取集合对象
cursor1 = 0; // 设置正向遍历游标的初始值
cursor2 = products.size() - 1; // 设置逆向遍历游标的初始值
}
@Override
public void next() {
if (cursor1 < products.size()) {
cursor1++;
}
}
@Override
public boolean isLast() {
return (cursor1 == products.size());
}
@Override
public void previous() {
if (cursor2 > -1) {
cursor2--;
}
}
@Override
public boolean isFirst() {
return (cursor2 == -1);
}
@Override
public Object getNextItem() {
return products.get(cursor1);
}
@Override
public Object getPreviousItem() {
return products.get(cursor2);
}
}
客户端类
package step1;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Scanner;
public class Client {
public static void main(String[] args) {
List products = new ArrayList<String>();
Scanner scanner = new Scanner(System.in);
int nums = scanner.nextInt();
for (int i = 0; i < nums; i++) {
products.add(scanner.next());
}
/********** Begin *********/
///请用java自带的iterator实现products的正向遍历访问
Iterator<String> iterator = products.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
/********** End *********/
System.out.println("--------------------");
/********** Begin *********/
///请用自定义的iterator实现products的反向遍历访问
AbstractInterator iterator1 = new ProductIterator(new ProductList(products));
while (!iterator1.isFirst()) {
System.out.println(iterator1.getPreviousItem());
iterator1.previous();
}
/********** End *********/
}
}
四、备忘录模式实验
任务描述
在计算机专业的实践教学系统中,学生需要根据教师提供的模板框架来补充、改写程序。学生在编写代码过程中可能会因为思路不清晰而导致框架原有结构被破坏,因此学生希望系统具有恢复成初始状态的功能,这样就能重新开始。
本关任务:请用备忘录模式来实现模板框架的恢复功能。
实现方式
确定担任原发器角色的类。 重要的是明确程序使用的一个原发器中心对象, 还是多个较小的对象;
创建备忘录类。 逐一声明对应每个原发器成员变量的备忘录成员变量;
将备忘录类设为不可变。 备忘录只能通过构造函数一次性接收数据。 该类中不能包含设置器;
如果你所使用的编程语言支持嵌套类, 则可将备忘录嵌套在原发器中; 如果不支持, 那么你可从备忘录类中抽取一个空接口, 然后让其他所有对象通过接口来引用备忘录。 你可在该接口中添加一些元数据操作, 但不能暴露原发器的状态;
在原发器中添加一个创建备忘录的方法。 原发器必须通过备忘录构造函数的一个或多个实际参数来将自身状态传递给备忘录。该方法返回结果的类型必须是你在上一步中抽取的接口 (如果你已经抽取了)。 实际上, 创建备忘录的方法必须直接与备忘录类进行交互;
在原发器类中添加一个用于恢复自身状态的方法。 该方法接受备忘录对象作为参数。 如果你在之前的步骤中抽取了接口, 那么可将接口作为参数的类型。 在这种情况下, 你需要将输入对象强制转换为备忘录, 因为原发器需要拥有对该对象的完全访问权限;
无论负责人是命令对象、 历史记录或其他完全不同的东西, 它都必须要知道何时向原发器请求新的备忘录、 如何存储备忘录以及何时使用特定备忘录来对原发器进行恢复;
负责人与原发器之间的连接可以移动到备忘录类中。 在本例中, 每个备忘录都必须与创建自己的原发器相连接。 恢复方法也可以移动到备忘录类中, 但只有当备忘录类嵌套在原发器中, 或者原发器类提供了足够多的设置器并可对其状态进行重写时, 这种方式才能实现。
编程要求
根据提示,在右侧编辑器补充
Originator.java
文件中 Begin-End 内的代码,其它文件不需要修改。测试说明
测试流程思想:先打印出内置的模板,然后读取你的输入替换模板再打印出来,最后把恢复后的代码再次打印出来:
测试输入:
this is my code
预期输出:
this is ______code
this is my code
this is ______code
窄接口
package step1;
public interface Memento {
}
发起人(Originator)角色
package step1;
public class Originator {
private String mould;
public void ReadMould()
{
//假设从数据库中读取模板代码
this.mould = "this is ______code";
}
///setMould是学生修改以后提交的代码
public void setMould( String code)
{
this.mould= code;
}
public String getMould()
{
return mould;
}
private static class MementoImpl implements Memento
{
/********** Begin *********/
private String mould;
public MementoImpl(String mould) {
this.mould = mould;
}
public String getMould(){
return mould;
}
public void setMemento(String mould) {
this.mould = mould;
}
/********** End *********/
}
public Memento createMemento() {
/********** Begin *********/
return new MementoImpl(mould);
/********** End *********/
}
public void restoreMemento(Memento memento)
{
/********** Begin *********/
MementoImpl roleStateMemento = (MementoImpl) memento;
this.mould = roleStateMemento.getMould();
/********** End *********/
}
}
管理者角色
package step1;
public class Caretaker {
private Memento memento = null;
public void setMemento(Memento memento){
this.memento = memento;
}
public Memento getMemento(){
return this.memento;
}
}
客户端类
package step1;
import java.util.Scanner;
public class Client {
public static void main(String[] args) {
// 创建原始对象和备忘录管理者对象
Originator originator=new Originator();
Caretaker caretaker=new Caretaker();
// 从文件中读取原始代码框架
originator.ReadMould();
// 打印显示原始代码框架
System.out.println(originator.getMould());
// 创建备份
caretaker.setMemento(originator.createMemento());
// 模拟学生提交最终代码
Scanner scanner = new Scanner(System.in);
String mycode = scanner.nextLine();
originator.setMould(mycode);
// 打印显示修改后的代码框架
System.out.println(originator.getMould());
// 恢复备份并显示
originator.restoreMemento(caretaker.getMemento());
System.out.println(originator.getMould());
}
}