《JAVA与模式》之访问者模式

  • 时间:
  • 浏览:0

在阎宏博士的《JAVA与模式》一书中开头是另一1个多 描述访问者(Visitor)模式的:

  访问者模式是对象的行为模式。访问者模式的目的是封装统统施加于你这个数据内部结构元素之上的操作。一旦那先 操作时要修改语句,接受你这个 操作的数据内部结构则还时要保持不变。

  变量被声明时的类型叫做变量的静态类型(Static Type),统统人又把静态类型叫做明显类型(Apparent Type);而变量所引用的对象的真实类型又叫做变量的实际类型(Actual Type)。比如:

List list = null;
list = new ArrayList();

  声明了一1个多 变量list,它的静态类型(也叫明显类型)是List,而它的实际类型是ArrayList。

  根据对象的类型而对最好的最好的办法进行的选者,统统派发(Dispatch),派发(Dispatch)又分为你这个,即静态派发动态派发

  静态派发(Static Dispatch)地处在编译时期,派发根据静态类型信息地处。静态派发对于大伙来说暂且陌生,最好的最好的办法重载统统静态派发。

  动态派发(Dynamic Dispatch)地处在运行时期,动态派发动态地置换掉某个最好的最好的办法。

 静态派发

  Java通过最好的最好的办法重载支持静态派发。用墨子骑马的故事作为例子,墨子还时要骑白马可能性黑马。墨子与白马、黑马和马的类图如下所示:

  在你这个 系统中,墨子由Mozi类代表

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()最好的最好的办法是由一1个多 最好的最好的办法重载而成的。你这个 个多 最好的最好的办法分别接受马(Horse)、白马(WhiteHorse)、黑马(BlackHorse)等类型的参数。

  没有在运行时,程序运行会打印出那先 结果呢?结果是程序运行会打印出相同的两行“骑马”。换言之,墨子发现他所骑的都在马。

  为那先 呢?两次对ride()最好的最好的办法的调用传入的是不同的参数,也统统wh和bh。它们实在 具有不同的真实类型,而且它们的静态类型都在一样的,均是Horse类型。

  重载最好的最好的办法的派发是根据静态类型进行的,你这个 派发过程在编译时期就完成了。

 动态派发

  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编译器在编译时期暂且一直知道那先 代码会被执行,可能性编译器仅仅知道对象的静态类型,而我统统知道对象的真实类型;而最好的最好的办法的调用则是根据对象的真实类型,而都在静态类型。另一1个多 一来,上面最后一行的eat()最好的最好的办法调用的是BlackHorse类的eat()最好的最好的办法,打印的是“黑马吃草”。

 派发的类型

  一1个多 最好的最好的办法所属的对象叫做最好的最好的办法的接收者,最好的最好的办法的接收者与最好的最好的办法的参数统称做最好的最好的办法的宗量。比如下面例子中的Test类

public class Test {

    public void print(String str){
        System.out.println(str);
    }
}

  在上面的类中,print()最好的最好的办法属于Test对象,统统它的接收者也统统Test对象了。print()最好的最好的办法有一1个多 参数是str,它的类型是String。

  根据派发还时要基于十好多个 种宗量,还时要将面向对象的语言划分为单派发语言(Uni-Dispatch)和多派发语言(Multi-Dispatch)。单派发语言根据一1个多 宗量的类型进行对最好的最好的办法的选者,多派发语言根据多于一1个多 的宗量的类型对最好的最好的办法进行选者。

  C++和Java均是单派发语言,多派发语言的例子包括CLOS和Cecil。按照另一1个多 的区分,Java统统动态的单派发语言,可能性你这个 语言的动态派发仅仅会考虑到最好的最好的办法的接收者的类型,共同又是静态的多派发语言,可能性你这个 语言对重载最好的最好的办法的派发会考虑到最好的最好的办法的接收者的类型以及最好的最好的办法的所有参数的类型。

  在一1个多 支持动态单派发的语言上面,有一1个多 条件决定了一1个多 请求会调用哪一1个多 操作:一是请求的名字,统统接收者的真实类型。单派发限制了最好的最好的办法的选者过程,使得上还可不可以 一1个多 宗量还时要被考虑到,你这个 宗量通常统统最好的最好的办法的接收者。在Java语言上面,可能性一1个多 操作是作用于某个类型不明的对象上面,没有对你这个 对象的真实类型测试仅会地处一次,这统统动态的单派发的内部结构。

 双重派发

  一1个多 最好的最好的办法根据一1个多 宗量的类型来决定执行不同的代码,这统统“双重派发”。Java语言不支持动态的多派发,也就原因分析分析Java不支持动态的双派发。而且通过使用设计模式,也还时要在Java语言里实现动态的双重派发。

  在Java中还时要通过两次最好的最好的办法调用来达到两次派发的目的。类图如下所示:

  在图涵盖一1个多 对象,左边的叫做West,右边的叫做East。现在West对象首先调用East对象的goEast()最好的最好的办法,并将它自己传入。在East对象被调用时,立即根据传入的参数知道了调用者是谁,于是反过来调用“调用者”对象的goWest()最好的最好的办法。通过两次调用将程序运行控制权轮番交给一1个多 对象,其时序图如下所示:

  另一1个多 就一直冒出了两次最好的最好的办法调用,程序运行控制权被一1个多 对象像传球一样,首先由West对象传给了East对象,而且又被返传给了West对象。

  而且仅仅返传了一下球,暂且能防止双重派发的什么的问题。关键是如可利用这两次调用,以及Java语言的动态单派发功能,使得在你这个 传球的过程中,还可不可以触发两次单派发。

  动态单派发在Java语言中是在子类重写父类的最好的最好的办法时地处的。换言之,West和East都时要分别置身于自己的类型等级内部结构中,如下图所示:

  源代码

  West类

public abstract class West {
    
    public abstract void goWest1(SubEast1 east);
    
    public abstract void goWest2(SubEast2 east);
}

  SubWest1类

public class SubWest1 extends West{
    
    @Override
    public void goWest1(SubEast1 east) {
        
        System.out.println("SubWest1 + " + east.myName1());
    }
    
    @Override
    public void goWest2(SubEast2 east) {
        
        System.out.println("SubWest1 + " + east.myName2());
    }
}

  SubWest2类

public class SubWest2 extends West{
    @Override
    public void goWest1(SubEast1 east) {
        
        System.out.println("SubWest2 + " + east.myName1());
    }
    
    @Override
    public void goWest2(SubEast2 east) {
        
        System.out.println("SubWest2 + " + east.myName2());
    }
}

  East类

public abstract class East {

    public abstract void goEast(West west);
}

  SubEast1类

public class SubEast1 extends East{
    @Override
    public void goEast(West west) {
        west.goWest1(this);
    }
    
    public String myName1(){
        return "SubEast1";
    }
}

  SubEast2类

public class SubEast2 extends East{
    @Override
    public void goEast(West west) {
        west.goWest2(this);
    }
    
    public String myName2(){
        return "SubEast2";
    }
}

  客户端类

public class Client {

    public static void main(String[] args) {
        //组合1
        East east = new SubEast1();
        West west = new SubWest1();
        east.goEast(west);
        //组合2
        east = new SubEast1();
        west = new SubWest2();
        east.goEast(west);
    }

}

  运行结果如下


SubWest1 + SubEast1

SubWest2 + SubEast1


  系统运行时,会首先创建SubWest1和SubEast1对象,而且客户端调用SubEast1的goEast()最好的最好的办法,并将SubWest1对象传入。可能性SubEast1对象重写了其超类East的goEast()最好的最好的办法,而且,你这个 以前就地处了一次动态的单派发。当SubEast1对象接到调用时,会从参数中得到SubWest1对象,统统它就立即调用你这个 对象的goWest1()最好的最好的办法,并将自己传入。可能性SubEast1对象有权选者调用哪一1个多 对象,而且,在此时又进行一次动态的最好的最好的办法派发。

  你这个 以前SubWest1对象就得到了SubEast1对象。通过调用你这个 对象myName1()最好的最好的办法,就还时要打印出自己的名字和SubEast对象的名字,其时序图如下所示:

  可能性你这个 个多 名字一1个多 来自East等级内部结构,另一1个多 来自West等级内部结构中,而且,它们的组合式是动态决定的。这统统动态双重派发的实现机制。

  访问者模式适用于数据内部结构相对未定的系统,它把数据内部结构和作用于内部结构上的操作之间的耦合解脱开,使得操作集合还时要相对自由地演化。访问者模式的简略图如下所示:

  数据内部结构的每一1个多 节点都还时要接受一1个多 访问者的调用,此节点向访问者对象传入节点对象,而访问者对象则反过来执行节点对象的操作。另一1个多 的过程叫做“双重派发”。节点调用访问者,将它自己传入,访问者则将某算法针对此节点执行。访问者模式的示意性类图如下所示:

  

  访问者模式涉及到的角色如下:

  ●  抽象访问者(Visitor)角色:声明了一1个多 可能性多个最好的最好的办法操作,形成所有的具体访问者角色时要实现的接口。

  ●  具体访问者(ConcreteVisitor)角色:实现抽象访问者所声明的接口,也统统抽象访问者所声明的各个访问操作。

  ●  抽象节点(Node)角色:声明一1个多 接受操作,接受一1个多 访问者对象作为一1个多 参数。

  ●  具体节点(ConcreteNode)角色:实现了抽象节点所规定的接受操作。

  ●  内部结构对象(ObjectStructure)角色:有如下的责任,还时要遍历内部结构中的所有元素;可能性时要,提供一1个多 高层次的接口让访问者对象还时要访问每一1个多 元素;可能性时要,还时要设计成一1个多 复合对象可能性一1个多 聚集,如List或Set。

  源代码

  还时要看了,抽象访问者角色为每一1个多 具体节点都准备了一1个多 访问操作。可能性有一1个多 节点,而且,对应都在一1个多 访问操作。

public interface Visitor {
    /**
     * 对应于NodeA的访问操作
     */
    public void visit(NodeA node);
    /**
     * 对应于NodeB的访问操作
     */
    public void visit(NodeB node);
}

  具体访问者VisitorA类

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());
    }

}

  具体访问者VisitorB类

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());
    }

}

  抽象节点类

public abstract class Node {
    /**
     * 接受操作
     */
    public abstract void accept(Visitor visitor);
}

  具体节点类NodeA

public class NodeA extends Node{
    /**
     * 接受操作
     */
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
    /**
     * NodeA特有的最好的最好的办法
     */
    public String operationA(){
        return "NodeA";
    }

}

  具体节点类NodeB

public class NodeB extends Node{
    /**
     * 接受最好的最好的办法
     */
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
    /**
     * NodeB特有的最好的最好的办法
     */
    public String operationB(){
        return "NodeB";
    }
}

  内部结构对象角色类,你这个 内部结构对象角色持有一1个多 聚集,并向外界提供add()最好的最好的办法作为对聚集的管理操作。通过调用你这个 最好的最好的办法,还时要动态地增加一1个多 新的节点。

public class ObjectStructure {
    
    private List<Node> nodes = new ArrayList<Node>();
    
    /**
     * 执行最好的最好的办法操作
     */
    public void action(Visitor visitor){
        
        for(Node node : nodes)
        {
            node.accept(visitor);
        }
        
    }
    /**
     * 再加一1个多



新元素
     */
    public void add(Node node){
        nodes.add(node);
    }
}

  客户端类

public class Client {

    public static void main(String[] args) {
        //创建一1个多



内部结构对象
        ObjectStructure os = new ObjectStructure();
        //给内部结构增加一1个多



节点
        os.add(new NodeA());
        //给内部结构增加一1个多



节点
        os.add(new NodeB());
        //创建一1个多



访问者
        Visitor visitor = new VisitorA();
        os.action(visitor);
    }

}

  实在 在你这个 示意性的实现里并没算不算缘无故冒出一1个多 错综复杂的具有多个树枝节点的对象树内部结构,而且,在实际系统中访问者模式通常是用来防止错综复杂的对象树内部结构的,而且访问者模式还时要用来防止跨不多个等级内部结构的树内部结构什么的问题。这正是访问者模式的功能强大之处。

  准备过程时序图

  首先,你这个 示意性的客户端创建了一1个多 内部结构对象,而且将一1个多 新的NodeA对象和一1个多 新的NodeB对象传入。

  其次,客户端创建了一1个多 VisitorA对象,并将此对象传给内部结构对象。

  而且,客户端调用内部结构对象聚集管理最好的最好的办法,将NodeA和NodeB节点加入到内部结构对象中去。

  最后,客户端调用内部结构对象的行动最好的最好的办法action(),启动访问过程。

  

  访问过程时序图

  

  内部结构对象会遍历它自己所保存的聚集中的所有节点,在本系统中统统节点NodeA和NodeB。首先NodeA会被访问到,你这个 访问是由以下的操作组成的:

  (1)NodeA对象的接受最好的最好的办法accept()被调用,并将VisitorA对象你这个传入;

  (2)NodeA对象反过来调用VisitorA对象的访问最好的最好的办法,并将NodeA对象你这个传入;

  (3)VisitorA对象调用NodeA对象的特有最好的最好的办法operationA()。

  从而就完成了双重派发过程,接着,NodeB会被访问,你这个 访问的过程和NodeA被访问的过程是一样的,这里不再叙述。

  ●  好的扩展性

  还可不可以在不修改对象内部结构中的元素的情況下,为对象内部结构中的元素再加新的功能。

  ●  好的复用性

  还时要通过访问者来定义整个对象内部结构通用的功能,从而提高复用程度。

  ●  分离无关行为

  还时要通过访问者来分离无关的行为,把相关的行为封装进共同,构成一1个多 访问者,另一1个多 每一1个多 访问者的功能都比较单一。

  ●  对象内部结构变化很困难

  不适用于对象内部结构中的类一直变化的情況,可能性对象内部结构地处了改变,访问者的接口和访问者的实现都在地处相应的改变,代价太高。

  ●  破坏封装

  访问者模式通常时要对象内部结构开放内部内部结构数据给访问者和ObjectStructrue,这破坏了对象的封装性。