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)又分为两种,即静态分派和动态分派。

  1. 静态分派(Static Dispatch)发生在编译时期,分派根据静态类型信息发生。静态分派对于我们来说并不陌生,方法重载就是静态分派。
  2. 动态分派(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,这破坏了对象的封装性。


每天用心记录一点点。内容也许不重要,但习惯很重要!

原文地址:https://www.cnblogs.com/binarylei/p/9021638.html

时间: 2024-11-06 10:48:18

Java 设计模式系列(二三)访问者模式(Vistor)的相关文章

《Java设计模式》之访问者模式

访问者模式是对象的行为模式.访问者模式的目的是封装一些施加于某种数据结构元素之上的操作.一旦这些操作需要修改的话,接受这个操作的数据结构则可以保持不变. 分派的概念 变量被声明时的类型叫做变量的静态类型(Static Type),有些人又把静态类型叫做明显类型(Apparent Type):而变量所引用的对象的真实类型又叫做变量的实际类型(Actual Type).比如 [java] view plaincopyprint? List list = null; list = new ArrayL

Java设计模式系列之策略模式

策略模式的定义: 策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换,策略模式让算法独立于使用它的客户而独立变化. 策略模式使这些算法在客户端调用它们的时候能够互不影响地变化. 策略模式的意义:   策略模式使开发人员能够开发出由许多可替换的部分组成的软件,并且各个部分之间是低耦合的关系. 低耦合的特性使软件具有更强的可扩展性,易于维护:更重要的是,它大大提高了软件的可重用性.    策略模式中有三个对象:      环境对象(Context):该类中实现了对抽象策略中

Java设计模式系列之状态模式

状态模式(State)的定义 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新.允许一个对象在其内部状态改变时改变它的行为.对象看起来似乎修改了它的类 状态模式(State)适用性 1.一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为. 2.一个操作中含有庞大的多分支的条件语句,且这些分支依赖于该对象的状态. 这个状态通常用一个或多个枚举常量表示. 通常,有多个操作包含这一相同的条件结构. State模式将每一个条件分支

Java设计模式系列之迭代器模式

迭代器模式定义 迭代器模式(Iterator),提供一种方法顺序访问一个聚合对象中的各种元素,而又不暴露该对象的内部表示. 迭代器模式的角色构成 (1)迭代器角色(Iterator):定义遍历元素所需要的方法,一般来说会有这么三个方法:取得下一个元素的方法next(),判断是否遍历结束的方法hasNext()),移出当前对象的方法remove(), (2)具体迭代器角色(Concrete Iterator):实现迭代器接口中定义的方法,完成集合的迭代. (3)容器角色(Aggregate): 

Java设计模式系列之桥接模式

桥接模式(Bridge)的定义 在软件系统中,某些类型由于自身的逻辑,它具有两个或多个维度的变化,那么如何应对这种“多维度的变化”?这就要使用桥接模式 将抽象部分与它的实现部分分离,使它们都可以独立地变化. 桥接模式(Bridge)的动机 当一种抽象类型可能有多种实现方式时,一般情况我们可以考虑使用继承来解决抽象类型的多种实现,在抽象类型中定义接口,而子类负责接口的具体实现.但这种做法缺乏灵活性,由于抽象类型和子类之间紧紧地绑定在一起,使得这种关系在运行时不能再修改,这使得它难以修改.扩展和重用

Java设计模式系列之命令模式

命令模式(Command)的定义 将一个请求封装为一个对象,从而可用不同的请求对客户进行参数化:对请求排队或记录日志,以及支持可撤销的操作,将”发出请求的对象”和”接收与执行这些请求的对象”分隔开来. 命令模式(Command)的适用性 1.抽象出待执行的动作以参数化某对象. 2.在不同的时刻指定.排列和执行请求. 3.支持取消操作. 4.支持修改日志,这样当系统崩溃时,这样修改可以被重做一遍. 5.用构建在原语操作上的高层操作构造一个系统. 命令模式(Command)的应用效果:1)comma

Java 设计模式系列(九)组合模式

Java 设计模式系列(九)组合模式 将对象组合成树形结构以表示"部分-整体"的层次结构.组合模式使得用户对单个对象的使用具有一致性. 一.组合模式结构 Component: 抽象的组件对象,为组合中的对象声明接口,让客户端可以通过这个接口来访问和管理整个对象结构,可以在里面为定义的功能提供缺省的实现. Leaf: 叶子节点对象,定义和实现叶子对象的行为,不再包含其它的子节点对象. Composite: 组合对象,通常会存储子组件,定义包含子组件的那些组件的行为,并实现在组件接口中定义

设计模式入门之访问者模式Visitor

Set集合的配置 数据表的创建:表关系一个员工拥有多个身份 create table EMPLOYEE ( id INT NOT NULL auto_increment, first_name VARCHAR(20) default NULL, last_name VARCHAR(20) default NULL, salary INT default NULL, PRIMARY KEY (id) ); create table CERTIFICATE ( id INT NOT NULL aut

java设计模式4--建造者模式(Builder)

本文地址:http://www.cnblogs.com/archimedes/p/java-builder-pattern.html,转载请注明源地址. 建造者模式 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示. 概述 当系统准备为用户提供一个内部结构复杂的对象时,就可以使用生成器模式,使用该模式可以逐步地构造对象,使得对象的创建更具弹性.生成器模式的关键是将一个包含有多个组件对象的创建分成若干个步骤,并将这些步骤封装在一个称作生成器的接口中. 适用性 1.当创建复杂