行为型模式下<迭代器模式、访问者模式、备忘录模式>
1、迭代器模式(Iterator)
在现实生活以及程序设计中,经常要访问一个聚合对象中的各个元素,如“数据结构”中的链表遍历,通常的做法是将链表的创建和遍历都放在同一个类中,但这种方式不利于程序的扩展,如果要更换遍历方法就必须修改程序源代码,这违背了“开闭原则”
既然将遍历方法去封装在聚合类中不可取,那么聚合类中不提供遍历方法,遍历方法由用户自己实现是否可行呢?答案是同样不可取,因为这种方式会存在两个缺点①暴露了聚合类的内部表示,使其数据不安全;②增加了客户的负担。
迭代器模式能较好地克服以上缺点,它在客户访问类与聚合类之间插入一个迭代器,这分离了聚合对象与其遍历行为,对客户也隐藏了其内部细节,且满足“单一职责原则”和“开闭原则”,如Java中的Collection、List、Set、Map等都包含了迭代器。
1.1、迭代器模式的特点和定义
迭代器模式指的是提供一个对象来顺序访问聚合对象中一系列数据,而不暴露聚合对象的内部表示。迭代器模式是一种对象行为型模式,简化聚合类,支持以不同的方式遍历一个聚合,为不同的聚合结构提供一个统一的接口,封装性良好。
1.2、模式的结构
迭代器模式主要定义了以下角色:
(1)抽象聚合角色(Aggregate):定义存储、添加、删除聚合对象以及创造迭代器对象的接口;
(2)具体聚合角色(Concrete Aggregate):实现抽象聚合类,返回一个具体迭代器的实例。
(3)抽象迭代器(Iterator)角色:定义访问和遍历聚合元素的接口,
(4)具体迭代器(Concrete Iterator)角色:实现抽象迭代器接口中定义的方法,完成聚合对象的遍历,记录当前的位置。
结构图如下:
实现的代码如下:

//抽象聚合 interface Aggregate { public void add(Object object); public void remove(Object object); public IteratorPattern getiterator(); } //具体聚合类 class ConcreteAggregate implements Aggregate { private List<Object> lists = new ArrayList<>(); @Override public void add(Object object) { lists.add(object); } @Override public void remove(Object object) { lists.remove(object); } @Override public IteratorPattern getiterator() { return (new ConcreteIteratorPattern(lists)); } } //抽象迭代器 interface IteratorPattern { Object first(); Object next(); boolean hasNext(); } //具体的迭代器 class ConcreteIteratorPattern implements IteratorPattern { private List<Object> list = null; private int index = 0; public ConcreteIteratorPattern(List<Object> list){ this.list = list; } @Override public Object first() { return list.get(0); } @Override public Object next() { Object obj = null; if(this.hasNext()) { obj = list.get(index++); } return obj; } @Override public boolean hasNext() { if(index >= list.size()){ return false; } else { return true; } } } public class TestIteratorPattern { public static void main(String[] args) { Aggregate aggregate = new ConcreteAggregate(); aggregate.add("北京"); aggregate.add("天津"); aggregate.add("上海"); aggregate.add("重庆"); System.out.println("中国的直辖市有:"); IteratorPattern iteratorPattern = aggregate.getiterator(); while(iteratorPattern.hasNext()) { Object object = iteratorPattern.next(); System.out.print(object.toString() + " "); } } }
1.3、模式的应场景
迭代器模式主要应用在以下几种场景中:
(1)当需要为聚合对象提供多种遍历方式时;
(2)当需要为遍历不同的聚合结构提供统一的接口时;
(3)当访问一个聚合对象的内容而无需暴露其内部细节的表示时。
1.4、迭代器模式的拓展
迭代器模式通常和组合模式结合使用,在对组合模式的容器构件进行访问的时候,经常将迭代器潜藏在组合模式的容器构成类中。当然,也可以构造一个外部迭代器来对容器构件进行访问,结构图如下:
2、访问者模式(Visitor)
现实生活中,有些集合的对象中存在多种不同的元素,且每种元素也存在多种不同的访问者和处理方式。例如公园存在许多景点,也有游客,不同游客针对同一经典的评价也不同。经典的表述:"一千个人有一千个哈姆雷特"。
这些被处理的数据元素相对稳定而访问方式多种多样的数据结构,如果采用“访问者模式”来处理比较方便,访问者模式能把处理方法从数据结构中分离出来,并可以根据需要增加新的处理方法,且不用修改原来的程序代码与数据结构,这提高了程序的扩展性和灵活性。
2.1 模式的定义和特点:
访问Visitor者模式将作用于某种数据结构中的各元素的操作分离出来封装成独立的类。使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式。它将数据的操作与数据的结构进行分离。是行为类模型中最复杂的一种模式。扩展性,复用性好,符合单一职责原则。但是增加新的元素类很困难,破坏封装、违反依赖倒置原则。
2.2、 模式的结构与实现
Visitor者模式的结构中主要包含以下角色:
(1)抽象访问者角色(Visitor):定义一个访问元素的接口,为每个具体元素类对应一个访问操作visit(),该操作中的参数类型表示了被访问的具体元素。
(2)具体访问者角色(Concrete Visitor):确定访问者访问一个元素时该做什么
(3)抽象元素角色(Element):声明一个包含接受操作accept()的接口,被接受的访问者对象作为accept方法的参数
(4)具体元素(Concrete Element)角色:实现抽象元素角色提供的accept()操作,其方法体通常都是visitor.visit(this)另外具体元素中可能还包括本身业务逻辑的相关操作
(5)对象结构(Object Structure)角色:是一个包含元素角色的容器,提供让访问者对象遍历容器中的所有元素的方法,通常由List、set、Map等聚合类实现
其结构类图如下:
实例代码如下:

//抽象观察者 interface Visitor{ public void visit (ConcreteElementA concreteElementA); public void visit (ConcreteElementB concreteElementB); } //具体访问者A类 class ConcreteVisitorA implements Visitor{ @Override public void visit(ConcreteElementA concreteElementA) { System.out.println("具体访问者A访问-->" + concreteElementA.operationA()); } @Override public void visit(ConcreteElementB concreteElementB) { System.out.println("具体访问者B访问-->" + concreteElementB.operationB()); } } //具体访问者B类 class ConcreteVisitoB implements Visitor{ @Override public void visit(ConcreteElementA concreteElementA) { System.out.println("具体访问者B访问-->" + concreteElementA.operationA()); } @Override public void visit(ConcreteElementB concreteElementB) { System.out.println("具体访问者B访问-->" + concreteElementB.operationB()); } } //抽象元素类 interface Element{ public void accept(Visitor visitor); } //具体元素A类 class ConcreteElementA implements Element { @Override public void accept(Visitor visitor) { visitor.visit(this); } public String operationA(){ return "具体元素A的操作"; } } //具体元素B类 class ConcreteElementB implements Element { @Override public void accept(Visitor visitor) { visitor.visit(this); } public String operationB(){ return "具体元素B的操作"; } } //对象结构角色 class ObjectStructure { private List<Element> list = new ArrayList<>(); public void accept(Visitor visitor) { Iterator<Element> iterator = list.iterator(); while (iterator.hasNext()) { iterator.next().accept(visitor); } } public void add (Element element) { list.add(element); } public void remove(Element element) { list.remove(element); } } public class TestVisitorPattern { public static void main(String[] args) { ObjectStructure objectStructure = new ObjectStructure(); objectStructure.add(new ConcreteElementA()); objectStructure.add(new ConcreteElementB()); Visitor visitor = new ConcreteVisitorA(); objectStructure.accept(visitor); System.out.println("------------------------"); visitor = new ConcreteVisitoB(); objectStructure.accept(visitor); } }
2.3、 模式的应用实例
利用Visitor模式设计一个员工的信息管理系统来统计员工的工作时长(含加班和缺勤)。员工包括正式员工和临时工,每周人力和财务部两个部门需要对员工数据进行汇总,汇总数据包括员工工作时间、员工工资等。该公司基本制度如下:
(1) 正式员工每周工作时间为40小时,不同级别、不同部门的员工每周基本工资不同;如果超过40小时,超出部分按照100元/小时作为加班费;如果少于40小时,所缺时间按照请假处理,请假所扣工资以80元/小时计算,直到基本工资扣除到零为止。除了记录实际工作时间外,人力需记录加班时长或请假时长,作为员工平时表现的一项依据。
(2) 临时工每周工作时间不固定,基本工资按小时计算,不同岗位的临时工小时工资不同。人力资源部只需记录实际工作时间。
人力资源部和财务部工作人员可以根据各自的需要对员工数据进行汇总处理,人力资源部负责汇总每周员工工作时间,而财务部负责计算每周员工工资。
现使用访问者模式设计该员工信息管理子系统。
这个案例中,人力和财务两个部门就是具体的访问者,将他们抽象出一个接口来,作为抽象访问者(Department),那么具体的访问者就是人力和财务,分别是HRDepartment和FinanceDepartment,接受访问的就是正式员工(FullTimeWorker)和临时员工(PartimeWorker),将他们抽象出一个接口(比如就叫做Employee)来,作为员工类作为抽象元素(Element)角色:声明一个包含接受操作 accept() 的接口,被接受的访问者对象作为 accept() 方法的参数。其中临时工和正式员工分别实现Employee这个类,作为具体的元素角色。那么对象结构角色就可以是员工类列表,用一个List去管理这些员工。
实现代码如下:
//员工类 抽象元素类 //抽象元素用的是接口类 具体元素去实现这个接口 interface Employee{ public void accept(Department department); } //具体元素1:兼职员工 class Parttimeworker implements Employee { private String name;//名字 private double hourWage;//小时工资 private int workTime;//工作时间 public String getName() { return name; } public void setName(String name) { this.name = name; } public double getHourWage() { return hourWage; } public void setHourWage(double hourWage) { this.hourWage = hourWage; } public int getWorkTime() { return workTime; } public void setWorkTime(int workTime) { this.workTime = workTime; } public Parttimeworker(String name, double hourWage, int workTime) { this.name = name; this.hourWage = hourWage; this.workTime = workTime; } @Override public void accept(Department department) { department.visit(this); } } //具体元素2:正式员工。 class FulltimeEmployee implements Employee{ //姓名 private String name; //周工资总额 private double weeklyWage; //工作时间 private int workTime; public FulltimeEmployee(String name, double weeklyWage, int workTime) { super(); this.name = name; this.weeklyWage = weeklyWage; this.workTime = workTime; } public String getName() { return name; } public void setName(String name) { this.name = name; } public double getWeeklyWage() { return weeklyWage; } public void setWeeklyWage(double weeklyWage) { this.weeklyWage = weeklyWage; } public int getWorkTime() { return workTime; } public void setWorkTime(int workTime) { this.workTime = workTime; } @Override public void accept(Department handler) { handler.visit(this); } } //作为抽象访问者(Department) abstract class Department { public abstract void visit(Employee employee);//注意此处只是抽象出一个方法出来 } //具体访问者1:HR class HRDepartment extends Department { //实现人力对全部职工的访问 @Override public void visit(Employee employee) { if(employee instanceof FulltimeEmployee){ int workTime = ((FulltimeEmployee) employee).getWorkTime(); System.out.println("正式员工" + ((FulltimeEmployee) employee).getName() + "实际工作时间:" + workTime + "h"); if(workTime > 40) { System.out.println("正式员工" + ((FulltimeEmployee) employee).getName() + "加班时间:" + (workTime-40) + "h"); } else { System.out.println("正式员工" + ((FulltimeEmployee) employee).getName() + "请假:" + (40-workTime) + "h"); } }else{ //实现对临时工的访问 int workTime = ((Parttimeworker) employee).getWorkTime(); System.out.println("临时工:"+((Parttimeworker)employee).getName()+"实际工作时间为:"+workTime+"小时"); } } } //具体访问者2:财务部门 class FinanceDepartment extends Department { @Override public void visit(Employee employee) { if(employee instanceof FulltimeEmployee) { int workTime = ((FulltimeEmployee) employee).getWorkTime(); //获取一周的工资 double weekWage = ((FulltimeEmployee) employee).getWeeklyWage(); if(workTime > 40) { weekWage += (workTime - 40) * 100; }else { weekWage -= (40 - workTime) * 80; } if(weekWage < 0) { weekWage = 0; } System.out.println("正式员工" + ((FulltimeEmployee) employee).getName() + "工资" + weekWage); } else { //针对临时工的工资处理 int workTime = ((Parttimeworker) employee).getWorkTime(); double hourWage = ((Parttimeworker) employee).getHourWage(); System.out.println("临时工员工" + ((Parttimeworker) employee).getName() + "工资是:" + hourWage + "元"); } } } //对象结构角色:员工列表类,提供一个高层的接口,允许访问者访问元素Element的子类。 class EmployeeList{ private List<Employee> employeeList = new ArrayList<>(); //增加员工 public void addEmployee(Employee employee) { employeeList.add(employee); } //遍历员工集合中的每一个员工对象 public void accept(Department department) { for(Employee employee : employeeList) { employee.accept(department); } } } public class TestVisitorPatternDesign { public static void main(String[] args) { //创建员工的实例化 EmployeeList employeeList = new EmployeeList(); //创建2个全职员工,一个临时工对象; Employee employee1, employee2, employee3; employee1 = new FulltimeEmployee("9527",500,80); employee2 = new FulltimeEmployee("华安",2000,40); employee3 = new Parttimeworker("唐伯虎",50000,10); //将员工放入集合 employeeList.addEmployee(employee1); employeeList.addEmployee(employee2); employeeList.addEmployee(employee3); //人力对员工列表进行访问 System.out.println("下面是HR人力的信息:"); Department department1 = new HRDepartment(); employeeList.accept(department1); //财务对员工列表进行访问 System.out.println("----------------------------------------------"); System.out.println("下面是财务部门的结果信息:"); Department department2 = new FinanceDepartment(); employeeList.accept(department2); } }
上面的代码写的还是不太好,其实还可以将具体元素1和2中的共同特性抽出来。
2.4、 访问者模式的应用场景
(1)对象结构相对稳定,但是其操作算法经常变化的程序
(2)对象结构中的对象需要提供多种不同且不相关的操作,而且要避免让这些操作的变化影响对象的结构。
(3)对象结构包含很多类型的对象,希望对这些对象实施一些依赖于其具体类型的操作
2.5、 访问者模式的扩展
访问者模式是使用频率非常高的一种设计模式,它常常和以下的两种设计模式联用。
(1)与迭代器模式联用,因为访问者模式中的对象结构是一个包含元素角色的容器,当访问者遍历容器中所有元素的时候,常常要用到迭代器。如果对象结构中聚合类没有提供迭代器,可以使用迭代器模式自定义一个。
(2)访问者模式同组合模式联用,因为访问者模式中的元素对象可能是叶子结点或者是容器对象,如果元素对象包含容器对象,就必须用到组合模式。
3、备忘录模式(Memento)
每个人都会给犯错误,都喜欢事后能弥补回来。重新开始。计算机应用中,同样也会犯错误,那这时候是否有后悔药呢?这个时候,备忘录模式来了。
其实很多时候很多软件都提供了该功能,比如记事本、Word、等软件在编辑的时候都提供了Ctrl+Z组合键,使文档恢复到之前的状态,还有数据库中的回滚操作,浏览器中的后退键,玩游戏时候的存档功能、数据库和操作系统的备份操作、棋牌类游戏中悔棋。
备忘录模式了记录一个对象的内部状态,当用户后悔时候能撤销当前的操作,使数据恢复到它原先的状态。
3.1 模式的定义和特点:
备忘录模式的定义:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后当需要的时候能够将对象恢复到原先保存的状态。该模式又叫做快照模式。(想想redis中的内存快照)
备忘录模式是一种对象行为型模式,提供一种可以恢复的机制,实现了内部状态的封装,简化发起人。但是资源消耗大,当保存咋内部状态信息过多或者比较频繁的情况下,占用比较大的内存。
3.2 模式的结构和实现
备忘录模式的核心就是设计备忘录类以及管理备忘录的管理类,主要的结构角色如下:
(1)发起人角色(Originator):记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务的功能,他可以访问备忘录里的所有信息
(2)备忘录(Memento)角色:负责储存发起人的内部状态,在需要的时候提供这些内部状态给发起人
(3)管理者(Caretaker)角色:对备忘录进行管理。提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改
备忘录模式的类结构图如下:
备忘录模式的实现代码如下:
//备忘录类 class Memento { private String state; public Memento(String state) { this.state = state; } public void setState(String state) { this.state = state; } public String getState() { return state; } } //发起人 class Originator { private String state; public String getState() { return state; } public void setState(String state) { this.state = state; } public Memento createMento() { return new Memento(state); } public void restoreMemento(Memento memento) { this.setState(memento.getState()); } } //管理者 class Caretaker { private Memento memento; public Memento getMemento() { return memento; } public void setMemento(Memento memento) { this.memento = memento; } } public class TestMementoPattern { public static void main(String[] args) { Originator originator = new Originator(); Caretaker caretaker = new Caretaker(); originator.setState("test"); System.out.println("初始状态是"+ originator.getState()); caretaker.setMemento(originator.createMento());//设置备忘录,保存状态 originator.setState("test again"); System.out.println("新的状态是" + originator.getState()); originator.restoreMemento(caretaker.getMemento());//恢复 保存的状态 System.out.println("恢复后的状态:"+ originator.getState()); } }
3.3 模式的应用实例
利用备忘录模式设计一个类来实现文本编辑器。 它任何时候都存在已经输入的数据,并且可以使用回退功能恢复之前的存储(写作)状态。我们将实现相同功能并且提供一个任何时候都把输入、存在内容到文件中的工具集,此外,我们也能恢复上一个存储(写作)的状态。
备忘录模式通过两个对象实现:Originator以及Caretaker。
Originator类代表了其状态能够被存储并被用于恢复之前的状态,可以使用使用内部类保存对象的状态。此内部类就被叫做备忘录,注意此类是私有的,它不能被其他对象访问。
Caretaker是一个帮助类,它的职责就是通过备忘录帮助Originator存储当前状态或者恢复重建其之前的状态。因为备忘录是Originator的私有,Caretaker不能访问它,因此它作为一个对象被存储在caretaker中。
实现代码如下:
//Originator类 发起人类 class FileWriterUtil { private String fileName; private StringBuilder content; public FileWriterUtil(String file){ this.fileName=file; this.content=new StringBuilder(); } @Override public String toString(){ return this.content.toString(); } public void write(String str) { content.append(str); } public Memento save() { return new Memento(this.fileName,this.content); } public void undoToLastSave(Object obj) { Memento memento = (Memento) obj; this.fileName= memento.fileName; this.content=memento.content; } private class Memento{ private String fileName; private StringBuilder content; public Memento(String file, StringBuilder content){ this.fileName=file; //深拷贝Memento and FileWriterUtil不会指向相同的对象。 this.content=new StringBuilder(content); } } } //Caretaker类 管理人角色 class FileWriterCaretaker { private Object obj; public void save(FileWriterUtil fileWriter){ this.obj=fileWriter.save(); } public void undo(FileWriterUtil fileWriter){ fileWriter.undoToLastSave(obj); } } public class TestMementoPatternDesign { public static void main(String[] args) { FileWriterCaretaker caretaker = new FileWriterCaretaker(); FileWriterUtil fileWriter = new FileWriterUtil("data.txt"); fileWriter.write("First Set of Data\n"); System.out.println(fileWriter+"\n"); caretaker.save(fileWriter); //now write something else fileWriter.write("Second Set of Data\n"); //checking file contents System.out.println(fileWriter+"\n"); //lets undo to last save caretaker.undo(fileWriter); //checking file content again System.out.println(fileWriter+"\n\n"); } }
3.4 模式的应用场景
(1)需要保存与恢复场景的数据,比如游戏中的中间结果的存档功能
(2)需要提供一个可供回滚的场景,比例软件中Ctrl+z组合键以及数据库的实务操作。
3.5 备忘录模式的拓展
上述的备忘录模式中,有单例备份的例子,也有许多状态备份的例子,下面介绍备忘录模式如何同原型模式进行混合,在备忘录模式中,通过定义备忘录来备份发起人的信息,而原型模式的clone()方法具有自我备份功能,所以如果让发起人实现Cloneable接口就有备份自己的功能,这时候就可以删除备忘录类,类结构图如下:
实现代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | //发起人原型 class OriginatorPrototype implements Cloneable{ private String state; public String getState() { return state; } public void setState(String state) { this .state = state; } public OriginatorPrototype createMemento() { return this .clone(); } public void restoreMemento(OriginatorPrototype memento) { this .setState(memento.getState()); } public OriginatorPrototype clone() { try { return (OriginatorPrototype) super .clone(); } catch (Exception e) { e.printStackTrace(); } return null ; } } //原型管理者 class PrototypeCaretaker { private OriginatorPrototype originatorPrototype; public void setMemento(OriginatorPrototype originatorPrototype) { this .originatorPrototype = originatorPrototype; } public OriginatorPrototype getMemento(){ return originatorPrototype; } } public class PrototypeMemento { public static void main(String[] args) { OriginatorPrototype originatorPrototype1 = new OriginatorPrototype(); PrototypeCaretaker prototypeCaretaker = new PrototypeCaretaker(); originatorPrototype1.setState( "test测试状态" ); System.out.println( "初始状态:" + originatorPrototype1.getState()); prototypeCaretaker.setMemento(originatorPrototype1.createMemento()); originatorPrototype1.setState( "test测试状态new" ); System.out.println( "新的状态:" + originatorPrototype1.getState()); originatorPrototype1.restoreMemento(prototypeCaretaker.getMemento()); System.out.println( "恢复状态:" + originatorPrototype1.getState()); } } |
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 字符编码:从基础到乱码解决
· 提示词工程——AI应用必不可少的技术