访问者模式


访问者模式

访问者模式是一种行为型设计模式,它允许你在不修改对象结构的前提下定义在这些结构上的新操作。

模式介绍

Visitor(访问者)模式中,我们使用了一个访问者类,这个类对元素的操作进行封装。数据结构与数据操作分离,操作保存在Visitor类中,实现中使得操作的新增变得容易,且正确性得到了保证。

访问者模式提供了一种方法来对元素结构中的元素集合进行操作,无需再污染元素结构本身。与中心思想是将元素结构与操作分离的策略及策略执行部分彼此独立,通过这样的分离,能对操作的变化及时做出响应,同时易于对操作进行扩展。

模式结构

访问者模式中包含以下几个角色:

  • Visitor:访问者,为每个具体元素声明一个visit操作,在该操作中对元素结构的每个具体元素进行操作。
  • ConcreteVisitor1ConcreteVisitor2:具体的访问者,实现每个由Visitor声明的操作,每个操作实现算法的不同部分。
  • Element:定义一个accept方法,接收一个访问者对象作为参数,完成访问操作。
  • ConcreteElement1ConcreteElement2:具体的元素,实现Element中定义的抽象接口,以便访问者可以访问它的数据结构。
  • ObjectStructure:对象结构,可以是一个复杂的、多层次的结构,也可以是一个很简单的单对象。

模式实现

假设现在我们有一个游戏中的角色类Role,我们希望根据该角色在游戏中经验的不同,能够进行不同程度的升级,解锁新技能,获取新属性等。但是,我们不希望在Role类中包含与升级有关的逻辑,以免代码变得冗杂。因此,我们可以采用访问者模式,将升级逻辑封装在UpgradeVisitor类中。

// 访问者接口
public interface UpgradeVisitor {
    // 访问弱角色
    void visit(WeakRole weakRole);
    // 访问一般角色
    void visit(NormalRole normalRole);
    // 访问强角色
    void visit(StrongRole strongRole);
}

UpgradeVisitor是一个访问者接口,我们为如游戏角色Role类中的不同属性(如攻击力、防御力、经验)声明了不同的访问方法。我们来看一下Role类的实现:

// 游戏角色类
public abstract class Role {

    public abstract void accept(UpgradeVisitor visitor);

}

Role类是元素Element的抽象类,我们在其中定义了一个accept方法,接收一个访问者对象作为参数,完成访问操作。让我们以弱角色为例:

public class WeakRole extends Role {

    private int attack = 10;
    private int defense = 10;
    private int exp = 0;

    public int getAttack() {
        return attack;
    }

    public int getDefense() {
        return defense;
    }

    public int getExp() {
        return exp;
    }

    public void setAttack(int attack) {
        this.attack = attack;
    }

    public void setDefense(int defense) {
        this.defense = defense;
    }

    public void setExp(int exp) {
        this.exp = exp;
    }

    // 接收访问者访问
    public void accept(UpgradeVisitor visitor) {
        visitor.visit(this);
    }
}

与普通的类别没有任何区别,除了在最后重载的accept函数中调用传入的Visitor来进行不同等级角色的升级操作。

最后,访问者模式中的ObjectStructure实现,我们称之为游戏管理器GameManager

public class GameManager {

    private List<Role> roles = new ArrayList<>();

    // 添加游戏角色
    public void addRole(Role role) {
        roles.add(role);
    }

    // 升级所有游戏角色
    public void upgradeAll(UpgradeVisitor visitor) {
        for (Role role: roles) {
            role.accept(visitor);
        }
    }
}

游戏管理器中可以包含不同等级的角色,我们在其中定义了一个upgradeAll方法,该方法会遍历所有的游戏角色,然后通过角色的accept方法,分别对不同等级的角色进行升级访问,最终调用不同等级的访问者对应的方法来完成升级。

优缺点

优点:

  1. 符合开闭原则,可以很方便地添加新的访问操作,而无需修改元素类。
  2. 将数据结构与操作分离,增加新的操作变得容易,且正确性得到了保证。
  3. 符合单一职责原则,访问者模式将数据结构中元素的每个操作放在了单独的访问者对象中,使得数据结构本身有更多的空间来进行优化。

缺点:

  1. 实现起来比较复杂,尤其是对象结构比较复杂的时候。
  2. 它将元素类的公共接口中提取了访问行为,应该算是破坏了它的封装。

适用场景

访问者模式一般应用于以下几种场景:

  1. 对象结构相对稳定,但是其操作算法经常变化的程序。
  2. 需要对一个对象结构中的对象进行多种不同的并且不相关的操作,而不希望对这些对象结构进行修改。
  3. 在访问一个复杂的、深层次的对象结构中各个元素的操作很自然地形成了一个复杂的递归结构,但是需要避免递归调用所带来的风险。
  4. 对象结构包含很多具有不同接口的对象,而你希望对这些对象实施一些依赖于其具体接口的操作。

总结

访问者模式的核心思想在于将访问操作封装到访问者类中,而不是直接修改元素类。这样,我们可以很容易地在不改变已有代码的情况下,添加新的操作,并且代码结构变得清晰易懂。