访问者模式——对数据不同操作的解耦


Demo 地址: https://github.com/ooblee/HelloDesignPattern

1. 定义

访问者模式(Visitor Pattern):提供一个作用于某对象结构中的各元素的操作表示,它使我们可以在不改变各元素的类的前提下定义作用于这些元素的新操作。访问者模式是一种对象行为型模式。

比如医院的药品处方单,作为多种数据的集合,不同的工作人员需要进行不同的操作。比如划价人员计算价格,药房人员准备药品。

对于这样一个复杂的数据对象,同时存在多个访问者,每个访问者又有不同的操作,可以应用访问者模式来进行设计。

2. 设计

主要角色:

  • 抽象访问者(Visitor),定义为每一个具体元素的访问的方法。
  • 具体访问者(Concrete Visitor),实现抽象访问者的方法,实现对具体元素的访问。
  • 抽象元素(Element),定义 accept 方法来接收抽象访问者。
  • 具体元素(Concrete Element),实现抽象元素的 accept 方法,调用访问者的方法完成对元素的操作。
  • 对象结构(Object Structure),元素的聚合。

类图:

访问者模式-类图

抽象访问者,对具体元素 A 和 B 的访问:

public interface Visitor {

    void visitA(ElementA a);

    void visitB(ElementB b);

}

抽象元素,通过 accept 方法接收访问者:

public interface Element {

    void accept(Visitor visitor);
}

具体元素 A,除了一些业务方法的实现,还有实现 accpet 方法,调用 visitor 的 visit 方法来访问当前对象:

public class ElementA implements Element {

    @Override
    public void accept(Visitor visitor) {
        visitor.visitA(this);
    }

    public void sayHello() {
        System.out.println("hello A.");
    }

    public void sayHi() {
        System.out.println("hi A.");
    }

}

具体访问者 HelloVisitor,实现 visit 方法,调用元素进行业务处理:

public class HelloVisitor implements Visitor {

    @Override
    public void visitA(ElementA a) {
        a.sayHello();
    }

    @Override
    public void visitB(ElementB b) {
        b.sayHello();
    }
}

元素聚合类 ObjectStructure,为元素的容器,可以传递 Visitor 给对应的元素类:

public class ObjectStructure {

    private ElementA elementA;
    private ElementB elementB;

    public ObjectStructure() {
    }

    public void addElement(ElementA a) {
        this.elementA = a;
    }

    public void addElement(ElementB b) {
        this.elementB = b;
    }

    public void accept(Visitor visitor) {
        elementA.accept(visitor);
        elementB.accept(visitor);
    }
}

使用的时候,可以很方便的新增或者删除访问者,而无需影响旧代码。

public class TestVisitor {

    public static void main(String[] args) {

        ObjectStructure objectStructure = new ObjectStructure();
        ElementA elementA = new ElementA();
        ElementB elementB = new ElementB();
        objectStructure.addElement(elementA);
        objectStructure.addElement(elementB);

        // 第一种访问者
        Visitor helloVisitor = new HelloVisitor();
        objectStructure.accept(helloVisitor);

        // 第二种访问者
        Visitor hiVisitor = new HiVisitor();
        objectStructure.accept(hiVisitor);

    }
}

2.1. 双重分派机制

具体元素对访问者调用的过程也是双重分派的过程:

  • 具体元素 ConcreteElement 定义了一个 accept 方法来接收抽象访问者 Visitor。具体访问者 ConcreteVisitor 会从这里传递过去。
  • 在 ConcreteElement 的 accept 方法中调用 Visitor 的 visit 方法,把当前元素传递给访问者。
  • 在 ConcreteVisitor 的 visit 方法中,调用具体元素 ConcreteElement 的业务方法。

使用双重分派的好处是,新增访问者不需要修改原本的代码。

3. 应用

应用场景比较有限。

在操作复杂对象,需要不同的访问者来进行不一样的处理,可以考虑使用访问者模式进行改善。

3.1. Spring:BeanDefinitionVisitor

BeanDefinition 为 Spring Bean 的定义信息,在 Spring 解析完配置后,会生成 BeanDefinition 并且记录下来。下次通过 getBean 获取 Bean 的时候,会通过 BeanDefinition 来实例化具体的 Bean 对象。

Spring 的 BeanDefinitionVisitor 用来访问 BeanDefinition。

主要在于解析属性或者构造方法里面的占位符,并且把解析的结果更新到 BeanDefinition 中。

这里就应用的访问者模式。

抽象元素为 BeanDefinition。对 Bean 的定义信息,比如属性值、构造方法参数或者更具体的实现。

具体元素有 RootBeanDefinition、ChildBeanDefinition、GenericBeanDefinition 等等。

这里因为没有对访问者进行扩展,所以只有一个具体访问者 BeanDefinitionVisitor,没有再抽出一层抽象访问者。访问者的访问方法为:

	public void visitBeanDefinition(BeanDefinition beanDefinition) {
		visitParentName(beanDefinition);
		visitBeanClassName(beanDefinition);
		visitFactoryBeanName(beanDefinition);
		visitFactoryMethodName(beanDefinition);
		visitScope(beanDefinition);
		visitPropertyValues(beanDefinition.getPropertyValues());
		ConstructorArgumentValues cas = beanDefinition.getConstructorArgumentValues();
		visitIndexedArgumentValues(cas.getIndexedArgumentValues());
		visitGenericArgumentValues(cas.getGenericArgumentValues());
	}

这里也没有使用双重分派模式,直接调用 visit 进行元素的访问。

		BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver);

		String[] beanNames = beanFactoryToProcess.getBeanDefinitionNames();
		for (String curName : beanNames) {
			if (!(curName.equals(this.beanName) && beanFactoryToProcess.equals(this.beanFactory))) {
				BeanDefinition bd = beanFactoryToProcess.getBeanDefinition(curName);
				try {
					visitor.visitBeanDefinition(bd);
				}
				catch (Exception ex) {
					throw new BeanDefinitionStoreException(bd.getResourceDescription(), curName, ex.getMessage(), ex);
				}
			}
		}

4. 特点

4.1. 优势

  • 易扩展,新的访问者只需要创建一个新的类,无需修改源码。
  • 复用,复用元素对象,相同的元素对象可以被不同的访问者访问。

4.2. 缺点

  • 添加元素复杂,抽象的访问者依赖了具体的元素,所以新增元素会影响到所有具体的访问者。
  • 破坏封装,元素需要暴露一定的内部状态给访问者调用。
发布了61 篇原创文章 · 获赞 43 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/firefile/article/details/90314409