访问者模式:5 分钟读完一种直观实用的理解方法 | Visitor Pattern: An intuitive and practical approach in 5 minutes

虽然看起来有些复杂,但其目的很常见。
本质源于面向对象编程中,对抽象的权衡取舍,或者说一种优雅的平衡方式。
我们先理解这样一种直观:写代码的时候,为了减少重复,我们主体代码通常希望使用高层接口;但为了支持灵活性,底层实现需要多种实现。
那么,如何在高层接口的基础上,访问到具体底层对象的方法呢?

当然,可以 instanceof + cast,但太不优雅。
访问者模式就从这里介入。

我们引入一个更具体的例子。
假设我们要考虑一个树结构,其中每个节点可由不同的内存布局,或者说有不同的物理实现方式,甚至可能某个逻辑节点时由多个对象构成的——那么显然我们应该将逻辑节点抽象为接口,不妨将其设计为 INode。我们假设逻辑节点之间的映射是通过字符串实现的,那么至少有两个接口如下

public interface INode {
	public INode getChild(String key);
	public void addChild(String key, INode child);
}

然而,为了实现灵活性,有子接口分别以 byte[]byte 作为映射子节点的参数。

public interface INodeA implements INode {
	public INode getChildA(byte[] k);
}

public interface INodeB implements INode {
	public INode getChildB(byte k);
}

在树结构的主体代码中,我们显然希望将 INode 作为主要引用,如

public long searchOnTree(String[] keys) {
	// do something about INode
	INode cur = root;
	while(!cur.isLeaf()) {
		...
	}
}

但是,对某些情况下,我们需要对 INode 引用的对象,调用 getChildA/getChildB
如前文,显然可以 instanceof + cast,但太不优雅。
因为这里只考虑 INode 这个接口的多样性,那么我们可以假设某个静态实例 VISITOR 作为访问者。
设计如下接口

public interface INode {
	public INode accept(Visitor v);
}

考虑两个实现不同接口的类有

public class Node implements INode {
	public INode accept(Visitor v) {
		// call getChild(String);
	}
}

public class NodeA implements INodeA {
	public Inode accept(Visitor v){
		// call getChildA(byte[]);
	}
}

public class NodeB implements INodeB {
	public INode accept(Visitor v) {
		// call getChildB(byte);
	}
}

通过 accept 的动态绑定,可以从 INodeaccept 接口出发,最终调用多样化的实现方法(getChild/getChildA/getChildB)。

事实上, 对于完整的访问者模式,visitor 也可以实现多态——在执行 visitor.visit([INode|INodeA|INodeB]) 时,根据不同的 visitor 对象类型来实现不同的逻辑。

小结,这种做法可以:

  1. 在主体代码中围绕高层引用编程,减少代码重复;
  2. 为不同的实现类型,执行不同的方法,即使这些方法并没有在高层接口中暴露出来
  3. 实现上述基础的情况下,避免使用 instanceof + cast 命令。

一句话:其实就是“包了一层”,用方法动态绑定代替了类型判断、转换。


事实上,还有一种方法可以替换上述做法,可以对比加深理解。
即,将各子类方法并集到公共接口中,同时,为各个子类方法实现 default 直接抛出 UnsupportedOperation 即有

public interface INode {
	public INode getChild(String key);
	default public INode getChildA(byte[] key) {throw new UnsupportedOperation();}
	default public INode getChildB(byte key) {throw new UnsupportedOperation();}
}

仅在对应类中对 getChildA/getChildB 做覆盖 override,在主体代码中直接用公共接口的引用调用对应方法即可。
通过代码逻辑语义避免抛出异常。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值