访问者模式
1、定义与特点
- 定义:将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式。它将对数据的操作与数据结构进行分离,是行为类模式中最复杂的一种模式。
- 优点:
-
- 扩展性好。能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。
- 复用性好。可以通过访问者来定义整个对象结构通用的功能,从而提高系统的复用程度。
- 灵活性好。访问者模式将数据结构与作用于结构上的操作解耦,使得操作集合可相对自由地演化而不影响系统的数据结构。
- 符合单一职责原则。访问者模式把相关的行为封装在一起,构成一个访问者,使每一个访问者的功能都比较单一。
- 缺点:
-
- 增加新的元素类很困难。在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”。
- 破坏封装。访问者模式中具体元素对访问者公布细节,这破坏了对象的封装性。
- 违反了依赖倒置原则。访问者模式依赖了具体类,而没有依赖抽象类。
2、结构与实现
访问者(Visitor)模式实现的关键是如何将作用于元素的操作分离出来封装成独立的类
- 模式的结构:
-
- 抽象访问者(Visitor)角色:定义一个访问具体元素的接口,为每个具体元素类对应一个访问操作 visit() ,该操作中的参数类型标识了被访问的具体元素。
- 具体访问者(ConcreteVisitor)角色:实现抽象访问者角色中声明的各个访问操作,确定访问者访问一个元素时该做什么。
- 抽象元素(Element)角色:声明一个包含接受操作 accept() 的接口,被接受的访问者对象作为 accept() 方法的参数。
- 具体元素(ConcreteElement)角色:实现抽象元素角色提供的 accept() 操作,其方法体通常都是 visitor.visit(this) ,另外具体元素中可能还包含本身业务逻辑的相关操作。
- 对象结构(Object Structure)角色:是一个包含元素角色的容器,提供让访问者对象遍历容器中的所有元素的方法,通常由 List、Set、Map 等聚合类实现。
- 模式的实现:
import java.util.*;
public class VisitorPattern{
public static void main(String[] args){
ObjectStructure os=new ObjectStructure();
os.add(new ConcreteElementA());
os.add(new ConcreteElementB());
Visitor visitor=new ConcreteVisitorA();
os.accept(visitor);
System.out.println("------------------------");
visitor=new ConcreteVisitorB();
os.accept(visitor);
}
}
//抽象访问者
interface Visitor{
void visit(ConcreteElementA element);
void visit(ConcreteElementB element);
}
//具体访问者A类
class ConcreteVisitorA implements Visitor{
public void visit(ConcreteElementA element){
System.out.println("具体访问者A访问-->"+element.operationA());
}
public void visit(ConcreteElementB element){
System.out.println("具体访问者A访问-->"+element.operationB());
}
}
//具体访问者B类
class ConcreteVisitorB implements Visitor{
public void visit(ConcreteElementA element){
System.out.println("具体访问者B访问-->"+element.operationA());
}
public void visit(ConcreteElementB element){
System.out.println("具体访问者B访问-->"+element.operationB());
}
}
//抽象元素类
interface Element{
void accept(Visitor visitor);
}
//具体元素A类
class ConcreteElementA implements Element{
public void accept(Visitor visitor){
visitor.visit(this);
}
public String operationA(){
return "具体元素A的操作。";
}
}
//具体元素B类
class ConcreteElementB implements Element{
public void accept(Visitor visitor){
visitor.visit(this);
}
public String operationB(){
return "具体元素B的操作。";
}
}
//对象结构角色
class ObjectStructure{
private List<Element> list=new ArrayList<Element>();
public void accept(Visitor visitor){
Iterator<Element> i=list.iterator();
while(i.hasNext()){
((Element) i.next()).accept(visitor);
}
}
public void add(Element element){
list.add(element);
}
public void remove(Element element){
list.remove(element);
}
}
3、应用场景
- 对象结构相对稳定,但其操作算法经常变化的程序。
- 对象结构中的对象需要提供多种不同且不相关的操作,而且要避免让这些操作的变化影响对象的结构。
- 对象结构包含很多类型的对象,希望对这些对象实施一些依赖于其具体类型的操作。
4、扩展
- 访问者(Visitor)模式是使用频率较高的一种设计模式,它常常同以下两种设计模式联用:
备忘录模式
1、定义与特点
- 定义:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后当需要时能将该对象恢复到原先保存的状态。该模式又叫快照模式。
- 优点:
-
- 提供了一种可以恢复状态的机制。当用户需要时能够比较方便地将数据恢复到某个历史的状态。
- 实现了内部状态的封装。除了创建它的发起人之外,其他对象都不能够访问这些状态信息。
- 简化了发起人类。发起人不需要管理和保存其内部状态的各个备份,所有状态信息都保存在备忘录中,并由管理者进行管理,这符合单一职责原则。
- 缺点:资源消耗大。如果要保存的内部状态信息过多或者特别频繁,将会占用比较大的内存资源。
2、结构与实现
- 模式的结构:
-
- 发起人(Originator)角色:记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息。
- 备忘录(Memento)角色:负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人。
- 管理者(Caretaker)角色:对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改。
- 模式的实现:
public class MementoPattern{
public static void main(String[] args){
Originator or=new Originator();
Caretaker cr=new Caretaker();
or.setState("S0");
System.out.println("初始状态:"+or.getState());
cr.setMemento(or.createMemento()); //保存状态
or.setState("S1");
System.out.println("新的状态:"+or.getState());
or.restoreMemento(cr.getMemento()); //恢复状态
System.out.println("恢复状态:"+or.getState());
}
}
//备忘录
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 void setState(String state){
this.state=state;
}
public String getState(){
return state;
}
public Memento createMemento(){
return new Memento(state);
}
public void restoreMemento(Memento m){
this.setState(m.getState());
}
}
//管理者
class Caretaker{
private Memento memento;
public void setMemento(Memento m){
memento=m;
}
public Memento getMemento(){
return memento;
}
}
3、应用场景
- 需要保存与恢复数据的场景,如玩游戏时的中间结果的存档功能。
- 需要提供一个可回滚操作的场景,如 Word、记事本、Photoshop,Eclipse 等软件在编辑时按 Ctrl+Z 组合键,还有数据库中事务操作。
4、扩展
- 在前面介绍的备忘录模式中,有单状态备份的例子,也有多状态备份的例子。下面介绍备忘录模式如何同原型模式混合使用。在备忘录模式中,通过定义“备忘录”来备份“发起人”的信息,而原型模式的 clone() 方法具有自备份功能,所以,如果让发起人实现 Cloneable 接口就有备份自己的功能,这时可以删除备忘录类,其结构图如图 4 所示。
public class PrototypeMemento{
public static void main(String[] args){
OriginatorPrototype or=new OriginatorPrototype();
PrototypeCaretaker cr=new PrototypeCaretaker();
or.setState("S0");
System.out.println("初始状态:"+or.getState());
cr.setMemento(or.createMemento()); //保存状态
or.setState("S1");
System.out.println("新的状态:"+or.getState());
or.restoreMemento(cr.getMemento()); //恢复状态
System.out.println("恢复状态:"+or.getState());
}
}
//发起人原型
class OriginatorPrototype implements Cloneable{
private String state;
public void setState(String state){
this.state=state;
}
public String getState(){
return state;
}
public OriginatorPrototype createMemento(){
return this.clone();
}
public void restoreMemento(OriginatorPrototype opt){
this.setState(opt.getState());
}
public OriginatorPrototype clone(){
try{
return (OriginatorPrototype) super.clone();
}catch(CloneNotSupportedException e){
e.printStackTrace();
}
return null;
}
}
//原型管理者
class PrototypeCaretaker
{
private OriginatorPrototype opt;
public void setMemento(OriginatorPrototype opt){
this.opt=opt;
}
public OriginatorPrototype getMemento(){
return opt;
}
}
解释器模式
1、定义与特点
- 定义:给分析对象定义一个语言,并定义该语言的文法表示,再设计一个解析器来解释语言中的句子。也就是说,用编译语言的方式来分析应用中的实例。这种模式实现了文法表达式处理的接口,该接口解释一个特定的上下文。
- 优点:
-
- 扩展性好。由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法。
- 容易实现。在语法树中的每个表达式节点类都是相似的,所以实现其文法较为容易。
- 缺点:
-
- 执行效率较低。解释器模式中通常使用大量的循环和递归调用,当要解释的句子较复杂时,其运行速度很慢,且代码的调试过程也比较麻烦。
- 会引起类膨胀。解释器模式中的每条规则至少需要定义一个类,当包含的文法规则很多时,类的个数将急剧增加,导致系统难以管理与维护。
- 可应用的场景比较少。在软件开发中,需要定义语言文法的应用实例非常少,所以这种模式很少被使用到
2、结构与实现
- 模式的结构:
-
- 抽象表达式(Abstract Expression)角色:定义解释器的接口,约定解释器的解释操作,主要包含解释方法 interpret()。
- 终结符表达式(Terminal Expression)角色:是抽象表达式的子类,用来实现文法中与终结符相关的操作,文法中的每一个终结符都有一个具体终结表达式与之相对应。
- 非终结符表达式(Nonterminal Expression)角色:也是抽象表达式的子类,用来实现文法中与非终结符相关的操作,文法中的每条规则都对应于一个非终结符表达式。
- 环境(Context)角色:通常包含各个解释器需要的数据或是公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值。
- 客户端(Client):主要任务是将需要分析的句子或表达式转换成使用解释器对象描述的抽象语法树,然后调用解释器的解释方法,当然也可以通过环境角色间接访问解释器的解释方法。
- 模式的实现:
//抽象表达式类
interface AbstractExpression{
public Object interpret(String info); //解释方法
}
//终结符表达式类
class TerminalExpression implements AbstractExpression{
public Object interpret(String info){
//对终结符表达式的处理
}
}
//非终结符表达式类
class NonterminalExpression implements AbstractExpression{
private AbstractExpression exp1;
private AbstractExpression exp2;
public Object interpret(String info){
//非对终结符表达式的处理
}
}
//环境类
class Context{
private AbstractExpression exp;
public Context(){
//数据初始化
}
public void operation(String info){
//调用相关表达式类的解释方法
}
}
3、应用场景
- 当语言的文法较为简单,且执行效率不是关键问题时。
- 当问题重复出现,且可以用一种简单的语言来进行表达时。
- 当一个语言需要解释执行,并且语言中的句子可以表示为一个抽象语法树的时候,如 XML 文档解释。
更多设计模式的分享,请查看https://blog.csdn.net/pp_l_ly/category_9961646.html