一、引子
对于系统中一个已经完成的类层次结构,我们已经给它提供了满足需求的接口。但是面对新增加的需求,我们应该怎么做呢?如果这是为数不多的几次变动,而且你不用为了一个需求的调整而将整个类层次结构统统地修改一遍,那么直接在原有类层次结构上修改也许是个不错的主意。
但是往往我们遇到的却是:这样的需求变动也许会不停的发生;更重要的是需求的任何变动可能都要让你将整个类层次结构修改个底朝天……。这种类似的操作分布在不同的类里面,不是一个好现象,我们要对这个结构重构一下了。
那么,访问者模式也许是你很好的选择。
二、定义与结构
访问者模式,顾名思义使用了这个模式后就可以在不修改已有程序
结构的前提下,通过添加额外的“访问者”来完成对已有代码功能的提升。
《设计
模式》一书对于访问者模式给出的定义为:表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。从定义可以看出结构对象是使用访问者模式必须条件,而且这个结构对象必须存在遍历自身各个对象的方法。这便类似于java
中的collection概念了。
以下是访问者模式的组成结构:
1) 访问者角色(Visitor):为该对象结构中具体元素角色声明一个访问操作接口。该操作接口的名字和参数标识了发送访问请求给具体访问者的具体元素角色。这样访问者就可以通过该元素角色的特定接口直接访问它。
2) 具体访问者角色(Concrete Visitor):实现每个由访问者角色(Visitor)声明的操作。
3) 元素角色(Element):定义一个Accept操作,它以一个访问者为参数。
4) 具体元素角色(Concrete Element):实现由元素角色提供的Accept操作。
5) 对象结构角色(Object Structure):这是使用访问者模式必备的角色。它要具备以下特征:能枚举它的元素;可以提供一个高层的接口以允许该访问者访问它的元素;可以是一个复合(组合模式)或是一个集合,如一个列表或一个无序集合。
来张类图就能更加清晰的看清访问者模式的结构了。
那么像引言中假想的。我们应该做些什么才能让访问者模式跑起来呢?首先我们要在原有的类层次结构中添加accept方法。然后将这个类层次中的类放到一个对象结构中去。这样再去创建访问者角色……
三、举例
本人阅历实在可怜,没能找到访问者模式在实际应用中的例子。只好借《Thinking in Patterns with java》中的教学代码一用。我稍微做了下修改。
import java.util.*;
import junit.framework.*;
//访问者角色
interface Visitor {
void visit(Gladiolus g);
void visit(Runuculus r);
void visit(Chrysanthemum c);
}
// The Flower hierarchy cannot be changed:
//元素角色
interface Flower {
void accept(Visitor v);
}
//以下三个具体元素角色
class Gladiolus implements Flower {
public void accept(Visitor v) { v.visit(this);}
}
class Runuculus implements Flower {
public void accept(Visitor v) { v.visit(this);}
}
class Chrysanthemum implements Flower {
public void accept(Visitor v) { v.visit(this);}
}
// Add the ability to produce a string:
//实现的具体访问者角色
class StringVal implements Visitor {
String s;
public String toString() { return s; }
public void visit(Gladiolus g) {
s = "Gladiolus";
}
public void visit(Runuculus r) {
s = "Runuculus";
}
public void visit(Chrysanthemum c) {
s = "Chrysanthemum";
}
}
// Add the ability to do "Bee" activities:
//另一个具体访问者角色
class Bee implements Visitor {
public void visit(Gladiolus g) {
System.out.println("Bee and Gladiolus");
}
public void visit(Runuculus r) {
System.out.println("Bee and Runuculus");
}
public void visit(Chrysanthemum c) {
System.out.println("Bee and Chrysanthemum");
}
}
//这是一个对象生成器
//这不是一个完整的对象结构,这里仅仅是模拟对象结构中的元素
class FlowerGenerator {
private static Random rand = new Random();
public static Flower newFlower() {
switch(rand.nextInt(3)) {
default:
case 0: return new Gladiolus();
case 1: return new Runuculus();
case 2: return new Chrysanthemum();
}
}
}
//客户测试程序
public class BeeAndFlowers extends TestCase {
/*
在这里你能看到访问者模式执行的流程:
首先在客户端先获得一个具体的访问者角色
遍历对象结构
对每一个元素调用accept方法,将具体访问者角色传入
这样就完成了整个过程
*/
//对象结构角色在这里才组装上
List flowers = new ArrayList();
public BeeAndFlowers() {
for(int i = 0; i < 10; i++)
flowers.add(FlowerGenerator.newFlower());
}
Visitor sval ;
public void test() {
// It’s almost as if I had a function to
// produce a Flower string representation:
//这个地方你可以修改以便使用另外一个具体访问者角色
sval = new StringVal();
Iterator it = flowers.iterator();
while(it.hasNext()) {
((Flower)it.next()).accept(sval);
System.out.println(sval);
}
}
public static void main(String args[]) {
junit.textui.TestRunner.run(BeeAndFlowers.class);
}
} |
四、双重分派
对了,你在上面的例子中体会到双重分派的实现了没有?
首先在客户程序中将具体访问者模式作为参数传递给具体元素角色(加亮的地方所示)。这便完成了一次分派。
进入具体元素角色后,具体元素角色调用作为参数的具体访问者模式中的visitor方法,同时将自己(this)作为参数传递进去。具体访问者模式再根据参数的不同来选择方法来执行(加亮的地方所示)。这便完成了第二次分派。
五、优缺点及适用情况
先来看下访问者模式的使用能否避免引言中的痛苦。使用了访问者模式以后,对于原来的类层次增加新的操作,仅仅需要实现一个具体访问者角色就可以了,而不
必修改整个类层次。而且这样符合“开闭原则”的要求。而且每个具体的访问者角色都对应于一个相关操作,因此如果一个操作的需求有变,那么仅仅修改一个具体
访问者角色,而不用改动整个类层次。
看来访问者模式确实能够解决我们面临的一些问题。
而且由于访问者模式为我们的系统多提供了一层“访问者”,因此我们可以在访问者中添加一些对元素角色的额外操作。
但是“开闭原则”的遵循总是片面的。如果系统中的类层次发生了变化,会对访问者模式产生什么样的影响呢?你必须修改访问者角色和每一个具体访问者角色……
看来访问者角色不适合具体元素角色经常发生变化的情况。而且访问者角色要执行与元素角色相关的操作,就必须让元素角色将自己内部属性暴露出来,而在
java中就意味着其它的对象也可以访问。这就破坏了元素角色的封装性。而且在访问者模式中,元素与访问者之间能够传递的信息有限,这往往也会限制访问者
模式的使用。
《设计模式》一书中给出了访问者模式适用的情况:
1) 一个对象结构包含很多类对象,它们有不同的接口,而你想对这些对象实施一些依赖于其具体类的操作。
2) 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而你想避免让这些操作“污染”这些对象的类。Visitor使得你可以将相关的操作集中起来定义在一个类中。
3) 当该对象结构被很多应用共享时,用Visitor模式让每个应用仅包含需要用到的操作。
4) 定义对象结构的类很少改变,但经常需要在此结构上定义新的操作。改变对象结构类需要重定义对所有访问者的接口,这可能需要很大的代价。如果对象结构类经常改变,那么可能还是在这些类中定义这些操作较好。
你是否能很好的理解呢?
六、总结
这是一个巧妙而且复杂的模式,它的使用条件比较苛刻。当系统中存在着固定的数据结构(比如上面的类层次),而有着不同的行为,那么访问者模式也许是个不错的选择。
分享到:
相关推荐
《深入浅出Java设计模式》是一本专注于Java编程领域中的设计模式专著,旨在帮助开发者深化对设计模式的理解,提高代码质量和可维护性。设计模式是软件开发中经过时间检验的解决方案,它们是解决常见问题的最佳实践。...
3. **行为型模式**:这类模式关注对象之间的交互和职责分配,例如策略模式(Strategy)、模板方法模式(Template Method)、观察者模式(Observer)、迭代器模式(Iterator)、访问者模式(Visitor)、责任链模式...
本资源"深入浅出设计模式C#Java版"是针对这两种主流编程语言介绍设计模式的经典著作,旨在帮助开发者理解和应用这些模式。 1. **单例模式**:确保一个类只有一个实例,并提供全局访问点。在C#中,可以使用`密封`和`...
本篇文章将深入探讨七个主要的Java设计模式,包括单例模式、简单工厂模式、工厂方法模式、抽象工厂模式、代理模式、动态代理、策略模式以及建造者模式,这些都是提高代码复用性、可维护性和扩展性的关键工具。...
行为型设计模式包括策略模式、模板方法模式、观察者模式、责任链模式、命令模式、解释器模式、迭代器模式、备忘录模式、状态模式、访问者模式和模态模式。这些模式关注对象之间的交互和职责分配,如策略模式允许在...
- **行为型模式**:如策略模式、模板方法模式、观察者模式、责任链模式、命令模式、迭代器模式、访问者模式、备忘录模式、解释器模式和状态模式,着重于对象之间的交互和职责分配。 3. **C#与JAVA实现**: C#和...
本资料"深入浅出Java 23种设计模式"旨在帮助开发者理解和应用这23种经典设计模式。 首先,我们要了解设计模式的三大类别:创建型模式、结构型模式和行为型模式。创建型模式关注对象的创建过程,如单例模式...
行为型设计模式则关注对象间的交互和责任分配,如观察者模式、策略模式、模板方法模式、命令模式、迭代器模式、职责链模式、备忘录模式、状态模式、访问者模式和解释器模式,这些模式能够使代码更加模块化,提高代码...
总之,"深入浅出JavaSwing程序设计"是一本系统学习Java Swing编程的宝贵资源,无论你是初学者还是有经验的开发者,都能从中获得有价值的指导和启示。通过阅读这本书,你将能够熟练地利用Java Swing构建功能丰富的...
本资料“关于java深入浅出设计模式”旨在帮助读者全面理解和掌握Java中的设计模式。 一、单例模式(Singleton) 单例模式确保一个类只有一个实例,并提供一个全局访问点。在Java中,通常通过私有构造器和静态工厂...
《深入浅出设计模式(Java版)》这本书是面向Java开发者的一本全面解析设计模式的指南。它深入探讨了各种设计模式,旨在帮助读者更好地理解和应用这些模式来提高代码的可读性、可维护性和可扩展性。以下是该书可能...
本教程“深入浅出Java语言程序设计”旨在帮助学习者全面理解和掌握Java编程技术,无论你是初学者还是有一定经验的开发者,都能从中受益。 1. **Java基础知识** - **Java语法**:Java的基本语法结构,包括数据类型...
5.11VisitorPattern(访问者模式) 280 5.11.1定义 280 5.11.2现实例子——收银员收银计费 282 5.11.3C#实例——人事评估 283 5.11.4Java实例——维修工程师检查车辆 287 5.11.5优势和缺陷 291 5.11.6应用情??...
本资料“深入浅出Struts2”是InfoQ中文站出品的优秀入门教程,旨在帮助初学者快速理解并掌握Struts2的核心概念和技术。 **一、Struts2架构与核心组件** 1. **FilterDispatcher**: Struts2框架的入口点,作为...
4. 行为型设计模式:这类模式关注对象之间的交互和职责分配,例如策略模式(Strategy)、模板方法模式(Template Method)、观察者模式(Observer)、命令模式(Command)、迭代器模式(Iterator)、访问者模式...