Java 设计模式系列(二三)访问者模式(Vistor)
Java 设计模式系列(二三)访问者模式(Vistor)
访问者模式是对象的行为模式。访问者模式的目的是封装一些施加于某种数据结构元素之上的操作。一旦这些操作需要修改的话,接受这个操作的数据结构则可以保持不变。
一、访问者模式结构
访问者模式适用于数据结构相对未定的系统,它把数据结构和作用于结构上的操作之间的耦合解脱开,使得操作集合可以相对自由地演化。
数据结构的每一个节点都可以接受一个访问者的调用,此节点向访问者对象传入节点对象,而访问者对象则反过来执行节点对象的操作。这样的过程叫做“双重分派”。节点调用访问者,将它自己传入,访问者则将某算法针对此节点执行。访问者模式的示意性类图如下所示:

访问者模式涉及到的角色如下:
抽象访问者(Visitor)角色:声明了一个或者多个方法操作,形成所有的具体访问者角色必须实现的接口。
具体访问者(ConcreteVisitor)角色:实现抽象访问者所声明的接口,也就是抽象访问者所声明的各个访问操作。
抽象节点(Node)角色:声明一个接受操作,接受一个访问者对象作为一个参数。
具体节点(ConcreteNode)角色:实现了抽象节点所规定的接受操作。
结构对象(ObjectStructure)角色:有如下的责任,可以遍历结构中的所有元素;如果需要,提供一个高层次的接口让访问者对象可以访问每一个元素;如果需要,可以设计成一个复合对象或者一个聚集,如 List 或 Set。
源代码
(1) Vistor
可以看到,抽象访问者角色为每一个具体节点都准备了一个访问操作。由于有两个节点,因此,对应就有两个访问操作。
public interface Visitor {
/** 对应于NodeA的访问操作 */
public void visit(NodeA node);
/** 对应于NodeB的访问操作 */
public void visit(NodeB node);
}
public class VisitorA implements Visitor {
/** 对应于NodeA的访问操作 */
@Override
public void visit(NodeA node) {
System.out.println(node.operationA());
}
/** 对应于NodeB的访问操作 */
@Override
public void visit(NodeB node) {
System.out.println(node.operationB());
}
}
public class VisitorB implements Visitor {
/** 对应于NodeA的访问操作 */
@Override
public void visit(NodeA node) {
System.out.println(node.operationA());
}
/** 对应于NodeB的访问操作 */
@Override
public void visit(NodeB node) {
System.out.println(node.operationB());
}
}
(2) Node
public abstract class Node {
/** 接受操作 */
public abstract void accept(Visitor visitor);
}
public class NodeA extends Node{
/** 接受操作 */
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
/** NodeA特有的方法 */
public String operationA(){
return "NodeA";
}
}
public class NodeB extends Node{
/** 接受方法 */
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
/** NodeB特有的方法 */
public String operationB(){
return "NodeB";
}
}
(3) ObjectStructure
public class ObjectStructure {
private List<Node> nodes = new ArrayList<Node>();
/** 执行方法操作 */
public void action(Visitor visitor) {
for(Node node : nodes) {
node.accept(visitor);
}
}
/** 添加一个新元素 */
public void add(Node node){
nodes.add(node);
}
}
(4) 测试
public class Client {
public static void main(String[] args) {
//创建一个结构对象
ObjectStructure os = new ObjectStructure();
//给结构增加一个节点
os.add(new NodeA());
//给结构增加一个节点
os.add(new NodeB());
//创建一个访问者
Visitor visitor = new VisitorA();
os.action(visitor);
}
}
二、分派的概念
变量被声明时的类型叫做变量的静态类型(Static Type),有些人又把静态类型叫做明显类型(Apparent Type);而变量所引用的对象的真实类型又叫做变量的实际类型(Actual Type)。比如:
List list = null;
list = new ArrayList();
声明了一个变量 list,它的静态类型(也叫明显类型)是 List,而它的实际类型是 ArrayList。
根据对象的类型而对方法进行的选择,就是分派(Dispatch),分派(Dispatch)又分为两种,即静态分派和动态分派。
静态分派(Static Dispatch)发生在编译时期,分派根据静态类型信息发生。静态分派对于我们来说并不陌生,方法重载就是静态分派。
动态分派(Dynamic Dispatch)发生在运行时期,动态分派动态地置换掉某个方法。
(1) 静态分派
Java 通过方法重载支持静态分派。
public class Mozi {
public void ride(Horse h){
System.out.println("骑马");
}
public void ride(WhiteHorse wh){
System.out.println("骑白马");
}
public void ride(BlackHorse bh){
System.out.println("骑黑马");
}
public static void main(String[] args) {
Horse wh = new WhiteHorse();
Horse bh = new BlackHorse();
Mozi mozi = new Mozi();
mozi.ride(wh);
mozi.ride(bh);
}
}
显然,Mozi 类的 ride() 方法是由三个方法重载而成的。这三个方法分别接受马(Horse)、白马(WhiteHorse)、黑马(BlackHorse)等类型的参数。
那么在运行时,程序会打印出什么结果呢?结果是程序会打印出相同的两行“骑马”。换言之,墨子发现他所骑的都是马。
为什么呢?两次对 ride() 方法的调用传入的是不同的参数,也就是 wh 和 bh。它们虽然具有不同的真实类型,但是它们的静态类型都是一样的,均是 Horse 类型。
重载方法的分派是根据静态类型进行的,这个分派过程在编译时期就完成了。
(2) 动态分派
Java 通过方法的重写支持动态分派。
public class Horse {
public void eat(){
System.out.println("马吃草");
}
}
public class BlackHorse extends Horse {
@Override
public void eat() {
System.out.println("黑马吃草");
}
}
public class Client {
public static void main(String[] args) {
Horse h = new BlackHorse();
h.eat();
}
}
变量 h 的静态类型是 Horse,而真实类型是 BlackHorse。如果上面最后一行的 eat() 方法调用的是 BlackHorse 类的 eat() 方法,那么上面打印的就是“黑马吃草”;相反,如果上面的 eat() 方法调用的是 Horse 类的 eat() 方法,那么打印的就是“马吃草”。
所以,问题的核心就是 Java 编译器在编译时期并不总是知道哪些代码会被执行,因为编译器仅仅知道对象的静态类型,而不知道对象的真实类型;而方法的调用则是根据对象的真实类型,而不是静态类型。这样一来,上面最后一行的 eat() 方法调用的是 BlackHorse 类的 eat() 方法,打印的是“黑马吃草”。
(3) 分派的类型
一个方法所属的对象叫做方法的接收者,方法的接收者与方法的参数统称做方法的宗量。比如下面例子中的Test类
public class Test {
public void print(String str){
System.out.println(str);
}
}
在上面的类中,print() 方法属于 Test 对象,所以它的接收者也就是 Test 对象了。print() 方法有一个参数是 str,它的类型是 String。
根据分派可以基于多少种宗量,可以将面向对象的语言划分为单分派语言(Uni-Dispatch)和多分派语言(Multi-Dispatch)。单分派语言根据一个宗量的类型进行对方法的选择,多分派语言根据多于一个的宗量的类型对方法进行选择。
C++ 和 Java 均是单分派语言,多分派语言的例子包括 CLOS 和 Cecil。按照这样的区分,Java 就是动态的单分派语言,因为这种语言的动态分派仅仅会考虑到方法的接收者的类型,同时又是静态的多分派语言,因为这种语言对重载方法的分派会考虑到方法的接收者的类型以及方法的所有参数的类型。
在一个支持动态单分派的语言里面,有两个条件决定了一个请求会调用哪一个操作:一是请求的名字,而是接收者的真实类型。单分派限制了方法的选择过程,使得只有一个宗量可以被考虑到,这个宗量通常就是方法的接收者。在Java语言里面,如果一个操作是作用于某个类型不明的对象上面,那么对这个对象的真实类型测试仅会发生一次,这就是动态的单分派的特征。
(4) 双重分派
一个方法根据两个宗量的类型来决定执行不同的代码,这就是“双重分派”。Java 语言不支持动态的多分派,也就意味着 Java 不支持动态的双分派。但是通过使用设计模式,也可以在 Java 语言里实现动态的双重分派。
在 Java 中可以通过两次方法调用来达到两次分派的目的。
三、总结
(1) 访问者模式的优缺点
访问者模式的优点
好的扩展性:能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。
好的复用性:可以通过访问者来定义整个对象结构通用的功能,从而提高复用程度。
分离无关行为:可以通过访问者来分离无关的行为,把相关的行为封装在一起,构成一个访问者,这样每一个访问者的功能都比较单一。
访问者模式的缺点
对象结构变化很困难:不适用于对象结构中的类经常变化的情况,因为对象结构发生了改变,访问者的接口和访问者的实现都要发生相应的改变,代价太高。
破坏封装:访问者模式通常需要对象结构开放内部数据给访问者和 ObjectStructrue,这破坏了对象的封装性。
每天用心记录一点点。内容也许不重要,但习惯很重要!
Java 设计模式系列(二三)访问者模式(Vistor)的更多相关文章
- 重学 Java 设计模式:实战访问者模式「模拟家长与校长,对学生和老师的不同视角信息的访问场景」
作者:小傅哥 博客:https://bugstack.cn - 原创系列专题文章 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 能力,是你前行的最大保障 年龄会不断的增长,但是什么才能让你不 ...
- Java设计模式系列-抽象工厂模式
原创文章,转载请标注出处:https://www.cnblogs.com/V1haoge/p/10755412.html 一.概述 抽象工厂模式是对工厂方法模式的再升级,但是二者面对的场景稍显差别. ...
- Java设计模式系列-工厂方法模式
原创文章,转载请标注出处:<Java设计模式系列-工厂方法模式> 一.概述 工厂,就是生产产品的地方. 在Java设计模式中使用工厂的概念,那就是生成对象的地方了. 本来直接就能创建的对象 ...
- Java设计模式系列-装饰器模式
原创文章,转载请标注出处:<Java设计模式系列-装饰器模式> 一.概述 装饰器模式作用是针对目标方法进行增强,提供新的功能或者额外的功能. 不同于适配器模式和桥接模式,装饰器模式涉及的是 ...
- Java设计模式之十 ---- 访问者模式和中介者模式
前言 2018年已经过去,新的一年工作已经开始,继续总结和学习Java设计模式. 在上一篇中我们学习了行为型模式的解释器模式(Interpreter Pattern)和迭代器模式(Iterator P ...
- [设计模式-行为型]访问者模式(Vistor)
一句话 表示一个作用于某对象结构中的各元素的操作.它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作. 概括
- 《JAVA设计模式》之访问者模式(Visitor)
在阎宏博士的<JAVA与模式>一书中开头是这样描述访问者(Visitor)模式的: 访问者模式是对象的行为模式.访问者模式的目的是封装一些施加于某种数据结构元素之上的操作.一旦这些操作需要 ...
- C#设计模式系列:访问者模式(Visitor)
1.访问者模式简介 1.1>.定义 作用于某个对象群中各个对象的操作,可以使在不改变对象本身的情况下,定义作用于对象的新操作. 1.2>.使用频率 低 2.访问者模式结构 2.1> ...
- java设计模式-----24、访问者模式
概念: Visitor模式也叫访问者模式,是行为模式之一,它分离对象的数据和行为,使用Visitor模式,可以不修改已有类的情况下,增加新的操作. 访问者模式的应用示例 比如有一个公园,有一到多个不同 ...
- Java设计模式系列之工厂模式
工厂模式将大量有共同接口的类实例化,工厂模式可以实现动态决定实例化哪一个类的对象,工厂模式在<Java与模式>中分为三类:1)简单工厂模式(Simple Factory):添加某一种类型的 ...
随机推荐
- [UOJ388]【UNR #3】配对树
uoj description 给你一棵\(n\)个节点的树以及一个长为\(m\)的序列,序列每个位置上的值\(\in[1,n]\),你需要求出把序列中所有长度为偶数的区间内所有数拿出来在树上以最小代 ...
- linux下tengine安装
1.什么是tengine? 说到tengine,首先还是得说下nginx了,大家对于nginx并不陌生,对于基本的需求都能满足,如果是涉及高级性能,那么就必须使用商用版nginx plus了,一谈到商 ...
- 开始SDK之旅-入门1基本环境搭建与测试
已验证这个可用. http://bbs.ccflow.org/showtopic-2560.aspx 集成方式已经用一段时间了,今天刚好有时间,尝试下SDK.使用的话,也很方便,以下是简单的步骤1.新 ...
- Ionic 项目创建
1. Ionic介绍 Ionci 是一个强大的HTML5 应用程序框架. 可以帮助您使用Web技术,如HTML, CSS和Javascript构建原生体验的移动应用程序. Ionic主要关注外观和体验 ...
- 玩转Panabit 2008 Live CD到U盘,Panabit随身行
直接将Panabit 2008 Live CD的内容复制到U盘,即把Live CD转成U盘启动盘,U盘可写,方便保存配置.要能正常使用,必须机器支持USB启动,才能用上:但是熟悉此方法,同样可以灵活用 ...
- [C++ Primer] 第6章: 函数
参数传递 const形参和实参: 顶层const作用于对象本身, 和其他初始化过程一样, 当用实参初始化形参时会忽略掉顶层const, 换句话说, 形参顶层const被忽略掉了, 当形参有顶层cons ...
- .NET实现WebSocket服务端即时通信实例
即时通信常用手段 1.第三方平台 谷歌.腾讯 环信等多如牛毛,其中谷歌即时通信是免费的,但免费就是免费的并不好用.其他的一些第三方一般收费的,使用要则限流(1s/限制x条消息)要么则限制用户数. 但稳 ...
- 手淘flexible.js框架使用和源代码讲解
手淘框架是一个用来适配移动端的js框架,下面我们来讲解一下如何使用手淘的这套框架. 其实手淘框架的核心原理就是根据不同的width给网页中html跟节点设置不同的font-size,然后所有的距离大小 ...
- Log4j(1)--hellloworld
创建项目: 使用的是log4j-1.2.17.jar: log4j.properties: log4j.rootLogger=DEBUG, Console ,File #Console log4j.a ...
- bin sh git@github.com no such file or directory
window下使用git: 输入命令:git clone git @github.com:Alan0521/dotvim.git 出现/bin/sh:git@github.com no such fi ...