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

  • 时间:
  • 浏览:0
  • 来源:大发时时彩_时时彩破解_大发时时彩破解

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

  访问者模式是对象的行为模式。访问者模式的目的是封装但会 施加于本身数据形态学 元素之上的操作。一旦哪些操作这么 修改一句话,接受你但会 操作的数据形态学 则还这么 保持不变。

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

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

  声明了有有另一一俩个 变量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()方式是由有有另一一俩个 方式重载而成的。这有有另一一俩个 方式分别接受马(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编译器在编译时期不言而喻时不时知道哪些代码会被执行,但会 编译器仅仅知道对象的静态类型,而我不知道对象的真实类型;而方式的调用则是根据对象的真实类型,而这么 静态类型。原先一来,里面最后一行的eat()方式调用的是BlackHorse类的eat()方式,打印的是“黑马吃草”。

 分类分类整理的类型

  有有另一一俩个 方式所属的对象叫做方式的接收者,方式的接收者与方式的参数统称做方式的宗量。比如下面例子中的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语言里面,但会 有有另一一俩个 操作是作用于某个类型不明的对象里面,这么 对你但会 对象的真实类型测试仅会趋于稳定一次,这全都动态的单分类分类整理的形态学 。

 双重分类分类整理

  有有另一一俩个 方式根据有有另一一俩个 宗量的类型来决定执行不同的代码,这全都“双重分类分类整理”。Java语言不支持动态的多分类分类整理,也就意味着Java不支持动态的双分类分类整理。但会 通过使用设计模式,也还这么 在Java语言里实现动态的双重分类分类整理。

  在Java中还这么 通过两次方式调用来达到两次分类分类整理的目的。类图如下所示:

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

  原先就时不时出现了两次方式调用,守护程序运行运行控制权被有有另一一俩个 对象像传球一样,首先由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对象有权选者调用哪有有另一一俩个 对象,但会 ,在此时又进行一次动态的方式分类分类整理。

  你但会 要是SubWest1对象就得到了SubEast1对象。通过调用你但会 对象myName1()方式,就还这么 打印出自己的名字和SubEast对象的名字,其时序图如下所示:

  但会 这有有另一一俩个 名字有有另一一俩个 来自East等级形态学 ,原先来自West等级形态学 中,但会 ,它们的组合式是动态决定的。这全都动态双重分类分类整理的实现机制。

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

  数据形态学 的每有有另一一俩个 节点都还这么 接受有有另一一俩个 访问者的调用,此节点向访问者对象传入节点对象,而访问者对象则反过来执行节点对象的操作。原先的过程叫做“双重分类分类整理”。节点调用访问者,将它自己传入,访问者则将某算法针对此节点执行。访问者模式的示意性类图如下所示:

  

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

  ●  抽象访问者(Visitor)角色:声明了有有另一一俩个 但会 多个方式操作,形成所有的具体访问者角色这么 实现的接口。

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

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

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

  ●  形态学 对象(ObjectStructure)角色:有如下的责任,还这么 遍历形态学 中的所有元素;但会 这么 ,提供有有另一一俩个 高层次的接口让访问者对象还这么 访问每有有另一一俩个 元素;但会 这么 ,还这么 设计成有有另一一俩个 复合对象但会 有有另一一俩个 聚集,如List或Set。

  源代码

  还这么 看多,抽象访问者角色为每有有另一一俩个 具体节点都准备了有有另一一俩个 访问操作。但会 有有有另一一俩个 节点,但会 ,对应这么 有有另一一俩个 访问操作。

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";
    }
}

  形态学 对象角色类,你但会 形态学 对象角色持有有另一一俩个 多聚集,并向外界提供add()方式作为对聚集的管理操作。通过调用你但会 方式,还这么 动态地增加有有另一一俩个 新的节点。

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

  客户端类

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

}

  实在在你但会 示意性的实现里并这么 时不时出现有有另一一俩个 简化的具有多个树枝节点的对象树形态学 ,但会 ,在实际系统中访问者模式通常是用来防止简化的对象树形态学 的,但会 访问者模式还这么 用来防止跨越多个等级形态学 的树形态学 难题报告 。这正是访问者模式的功能强大之处。

  准备过程时序图

  首先,你但会 示意性的客户端创建了有有另一一俩个 形态学 对象,但会 将有有另一一俩个 新的NodeA对象和有有另一一俩个 新的NodeB对象传入。

  其次,客户端创建了有有另一一俩个 VisitorA对象,并将此对象传给形态学 对象。

  但会 ,客户端调用形态学 对象聚集管理方式,将NodeA和NodeB节点加入到形态学 对象中去。

  最后,客户端调用形态学 对象的行动方式action(),启动访问过程。

  

  访问过程时序图

  

  形态学 对象会遍历它自己所保存的聚集中的所有节点,在本系统中全都节点NodeA和NodeB。首先NodeA会被访问到,你但会 访问是由以下的操作组成的:

  (1)NodeA对象的接受方式accept()被调用,并将VisitorA对象本身传入;

  (2)NodeA对象反过来调用VisitorA对象的访问方式,并将NodeA对象本身传入;

  (3)VisitorA对象调用NodeA对象的特有方式operationA()。

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

  ●  好的扩展性

  要能在不修改对象形态学 中的元素的情況下,为对象形态学 中的元素打上去新的功能。

  ●  好的复用性

  还这么 通过访问者来定义整个对象形态学 通用的功能,从而提高复用程度。

  ●  分离无关行为

  还这么 通过访问者来分离无关的行为,把相关的行为封装进去 同时,构成有有另一一俩个 访问者,原先每有有另一一俩个 访问者的功能都比较单一。

  ●  对象形态学 变化很困难

  不适用于对象形态学 中的类时不时变化的情況,但会 对象形态学 趋于稳定了改变,访问者的接口和访问者的实现这么 趋于稳定相应的改变,代价太高。

  ●  破坏封装

  访问者模式通常这么 对象形态学 开放内部管理数据给访问者和ObjectStructrue,这破坏了对象的封装性。