zoukankan      html  css  js  c++  java
  • 深入理解设计模式(15):访问者模式

    一、什么是访问者模式

    定义:表示一个作用于其对象结构中的各元素的操作,它使你可以在不改变各元素类的前提下定义作用于这些元素的新操作。

    可以对定义这么理解:有这么一个操作,它是作用于一些元素之上的,而这些元素属于某一个对象结构。同时这个操作是在不改变各元素类的前提下,在这个前提下定义新操作是访问者模式精髓中的精髓。

    主要解决:稳定的数据结构和易变的操作耦合问题。就是把数据结构和作用于结构上的操作解耦合,使得操作集合可相对自由地演化。

    本质:预留通路,回调实现。它的实现主要就是通过预先定义好调用的通路,在被访问的对象上定义accept方法,在访问者的对象上定义visit方法;然后在调用真正发生的时候,通过两次分发的技术,利用预先定义好的通路,回调到访问者具体的实现上。

    二、访问者模式的结构

    Visitor抽象访问者接口:它定义了对每一个元素(Element)访问的行为,它的参数就是可以访问的元素,它的方法个数理论上来讲与元素个数(Element的实现类个数)是一样的,从这点不难看出,访问者模式要求元素类的个数不能改变(不能改变的意思是说,如果元素类的个数经常改变,则说明不适合使用访问者模式)。

    ConcreteVisitor具体访问者角色:它需要给出对每一个元素类访问时所产生的具体行为。

    Element抽象节点(元素)角色:它定义了一个接受访问者(accept)的方法,其意义是指,每一个元素都要可以被访问者访问。

    ConcreteElement具体节点(元素)角色:它提供接受访问方法的具体实现,而这个具体的实现,通常情况下是使用访问者提供的访问该元素类的方法。

    ObjectStructure结构对象角色:这个便是定义当中所提到的对象结构,对象结构是一个抽象表述,具体点可以理解为一个具有容器性质或者复合对象特性的类,它会含有一组元素(Element),并且可以迭代这些元素,供访问者访问。

    三、访问者模式的使用场景

    (1)对象结构比较稳定,但经常需要在此对象结构上定义新的操作。

    (2)需要对一个对象结构中的对象进行很多不同的且不相关的操作,而需要避免这些操作“污染”这些对象的类,也不希望在增加新操作时修改这些类。

    四、访问者模式的优缺点

    优点:
    1. 访问者模式使得易于增加新的操作 访问者使得增加依赖于复杂对象结构的构件的操作变得容易了。仅需增加一个新的访问者即可在一个对象结构上定义一个新的操作。相反, 如果每个功能都分散在多个类之上的话,定义新的操作时必须修改每一类。 

    2. 访问者集中相关的操作而分离无关的操作 相关的行为不是分布在定义该对象结构的 各个类上,而是集中在一个访问者中。无关行为却被分别放在它们各自的访问者子类中。这 就既简化了这些元素的类,也简化了在这些访问者中定义的算法。所有与它的算法相关的数 据结构都可以被隐藏在访问者中。 

     缺点:
    1. 增加新的 ConcreteElement类很困难 

    Visitor模式使得难以增加新的 Element的子类。每 添加一个新的 ConcreteElement都要在 Vistor中添加一个新的抽象操作,并在每一个 ConcretVisitor类中实现相应的操作。有时可以在 Visitor中提供一个缺省的实现,这一实现可 以被大多数的 ConcreteVisitor继承,但这与其说是一个规律还不如说是一种例外。 

    所以在应用访问者模式时考虑关键的问题是系统的哪个部分会经常变化,是作用于对象结构上的算法呢还是构成该结构的各个对象的类。如果老是有新的 ConcretElement类加入进来的话, Vistor类层次将变得难以维护。在这种情况下,直接在构成该结构的类中定义这些操作可能更容易一些。如果 Element类层次是稳定的,而你不断地增加操作获修改算法,访问者模式可以帮助你管理这些改动。 

    2. 破坏封装 
    访问者方法假定ConcreteElement接口的功能足够强,足以让访问者进行它 们的工作。结果是,该模式常常迫使你提供访问元素内部状态的公共操作,这可能会破坏它 的封装性。

    五、访问者模式的实现

    抽象访问者角色:为每一个具体节点都准备了一个访问操作。

    //这里由于有两个节点,因此,对应就有两个访问操作。
    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 static void main(String[] args) {
            //创建一个结构对象
            ObjectStructure os = new ObjectStructure();
            //给结构增加一个节点
            os.add(new NodeA());
            //给结构增加一个节点
            os.add(new NodeB());
            //创建一个访问者
            Visitor visitor = new VisitorA();
            os.action(visitor);
        }
  • 相关阅读:
    Docker运行nginx文件服务器详细配置
    containerd 使用
    【转】Oracle将以特定分隔的字符串转成表格的方法(用于类似游标的遍历)
    我的博客园的定制化配置v20201229
    李叫兽-文案创意模板
    小程序海报最佳实现思路,可视化编辑直接生成代码使用
    X型文案和Y型文案,李叫兽教你如何减少文案中的“自嗨现象”
    【运营手册2020年12月】
    软件研发的原则
    《营销的16个关键词》笔记
  • 原文地址:https://www.cnblogs.com/xuwendong/p/9898021.html
Copyright © 2011-2022 走看看