高性能聊天系统
作者:板桥banq
1.4.2 访问者模式定义
访问者(Visitor)模式提供了一种可重用的通用解决模式,GOF(四位作者简称)《设计模式》中的Visitor模式定义如下:
Visitor模式表示作用于某对象集合中各元素的操作。这种操作不是一种原生的,而是使用了访问者模式后才有的方法,即这些元素原本没有做好接受这种操作的准备。因此,没有提供一种专门方法用于外界统一访问,如果强行需要对这些元素进行访问,那么就需要访问者模式为这些元素再设计一个统一接口。这样,通过这个统一接口提供的统一方法,外界可以对这些元素实现统一访问了。
可以这样通俗地理解:有一堆“身份”各异对象(通常是数据状态对象),这些对象有下面的特征:被动性(数据都是被动的),需要等待外界来操作或推动。那么现在外界有访问者来准备操作这些状态类了(例如:和这些类握手),但是走到面前,双方突然傻眼,发现谁也不认识啊(含义:这些对象没有声明可以接受这个动作访问,两者完全没有联系),这些状态类可能属于不同类型的接口,怎么办?
有一种最直接的办法就是:逐个更改这些类的接口代码。但是这样带来的问题也是显而易见的,修改的代码太多反而引起系统的不稳定。
在不能修改原有接口代码的情况下,还有一种办法,增加一个新的统一接口。让这些状态类再继承一次这个新的接口,在这个新的接口中,提供了让访问者访问操作的方法(类似Accept())。
使用访问者模式可以很容易地增加新的操作行为。只要增加一个新的访问者就可以给一批对象增加一个新的操作。否则,就需要逐个修改这些对象的代码。
在什么场合可以使用访问者模式是关键,通过研究设计模式的参与者角色,可以找到相关问题的答案。访问者模式的角色参与者有5个,如下:
· 对象集合结构(Collection):类似Collection这样的对象,该集合中包含一系列对象元素,能够使用Iterator这样的语句对这些元素实现枚举。
· 被访问接口(Visitable):定义一个Accept操作,它是以一个访问者为输入参数的。在访问者模式中,被访问接口一般是在以后扩展中难以进行变化的,这是访问者模式的一个特点,一个模式总是有不变的地方,而优势则是在可以动态扩展变化的地方。
· 被访问具体实现(ConcreteElement):实现Accept具体操作。
· 访问者接口(Visitor):这是一个主要角色,访问者主要是访问上述被访问者,通过被访问者提供的特定接口来直接访问它。
· 访问者具体实现(ConcreteVisitor):这是可以动态扩展变化的部分,也是该模式魅力所在,通过增加一个访问者具体实现,就可以增加对被访问者一系列的新行为和操作。
上述模式角色中,核心角色是3个:访问者、被访问者以及对象集合。在使用访问者模式时,如何从自己的具体项目应用中定位到这3种角色是非常关键的,当然,这个模式有一个非常重要的特点,就是有一个对象集合,在Java中就是类似Collection这样的集合,如果自己的项目中有这种集合概念,并实现对集合中元素相关操作,就可以考虑是否需要使用访问者模式了。
举一个例子:Java中的Collection(包括Vector和Hashtable)是大家最经常使用的技术,但是Collection好像是个黑色大染缸,本来有各种鲜明类型特征的对象一旦放入后,再取出时,这些类型就消失了,那么势必要实现强迫类型转换:
Iterator iterator = collection.iterator()
while (iterator.hasNext()) { //枚举集合中的元素
Object o = iterator.next();
if (o instanceof String) //使用instanceof实现检验判断
System.out.println("'"+o.toString()+"'");
else if (o instanceof Integer){
int temp = ((Integer)o).intValue + 1;
}else if (o instanceof ObjectA)
System.out.println((ObjectA(o)).getValue);
}
如果集合Collection中元素类型有很多,那么随之这种if语句判断会变得很长。从小处看:代码结构显得不够优雅、琐碎复杂;从大处看,每增加一个类型都要修改这个关键类,极有可能触动已经稳定运行的其他代码部分,危险度很高。
本例目的有两个:首先分辨出集合元素的类型,然后针对不同类型实现不同的运算方法。如果是String类型,则打印出;如果是整数型,则实现运算;如果是ObjectA,则打印出ObjectA的getValue返回值。
因为这些元素是存放在一个集合中的,有集合的概念,可以考虑是否使用访问者模式,那么在这个应用中是否还能找到其他角色呢?
这个应用是为了对这些元素逐个访问,然后分别采取相应的行为操作。既然有访问行为发生,那么存在两个基本角色:访问者和被访问者。被访问者是这些元素,而访问者可能没有清晰的实体,但是访问者的行为能够确定,那么本例可以使用访问者模式。
首先从基本角色访问者和被访问者开始设计,被访问者是Collection中的元素,他们必须有一个统一接口,用于接受访问者的访问(accept方法)。代码如下:
public interface Visitable
{
public void accept(Visitor visitor); //用于接受访问者
}
访问者角色有可以确定的行为,如下:
public interface Visitor
{
public visitCollection(Collection collection); //访问行为
public void visit(StringElement stringE);
public void visit(IntegerElement integerE);
public void visit(ObjectAElement objectAE);
//可以在以后扩展中增加新的访问行为
}
两个基本接口确定后,可以设计它们的具体实现。
Visitable接口的具体实现主要是集合Collection中的元素,目前本例中有几个元素类型:String、Integer和ObjectA。如果是String,则打印出这个String对象,这是针对String元素的一个行为,String是一个Java基本类型,如何使这个String具备被访问的能力,那么就要创建一个包装String对象的新类,如下:
public class StringElement implements Visitable
{
private String value;
public StringElement (String string) {
value = string; //通过构造方法,将String字符串包装起来
}
public String getValue(){
return value;
}
public void accept(Visitor visitor) {
visitor. visit(this); //实现回调
}
}
通过StringElement类的构造方法的参数输入,将字符串String包装进入这个类,这种通过建立新类,将原来的类包装起来的做法实际是适配器Adapter模式,在Java中经常使用。
适配器Adapter模式是在不改动原来类的代码的情况下,将这个类进行一定的包装,使之能够适合新的应用,所以Adapter模式也叫Wrapper(包装)模式。
其他元素类型也采取Adapter模式,例如ObjectA的Visitable具体实现如下:
public class ObjectAElement implements Visitable
{
private ObjectA objectA;
public ObjectAElement (ObjectA objectA) {
objectA = objectA; //通过构造方法,将ObjectA包装起来
}
public String getValue(){
return objectA.getValue();
}
public void accept(Visitor visitor) {
visitor. visit(this); //实现回调
}
}
访问者的具体实现是可以动态扩展的,是访问者模式使用的原因所在。正是因为只要添加一个访问者具体实现,就可以为那些被访问者实现一个统一的行为,而不必逐个修改那些被访问者。
public class ConcreteVisitor implements Visitor
{
//在本方法中,实现了对Collection元素的访问
public void visitCollection(Collection collection) {
Iterator iterator = collection.iterator()
while (iterator.hasNext()) {
Object o = iterator.next();
if (o instanceof Visitable)
((Visitable)o).accept(this); //统一执行accpet语句
}
}
public void visit(StringElement stringE) {
System.out.println("'"+string.getValue()+"'");
}
public void visit(IntegerElement integerE){
Integer temp = integerE.getValue();
int temp = temp.intValue + 1;
}
public void visit(ObjectAElement objectAE){
System.out.println(objectAE.getValue());
}
}
客户端调用上述访问者模式如下:
Visitor visitor = new ConcreteVisitor();
visitor. visitCollection(collection);
这两句实现了原始代码冗长的if语句实现的功能,而且通过访问者模式的实现,使得代码在功能上有了进一步扩展的余地,因为修改维护都集中在一个类中的某个方法,不会触及其他的功能,保证了良好的可维护性和稳定性。