详解设计模式:访问者模式

2022-12-07 13:42:12 浏览数 (1)

访问者模式(Visitor Pattern),是在 GoF 23 种设计模式中定义了的行为型模式。据《大话设计模式》中说算是最复杂也是最难以理解的一种模式了。 访问者模式 是一种将数据操作与数据结构分离的设计模式,它可以算是 23 中设计模式中最复杂的一个,但它的使用频率并不是很高,大多数情况下,你并不需要使用访问者模式,但是当你一旦需要使用它时,那你就是需要使用它了。 ~ 本篇文章内容包括:关于访问者模式、访问者模式 Demo


文章目录
  • 一、关于访问者模式
    • 1、关于访问者模式
    • 2、关于访问者模式的构成
    • 3、关于访问者模式的UML
    • 4、关于访问者模式的适用场景
    • 5、关于访问者模式的优缺点

  • 二、访问者模式 Demo
    • 1、Demo 设计
    • 2、Demo 实现
    • 3、Demo 测试

一、关于访问者模式

1、关于访问者模式

访问者模式(Visitor Pattern),是在 GoF 23 种设计模式中定义了的行为型模式。据《大话设计模式》中说算是最复杂也是最难以理解的一种模式了。

访问者模式 是一种将数据操作与数据结构分离的设计模式,它可以算是 23 中设计模式中最复杂的一个,但它的使用频率并不是很高,大多数情况下,你并不需要使用访问者模式,但是当你一旦需要使用它时,那你就是需要使用它了。

访问者模式 的基本想法是,软件系统中拥有一个由许多对象构成的、比较稳定的对象结构,这些对象的类都拥有一个 accept 方法用来接受访问者对象的访问。访问者是一个接口,它拥有一个 visit 方法,这个方法对访问到的对象结构中不同类型的元素做出不同的处理。在对象结构的一次访问过程中,我们遍历整个对象结构,对每一个元素都实施 accept 方法,在每一个元素的 accept 方法中会调用访问者的 visit 方法,从而使访问者得以处理对象结构的每一个元素,我们可以针对对象结构设计不同的访问者类来完成不同的操作,达到区别对待的效果。

访问者模式 适用于数据结构相对稳定算法又易变化的系统。因为访问者模式使得算法操作增加变得容易。若系统数据结构对象易于变化,经常有新的数据对象增加进来,则不适合使用访问者模式

2、关于访问者模式的构成

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

  • 抽象访问者(Visitor)角色:定义了对每一个元素(Element)访问的行为,它的参数就是可以访问的元素,它的方法个数理论上来讲与元素类个数(Element的实现类个数)是一样的,从这点不难看出,访问者模式要求元素类的个数不能改变。
  • 具体访问者(ConcreteVisitor)角色: 具体访问者实现了每个由抽象访问者声明的操作,每一个操作用于访问对象结构中一种类型的元素。
  • 抽象元素(Element)角色:定义了一个接受访问者的方法(accept),其意义是指,每一个元素都要可以被访问者访问。
  • 具体元素(ConcreteElement)角色: 提供接受访问方法的具体实现,而这个具体的实现,通常情况下是使用访问者提供的访问该元素类的方法。
  • 对象结构(ObjectStructure)角色: 对象结构是一个元素的集合,它用于存放元素对象,并且提供了遍历其内部元素的方法。它可以结合组合模式来实现,也可以是一个简单的集合对象,如一个List对象或一个Set对象。
3、关于访问者模式的UML
4、关于访问者模式的适用场景
  • 对象结构相对稳定,但其操作算法经常变化的程序。
  • 对象结构中的对象需要提供多种不同且不相关的操作,而且要避免让这些操作的变化影响对象的结构。
5、关于访问者模式的优缺点

# 访问者模式的优点

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

# 访问者模式的缺点

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

二、访问者模式 Demo

1、Demo 设计

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

  • 首先,定义一个公司(Company)接口,它是抽象访问者,提供了两个根据纸(Paper)或铜(Cuprum)这两种元素创建作品的方法;再定义艺术公司(ArtCompany)类和造币公司(Mint)类,它们是具体访问者,实现了父接口的方法。
  • 然后,定义一个材料(Material)接口,它是抽象元素,提供了 accept(Company visitor)方法来接受访问者(Company)对象访问;再定义纸(Paper)类和铜(Cuprum)类,它们是具体元素类,实现了父接口中的方法。
  • 最后,定义一个材料集(SetMaterial)类,它是对象结构角色,拥有保存所有元素的容器 List,并提供让访问者对象遍历容器中的所有元素的 accept(Company visitor)方法。
2、Demo 实现

# Company 抽象访问者(Visitor)角色

代码语言:javascript复制
public interface Company {
    
    /**
     * 操作Page元素
     *
     * @param element
     * @return
     */
    String create(Paper element);

    /**
     * 操作Cuprum元素
     *
     * @param element
     * @return
     */
    String create(Cuprum element);
}

# ArtCompany/MintCompany 具体访问者(ConcreteVisitor)角色

代码语言:javascript复制
public class ArtCompany implements Company {

    @Override
    public String create(Paper element) {
        // 艺术公司利用Paper元素操作
        return "打印广告";
    }

    @Override
    public String create(Cuprum element) {
        // 艺术公司利用Cuprum元素制造铜像
        return "孔子铜像";
    }
}

public class MintCompany implements Company {
    
    @Override
    public String create(Paper element) {
        // 造币公司利用Paper元素造纸币
        return "纸币";
    }

    @Override
    public String create(Cuprum element) {
        // 造币公司利用Cuprum元素造铜币
        return "铜币";
    }
}

# Material 抽象元素(Element)角色

代码语言:javascript复制
public interface Material {
    
    /**
     * 给指定访问者提供访问当前元素(就是this)的方法
     *
     * @param visitor 指定的访问者
     * @return 访问当前元素返回的结果
     */
    String accept(Company visitor);

}

# Paper/Cuprum 具体元素(ConcreteElement)角色

代码语言:javascript复制
public class Paper implements Material {

    @Override
    public String accept(Company visitor) {
        // 让指定访问者visitor访问当前Paper元素
        return visitor.create(this);
    }
}
public class Cuprum implements Material {

    @Override
    public String accept(Company visitor) {
        // 让指定访问者visitor访问当前Cuprum元素
        return visitor.create(this);
    }
}

# MaterialSet 对象结构(ObjectStructure)角色

代码语言:javascript复制
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);
    }
}
3、Demo 测试
代码语言:javascript复制
public class Client {

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

0 人点赞