设计模式之访问者模式

Posted 二木成林

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计模式之访问者模式相关的知识,希望对你有一定的参考价值。

概述

在现实生活中,有些集合对象存在多种不同的元素,且每种元素也存在多种不同的访问者和处理方式。例如,公园中存在多个景点,也存在多个游客,不同的游客对同一个景点的评价可能不同;医院医生开的处方单中包含多种药元素,査看它的划价员和药房工作人员对它的处理方式也不同,划价员根据处方单上面的药品名和数量进行划价,药房工作人员根据处方单的内容进行抓药。

这样的例子还有很多,例如,电影或电视剧中的人物角色,不同的观众对他们的评价也不同;还有顾客在商场购物时放在“购物车”中的商品,顾客主要关心所选商品的性价比,而收银员关心的是商品的价格和数量。

这些被处理的数据元素相对稳定而访问方式多种多样的数据结构,如果用“访问者模式”来处理比较方便。访问者模式能把处理方法从数据结构中分离出来,并可以根据需要增加新的处理方法,且不用修改原来的程序代码与数据结构,这提高了程序的扩展性和灵活性。

访问者(Visitor)模式的定义:将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式。它将对数据的操作与数据结构进行分离,是行为类模式中最复杂的一种模式。

结构

访问者模式包含以下主要角色:

  1. 抽象访问者(Visitor)角色:定义一个访问具体元素的接口,为每个具体元素类对应一个访问操作 visit() ,该操作中的参数类型标识了被访问的具体元素。
  2. 具体访问者(ConcreteVisitor)角色:实现抽象访问者角色中声明的各个访问操作,确定访问者访问一个元素时该做什么。
  3. 抽象元素(Element)角色:声明一个包含接受操作 accept() 的接口,被接受的访问者对象作为 accept() 方法的参数。
  4. 具体元素(ConcreteElement)角色:实现抽象元素角色提供的 accept() 操作,其方法体通常都是 visitor.visit(this) ,另外具体元素中可能还包含本身业务逻辑的相关操作。
  5. 对象结构(Object Structure)角色:是一个包含元素角色的容器,提供让访问者对象遍历容器中的所有元素的方法,通常由 List、Set、Map 等聚合类实现。

其UML类图如下:

各实现类代码如下:

  • Visitor.java
/**
 * @author lcl100
 * @create 2021-07-17 22:00
 * @desc 抽象访问者接口
 */
public interface Visitor {
    /**
     * 访问ConcreteElementA元素的方法
     *
     * @param element ConcreteElementA元素
     */
    public void visit(ConcreteElementA element);

    /**
     * 访问ConcreteElementB元素的方法
     *
     * @param element ConcreteElementB元素
     */
    public void visit(ConcreteElementB element);
}
  • ConcreteVisitorA.java
/**
 * @author lcl100
 * @create 2021-07-17 22:03
 * @desc 具体访问者A
 */
public class ConcreteVisitorA implements Visitor{
    @Override
    public void visit(ConcreteElementA element) {
        // 具体访问者A访问ConcreteElementA元素会做的操作
        System.out.println("具体访问者A访问-->"+element.operationA());
    }

    @Override
    public void visit(ConcreteElementB element) {
        // 具体访问者A访问ConcreteElementB元素会做的操作
        System.out.println("具体访问者A访问-->"+element.operationB());
    }
}
  • ConcreteVisitorB.java
/**
 * @author lcl100
 * @create 2021-07-17 22:06
 * @desc 具体访问者B
 */
public class ConcreteVisitorB implements Visitor {
    @Override
    public void visit(ConcreteElementA element) {
        // 具体访问者B访问ConcreteElementA元素会做的操作
        System.out.println("具体访问者B访问-->"+element.operationA());
    }

    @Override
    public void visit(ConcreteElementB element) {
        // 具体访问者B访问ConcreteElementB元素会做的操作
        System.out.println("具体访问者B访问-->"+element.operationB());
    }
}
  • Element.java
/**
 * @author lcl100
 * @create 2021-07-17 22:09
 * @desc 抽象元素接口
 */
public interface Element {
    /**
     * 能够接受访问者对象的方法
     *
     * @param visitor 访问者对象
     */
    public void accept(Visitor visitor);
}
  • ConcreteElementA.java
/**
 * @author lcl100
 * @create 2021-07-17 22:11
 * @desc 具体元素A
 */
public class ConcreteElementA implements Element {

    @Override
    public void accept(Visitor visitor) {
        // 让访问者对象来访问当前的具体元素A
        visitor.visit(this);
    }

    /**
     * 具体元素A会执行的操作
     * @return
     */
    public String operationA() {
        return "具体元素A的操作...";
    }
}
  • ConcreteElementB.java
/**
 * @author lcl100
 * @create 2021-07-17 22:14
 * @desc 具体元素B
 */
public class ConcreteElementB implements Element{
    @Override
    public void accept(Visitor visitor) {
        // 让访问者对象来访问当前的具体元素B
        visitor.visit(this);
    }

    /**
     * 具体元素B会执行的操作
     * @return
     */
    public String operationB() {
        return "具体元素B的操作...";
    }
}
  • ObjectStructure.java
/**
 * @author lcl100
 * @create 2021-07-17 22:17
 * @desc 对象结构
 */
public class ObjectStructure {
    /**
     * 用来存储元素的集合
     */
    private List<Element> list = new ArrayList<>();

    /**
     * 让指定的访问者对象访问list集合中的所有元素
     *
     * @param visitor 指定的访问者对象
     */
    public void accept(Visitor visitor) {
        // 获取List集合的迭代器
        Iterator<Element> iterator = list.iterator();
        // 循环遍历集合,让访问者对象来访问集合中的所有元素,不仅仅是一个元素
        while (iterator.hasNext()) {
            iterator.next().accept(visitor);
        }
    }

    /**
     * 向集合中添加元素
     *
     * @param element 待添加的元素
     */
    public void add(Element element) {
        list.add(element);
    }

    /**
     * 删除集合中的指定元素
     *
     * @param element 待删除的元素
     */
    public void remove(Element element) {
        list.remove(element);
    }
}
  • Test.java
/**
 * @author lcl100
 * @create 2021-07-17 22:24
 * @desc 测试类
 */
public class Test {
    public static void main(String[] args) {
        // 创建对象结构类的对象
        ObjectStructure os=new ObjectStructure();
        os.add(new ConcreteElementA());// 向集合中添加具体元素对象
        os.add(new ConcreteElementB());// 向集合中添加具体元素对象

        // 创建具体访问者A,让该访问者来访问对象结构中的所有元素
        Visitor visitorA=new ConcreteVisitorA();
        os.accept(visitorA);

        System.out.println("==============================");

        // 创建具体访问者B,让该访问者来访问对象结构中的所有元素
        Visitor visitorB=new ConcreteVisitorB();
        os.accept(visitorB);
    }
}

案例实现

例:艺术公司利用“铜”可以设计出铜像,利用“纸”可以画出图画;造币公司利用“铜”可以印出铜币,利用“纸”可以印出纸币。对“铜”和“纸”这两种元素,两个公司的处理方法不同,所以该实例用访问者模式来实现比较适合。

首先,定义一个公司(Company)接口,它是抽象访问者,提供了两个根据纸(Paper)或铜(Cuprum)这两种元素创建作品的方法;再定义艺术公司(ArtCompany)类和造币公司(Mint)类,它们是具体访问者,实现了父接口的方法。

然后,定义一个材料(Material)接口,它是抽象元素,提供了 accept(Company visitor)方法来接受访问者(Company)对象访问;再定义纸(Paper)类和铜(Cuprum)类,它们是具体元素类,实现了父接口中的方法。

最后,定义一个材料集(SetMaterial)类,它是对象结构角色,拥有保存所有元素的容器 List,并提供让访问者对象遍历容器中的所有元素的 accept(Company visitor)方法。

其UML类图如下:

各实现代码如下:

  • Company.java
/**
 * @author lcl100
 * @create 2021-07-17 23:15
 * @desc 抽象访问者接口,公司
 */
public interface Company {
    /**
     * 操作Page元素
     * @param element
     * @return
     */
    String create(Paper element);

    /**
     * 操作Cuprum元素
     * @param element
     * @return
     */
    String create(Cuprum element);
}
  • ArtCompany.java
/**
 * @author lcl100
 * @create 2021-07-17 23:27
 * @desc 具体访问者,艺术公司
 */
public class ArtCompany implements Company{
    @Override
    public String create(Paper element) {
        // 艺术公司利用Paper元素操作
        return "打印广告";
    }

    @Override
    public String create(Cuprum element) {
        // 艺术公司利用Cuprum元素制造铜像
        return "孔子铜像";
    }
}
  • MintCompany.java
/**
 * @author lcl100
 * @create 2021-07-17 23:30
 * @desc 具体访问者,造币公司
 */
public class MintCompany implements Company {
    @Override
    public String create(Paper element) {
        // 造币公司利用Paper元素造纸币
        return "纸币";
    }

    @Override
    public String create(Cuprum element) {
        // 造币公司利用Cuprum元素造铜币
        return "铜币";
    }
}
  • Material.java
/**
 * @author lcl100
 * @create 2021-07-17 23:32
 * @desc 抽象元素接口,材料类
 */
public interface Material {
    /**
     * 给指定访问者提供访问当前元素(就是this)的方法
     *
     * @param visitor 指定的访问者
     * @return 访问当前元素返回的结果
     */
    String accept(Company visitor);
}
  • Paper.java
/**
 * @author lcl100
 * @create 2021-07-17 23:
 * @desc 具体元素类,纸
 */
public class Paper implements Material{
    @Override
    public String accept(Company visitor) {
        // 让指定访问者visitor访问当前Paper元素
        return visitor.create(this);
    }
}
  • Cuprum.java
/**
 * @author lcl100
 * @create 2021-07-17 23:35
 * @desc 具体元素,铜
 */
public class Cuprum implements Material {
    @Override
    public String accept(Company visitor) {
        // 让指定访问者visitor访问当前Cuprum元素
        return visitor.create(this);
    }
}
  • MaterialSet.java
/**
 * @author lcl100
 * @create 2021-07-17 23:36
 * @desc 对象结构类,材料集
 */
public class MaterialSet {
    /**
     * 存储材料元素的集合
     */
    private List<Material> list = new ArrayList<>();

    /**
     * 让指定访问者访问list集合中的所有元素
     *
     * @param visitor 指定的访问者
     * @return 批量访问的结果
     */
    public String accept(Company visitor) {
        // 获取集合的迭代器
        Iterator<Material> iterator = list.iterator();
        // 遍历集合,让集合中的所有材料元素都被当前访问者所访问
        String result = "";
        while (iterator.hasNext()) {
            result += iterator.next().accept(visitor) + " ";
        }
        // 返回某公司的作品集
        return result;
    }

    /**
     * 添加元素到材料集合中
     *
     * @param element 待添加的元素
     */
    public void add(Material element) {
        list.add(element);
    }

    /**
     * 删除集合中的指定元素
     *
     * @param element 待删除的元素
     */
    public void remove(Material element) {
        list.remove(element);
    }
}
  • Test.java
/**
 * @author lcl100
 * @create 2021-07-17 23:42
 * @desc 测试类
 */
public class Test {
    public static void main(String[] args) {
        // 创建材料元素集合
        MaterialSet ms = new MaterialSet();
        ms.add(new Paper());// 向集合中添加元素
        ms.add(new Cuprum());// 向集合中添加元素

        // 创建具体的访问者,让该访问者来访问对象结构中的所有元素
        Company artCompany = new ArtCompany();
        System.out.println(ms.accept(artCompany));

        System.out.println("==========================");

        // 创建具体的访问者,让该访问者来访问对象结构中的所有元素
        Company mintCompany = new MintCompany();
        System.out.println(ms.accept(mintCompany));
    }
}

优缺点

访问者(Visitor)模式是一种对象行为型模式,其主要优点如下:

  1. 扩展性好。能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。
  2. 复用性好。可以通过访问者来定义整个对象结构通用的功能,从而提高系统的复用程度。
  3. 灵活性好。访问者模式将数据结构与作用于结构上的操作解耦,使得操作集合可相对自由地演化而不影响系统的数据结构。
  4. 符合单一职责原则。访问者模式把相关的行为封装在一起,构成一个访问者,使每一个访问者的功能都比较单一。

访问者(Visitor)模式的主要缺点如下:

  1. 增加新的元素类很困难。在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”。
  2. 破坏封装。访问者模式中具体元素对访问者公布细节,这破坏了对象的封装性。
  3. 违反了依赖倒置原则。访问者模式依赖了具体类,而没有依赖抽象类。

适用场景:

当系统中存在类型数量稳定(固定)的一类数据结构时,可以使用访问者模式方便地实现对该类型所有数据结构的不同操作,而又不会对数据产生任何副作用(脏数据)。简而言之,就是当对集合中的不同类型数据(类型数量稳定)进行多种操作时,使用访问者模式。通常在以下情况可以考虑使用访问者(Visitor)模式。

  1. 对象结构相对稳定,但其操作算法经常变化的程序。
  2. 对象结构中的对象需要提供多种不同且不相关的操作,而且要避免让这些操作的变化影响对象的结构。
  3. 对象结构包含很多类型的对象,希望对这些对象实施一些依赖于其具体类型的操作。

以上是关于设计模式之访问者模式的主要内容,如果未能解决你的问题,请参考以下文章

设计模式之观察者模式与访问者模式详解和应用

PHP面向对象之访问者模式+组合模式

设计模式之单例模式

设计模式之访问者模式

JAVA SCRIPT设计模式--行为型--设计模式之Vistor访问者(23)

JAVA SCRIPT设计模式--行为型--设计模式之Vistor访问者(23)