设计模式实战-访问者模式,减少代码体积

时间:2022-07-22
本文章向大家介绍设计模式实战-访问者模式,减少代码体积,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

1、定义

访问者模式(Visitor Pattern)的英文定义如下:

Represent an operation to be performed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates.

意思是:封装一些作用于某种数据结构中的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。简单地来说,就是将数据结构和数据操作相分离。

比如说,做过开发的都知道,普通用户和 Root 用户在 Linux 机器上对文件或者文件夹进行操作时,Root 用户往往不受限制,而普通用户却需要各种访问权限才能正常进行,这种同样是访问文件或文件夹,不同的访问者表现出的行为不同的现象就是我们的访问者模式,文件或文件夹就是被访问的元素,Root 用户或者普通用户就是我们的访问者,而文件或文件夹往往是位于计算机或者其它存储设备上的,这里的存储设备就是访问者模式中的 ObjectStructure,可以类比为元素的容器对象。

2、组成角色

访问者模式包含角色如下:

  • 访问者(Visitor):定义对不同的元素进行访问时的抽象行为,一般来说,有多少个具体元素,就有多少个抽象接口;
  • 具体访问者(ConcreteVisitor):实现上面 Visitor 定义的所有接口,用来指定该访问者对各个元素进行访问时的具体行为,在本文中由 Root 用户和普通用户扮演该角色;
  • 元素(Element):抽象的被访问的元素,一般会定义一个 accept 方法,指定其被访问时的抽象行为;
  • 具体元素(ConcreteElement):具体的被访问的元素,实现上面 Element 的 accept 方法,各个元素负责定义自己的 accept 行为,来表示其被访问时的行为,本文中由 FileElement 和 DictionaryElement 类扮演;
  • 对象结构(ObjectStructure):对象结构实际上是一个被访问元素的集合,好比一个元素容器,对容器的具体元素的访问表现出的行为如何,这是由访问者模式决定的。

角色之间的 UML 关系图如下:

3、访问者模式代码实现

3.1 抽象访问者

// 抽象的访问者角色,需要针对每个被访问元素都定义一个接口
public interface Visitor {
    void visit(ConcreteElementA element);
}

3.2 具体访问者

// 具体的访问者
public class ConcreteVisitorA implements Visitor {
    @Override
    public void visit(ConcreteElementA element){
        // 实现自己的对元素ConcreteElementA的访问行为
    }
}

3.3 抽象元素

// 被访问元素的抽象
public interface Element {
    // 声明 accept 方法,代表元素可以被访问
    void accept(Visitor visitor);
}

3.4 具体元素

// 实现抽象元素接口
public class ConcreteElementA implements Element {
    @Override
    public void accept(Visitor visitor) {
        // 传入对象自身
        visitor.visit(this);
    }
}

3.5 对象结构

public class ObjectStructure {
    private List<Element> elements;
    public ObjectStructure(List<Element> elements) {
        this.elements = elements;
    }
    // 自定义方法,该方法往往要对 elements 进行遍历
    public void show(Visitor visitor) {
        for(Element element: elements) {
            element.accept(visitor);
        }
    }
}

4、优缺点

访问者模式的优点:

  • 数据结构和数据操作相分离;
  • 对访问者拓展性良好,只需要增加新的访问者类即可;
  • 各个角色职责明确,符合单一职责原则。

访问者模式的缺点:

  • 元素变更时会导致整个代码都要调整。

5、应用场景

访问者模式的典型应用场景如下:

  • 对象的结构(元素)比较稳定,而访问者频繁变动的场景;
  • 数据操作和数据结构分离的场景。

6、使用实例

还是以文章开始讲的 Root 用户和普通用户访问文件或文件夹为例,下面我们以访问者模式来实现一下:

6.1 抽象的访问元素

// 被访问的元素
interface Element {
    void accept(Visitor visitor);
}

6.2 具体访问元素 —— 文件元素

// 文件元素
class FileElement implements Element {
    // 当前元素的访问权限
    private String lookPerms;
    // 文件名
    private String name;
    FileElement(String name, String lookPerms) {
        this.name = name;
        this.lookPerms = lookPerms;
    }
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
    String getName() {
        return name;
    }
    String getLookPerms() {
        return lookPerms;
    }
}

6.3 具体访问元素 —— 文件夹元素

// 文件夹
class DictionaryElement implements Element {
    // 当前元素的访问权限
    private String lookPerms;
    // 文件夹名
    private String name;
    DictionaryElement(String name, String lookPerms) {
        this.name = name;
        this.lookPerms = lookPerms;
    }
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
    String getName() {
        return name;
    }
    String getLookPerms() {
        return lookPerms;
    }
}

6.4 抽象的访问者

// 访问者
interface Visitor {
    // 定义对不同的元素(文件)进行访问时的具体行为
    void visit(FileElement fileElement);
    // 定义对不同的元素(文件夹)进行访问时的具体行为
    void visit(DictionaryElement dictionaryElement);
}

6.5 具体访问者 ——Root 用户

// root用户
class RootVisitor implements Visitor {
    @Override
    public void visit(FileElement fileElement) {
        System.out.println("当前:Root " + "要访问的文件名:" + fileElement.getName() + " 允许访问!");
    }
    @Override
    public void visit(DictionaryElement dictionaryElement) {
        System.out.println("当前:Root " + "要访问的文件夹名:" + dictionaryElement.getName() + " 允许访问!");
    }
}

6.6 具体访问者 —— 普通用户

// 普通用户
class NormalVisitor implements Visitor {
    // 定义该用户具备的权限集合
    private List<String> lookPerms;
    NormalVisitor(List<String> lookPerms) {
        this.lookPerms = lookPerms;
    }
    @Override
    public void visit(FileElement fileElement) {
        if (this.lookPerms.indexOf(fileElement.getLookPerms()) >= 0) {
            System.out.println("当前:普通用户" + " 具备权限:" + this.lookPerms.toString() + " 要访问的文件名:" + fileElement.getName()  + " 允许访问!");
        } else {
            System.out.println("当前:普通用户" + " 具备权限:" + this.lookPerms.toString() +" 要访问的文件名:" + fileElement.getName() + " 权限不足,禁止访问!");
        }
    }
    @Override
    public void visit(DictionaryElement dictionaryElement) {
        if (this.lookPerms.indexOf(dictionaryElement.getLookPerms()) >= 0) {
            System.out.println("当前:普通用户" + " 具备权限:" + this.lookPerms.toString() + " 要访问的文件夹名:" + dictionaryElement.getName()  + " 允许访问!");
        } else {
            System.out.println("当前:普通用户" + " 具备权限:" + this.lookPerms.toString() +" 要访问的文件夹名:" + dictionaryElement.getName() + " 权限不足,禁止访问!");
        }
    }
}

6.7 存储设备

// ObjectStructure角色
class Computor {
    // 计算机中的文件和文件夹List
    private List<Element> elementList = new LinkedList<>();
    {
        elementList.add(new FileElement("Java讲义.pdf", "look-file"));
        elementList.add(new DictionaryElement("program", "look-dictionary"));
    }
    // 展示该电脑中的文件和文件夹
    void showFileAndDict(Visitor visitor) {
        for (Element element: elementList) {
            element.accept(visitor);
        }
    }
}

6.8 客户端

public static void main(String[] args) {
    Computor computor = new Computor();
    computor.showFileAndDict(new NormalVisitor(Collections.singletonList("look-dictionary")));
    computor.showFileAndDict(new RootVisitor());
    computor.showFileAndDict(new NormalVisitor(Arrays.asList("look-file", "look-dictionary")));
}

输出结果如下:

当前:普通用户 具备权限:[look-dictionary] 要访问的文件名:Java 讲义.pdf 权限不足,禁止访问! 当前:普通用户 具备权限:[look-dictionary] 要访问的文件夹名:program 允许访问! 当前:Root 要访问的文件名:Java 讲义.pdf 允许访问! 当前:Root 要访问的文件夹名:program 允许访问! 当前:普通用户 具备权限:[look-file, look-dictionary] 要访问的文件名:Java 讲义.pdf 允许访问! 当前:普通用户 具备权限:[look-file, look-dictionary] 要访问的文件夹名:program 允许访问!

7、总结

访问者模式适应于元素种类基本不变但是 visit 行为变化的场景,或者说访问者不断增加的场景,访问者增加时我们只需要增加新的访问者类即可,一定程度上避免了在 visit 上进行 if…else 的繁杂逻辑判断,减少了代码体积