如何使用访问者模式
访问者模式将数据操作和数据结构分离,客户端使用用访问者对象去访问原有的数据结构,这样做的好处是在不用修改原有数据结构的前提下,通过定义不同的访问者就可以对同一个数据结构进行不同的操作.
它的uml图如下:
一个简单的应用:
公司ceo和cto对员工的绩效进行考核,目前有两种角色,一种是经理,一种工程师。ceo对经理的绩效关注kpi和产品数量,对工程师的绩效关注kpi. cto对经理的绩效关注注kpi和产品数量,对工程师的绩效关注kpi和代码数量.
这里就是一个访问者模式的使用场景
下面来看看代码怎么实现:
public interface Visitor {
void visit(Engineer engineer);
void visit(Manager manager);
}
public abstract class Staff {
public String name;
public int kpi;
public Staff(String name) {
this.name = name;
kpi = new Random().nextInt(10);
}
public abstract void accept(Visitor visitor);
}
public class Engineer extends Staff{
public Engineer(String name) {
super(name);
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
public int getCodeLines(){
return new Random().nextInt(10 * 10000);
}
}
public class Manager extends Staff{
private int products;
public Manager(String name) {
super(name);
products = new Random().nextInt(10);
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
public int getProducts(){
return products;
}
}
public class CEOVisitor implements Visitor{
@Override
public void visit(Engineer engineer) {
System.out.println("工程师 : "+engineer.name + ", KPI : "+engineer.kpi);
}
@Override
public void visit(Manager manager) {
System.out.println("经理 : "+manager.name + ", KPI : "+manager.kpi+", 新产品数量 : "+manager.getProducts());
}
}
public class CTOVisitor implements Visitor{
@Override
public void visit(Engineer engineer) {
System.out.println("工程师 : "+engineer.name+" 代码行数:"+engineer.getCodeLines());
}
@Override
public void visit(Manager manager) {
System.out.println("经理 : "+manager.name+" 产品数量:"+manager.getProducts());
}
}
public class BusinessReport {
List<Staff> mStaffs = new LinkedList<>();
public BusinessReport(){
mStaffs.add(new Manager("王经理"));
mStaffs.add(new Engineer("工程师-Shawn.X"));
mStaffs.add(new Engineer("工程师-Eric"));
mStaffs.add(new Engineer("工程师-K"));
mStaffs.add(new Engineer("工程师-Jay"));
}
public void showReport(Visitor visitor){
for (Staff staff : mStaffs){
staff.accept(visitor);
}
}
}
最后调用
public static void main(String[] args){
BusinessReport businessReport = new BusinessReport();
businessReport.showReport(new CTOVisitor());
businessReport.showReport(new CEOVisitor());
}
Android源码中的访问者模式
在java中注解分为两种,一种是运行时注解,另外一种是编译期注解。运行时注解存在效率问题,所以一直被一些人诟病,编译注解的核心原理依赖APT(Annotaion Processing Tools)实现.
编译时Annotation解析的基本原理是,在某些代码元素上(如类型,函数,字段等)添加注解,在编译时编译器会检查AbstractProcessor的子类,并且调用该类型的process函数,然后将添加了注解的所有元素都传递到process函数中,使得开发人员可以在编译期进行相应的处理, 例如根据注解生成新的java类,这也就是ButterKnife等开源库的基本原理.
在我们了解process这个过程之前,先来了解下Element类
PackageElement - 包元素,包含某个包下面的信息,可以获取到包名等
TypeElement - 类型元素,如某个字段属于某种类型
ExecutableElement - 可执行元素,代表了函数类型的元素
VariableElement - 变量元素
TypeParameterElement - 类型参数元素
我们常常会在注解中看到
@Target(ElementType.TYPE)
上面是在指定作用域,每一个ElementType对应下面一个类,下面一个例子
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
@interface Test{
String value()
}
ElementType.TYPE该注解表明只能作用于函数类型,它对应的元素类型是ExecutableElement元素,当我们用APT处理这个注解时候可以将它转换为ExecutableElement元素,以便获取完整的对应信息.
public interface Element extends AnnotatedConstruct {
TypeMirror asType();
ElementKind getKind();
Set<Modifier> getModifiers();
Name getSimpleName();
Element getEnclosingElement();
List<? extends Element> getEnclosedElements();
boolean equals(Object var1);
int hashCode();
List<? extends AnnotationMirror> getAnnotationMirrors();
<A extends Annotation> A getAnnotation(Class<A> var1);
<R, P> R accept(ElementVisitor<R, P> var1, P var2);
}
可以看到Element中有一个<R, P> R accept(ElementVisitor<R, P> var1, P var2); ,这里就是利用访问者模式去访问Element中的所有元素信息, 我们来看看ElementVisitor.
public interface ElementVisitor<R, P> {
R visit(Element var1, P var2);
R visit(Element var1);
R visitPackage(PackageElement var1, P var2);
R visitType(TypeElement var1, P var2);
R visitVariable(VariableElement var1, P var2);
R visitExecutable(ExecutableElement var1, P var2);
R visitTypeParameter(TypeParameterElement var1, P var2);
R visitUnknown(Element var1, P var2);
}
ElementVisitor中定义了很多visit方法,每个visit方法对应一种元素,和之前ceo和cto访问不同类型员工调用不同visit方法一样。同样我们可以联想到注解中类元素和函数元素的Element是不一样的,访问者模式正好使得Element元素独立(不需要提供复杂的访问成员变量方法)。另外里面visitUnknown方法是为了扩展新的Element调用的。
下面来看看两个ElementVisitor的实现类,SimpleElementVisitor6
public class SimpleElementVisitor6<R, P> extends AbstractElementVisitor6<R, P> {
protected final R DEFAULT_VALUE;
protected SimpleElementVisitor6() {
this.DEFAULT_VALUE = null;
}
protected SimpleElementVisitor6(R var1) {
this.DEFAULT_VALUE = var1;
}
protected R defaultAction(Element var1, P var2) {
return this.DEFAULT_VALUE;
}
public R visitPackage(PackageElement var1, P var2) {
return this.defaultAction(var1, var2);
}
public R visitType(TypeElement var1, P var2) {
return this.defaultAction(var1, var2);
}
public R visitVariable(VariableElement var1, P var2) {
return var1.getKind() != ElementKind.RESOURCE_VARIABLE?this.defaultAction(var1, var2):this.visitUnknown(var1, var2);
}
public R visitExecutable(ExecutableElement var1, P var2) {
return this.defaultAction(var1, var2);
}
public R visitTypeParameter(TypeParameterElement var1, P var2) {
return this.defaultAction(var1, var2);
}
}
它基本没做什么操作,直接访问了元素的默认值.
另外一个元素是ElementKindVisitor6
public class ElementKindVisitor6<R, P> extends SimpleElementVisitor6<R, P> {
public R visitType(TypeElement var1, P var2) {
ElementKind var3 = var1.getKind();
switch(null.$SwitchMap$javax$lang$model$element$ElementKind[var3.ordinal()]) {
case ANNOTATION_TYPE:
return this.visitTypeAsAnnotationType(var1, var2);
case CLASS:
return this.visitTypeAsClass(var1, var2);
case ENUM:
return this.visitTypeAsEnum(var1, var2);
case INTERFACE:
return this.visitTypeAsInterface(var1, var2);
default:
throw new AssertionError("Bad kind " + var3 + " for TypeElement" + var1);
}
}
}
ElementKindVisitor6对于不同的类型进行不同的处理。
总结:在现实情况下,我们根据具体的情况来评估是否适合使用访问者模式,例如,我们的对象结构是否稳定,使用访问者模式是否优化我们的代码,而不是使得代码变得更加复杂.
访问者模式的优点:
(1)各角色职责分离,符合单一职责原则
(2)具有优秀的扩展性
(3)使得数据结构和作用于结构上的操作解耦,使得操作集合可以独立变化
(4)灵活性
访问者模式的缺点:
(1)具体元素对访问者公布细节,违反了迪米特原则
(2)具体元素变更时导致修改成本大
(3)违反了依赖倒置原则,为了达到区别对待而依赖了具体类,没有依赖抽象
【附录】
需要资料的朋友可以加入Android架构交流QQ群聊:513088520
点击链接加入群聊【Android移动架构总群】:加入群聊
获取免费学习视频,学习大纲另外还有像高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)等Android高阶开发资料免费分享。
网友评论