Visitor Pattern

原创
2018/12/02 14:56
阅读数 119

摘要

设计模式是对设计原则的具体实践,在编码过程中我们要牢记设计原则,根据当前需求灵活选用我们要使用的设计模式。Visitor Pattern 是一个不常用的模式,在我看来,visitor pattern 也算是面向对象里的一种奇技淫巧了。

what

什么是visitor模式?从Wikipedia 上的定义为:In object-oriented programming and software engineering, the visitor design pattern is a way of separating an algorithm from an object structure on which it operates.. 是将算法与类结构分开的这么一种模式,而且是面向对象独有的。

When

我们在什么条件下才可以使用这个模式呢?有以下几个方面需要满足:

  • 有一组提前定义好的类(已知)
  • 对象上的操作是未知的
  • 对象需要根据不同的类做出不同的操作,因此需要使用(instanceof)判断

操作就是定义中的algorithm,提前定义好的类就是object structure。也就是说由于操作未知,所以将原本属于类中的操作拿出来,放到 visitor 中。

Why

其实按上面的定义是不是感觉 visitor pattern 违反了将类本身的职责放在类中这个简单原则呢?在我看来是的,那为何出现了这种反原则的模式并且堂而皇之的成为了24种模式之一呢?其实这是因为语言自身的问题导致的。visitor pattern 的底层本质是 double dispatching, 而像Java 这种语言只支持single dispatching,而要实现 double dispatching 怎么办呢,就只能使用 visitor pattern这种笨拙的模式了。

How

现在我们有两种动物,定义如下:

public abstract class Animal{}
public class Dog extends Animal{}
public class Cat extends Animal{}

我们需要增加一个叫声的方法,由于猫狗的叫声不一样,我们需要将3个类都修改一下。

public abstract class Animal {
    public abstract void makeSound();
}
public class Dog extends Animal{
    public void makeSound() {
        System.out.println("wang wang");
    }
}
public class Cat extends Animal{
    public void makeSound(){
        System.out.println("miao miao");
    }
}

过了两天我们发现还需要增加一个eat 的方法,三个类又需要同步修改,违反了OCP原则,这时我们就可以使用Visitor Pattern,将变化提取出来, Animal的三个类保持不动。

public abstract class Animal{}
public class Dog extends Animal{}
public class Cat extends Animal{}
public abstract class AnimalVisitor {
    public abstract void visit(Animal animal);
    public abstract void visit(Dog dog);
    public abstract void visit(Cat cat);
}
public class MakeSoundVisitor extends AnimalVisitor{
    public void visit(Animal animal){
        System.out.println("animal sound")
    }
    public void visit(Dog dog){
        System.out.println("wang wang");
    }
    public void visit(Cat cat) {
        System.out.println("miao miao");
    }
}
public class EatVisitor extends AnimalVisitor {
    public void visit(Animal animal){
        System.out.println("animal eat");
    }
    public void visit(Dog dog){
        System.out.println("eat dog food");
    }
    public void visit(Cat cat){
        System.out.println("eat cat food");
    }
}

很美好,我们每当有什么操作需要添加的时候就新增一个继承了AnimalVisitor的类,由它来定义具体的行为。我们可以这样使用

Dog dog = new Dog();
Cat cat = new Cat();

EatVisitor eat = new EatVisitor();
eat.visit(dog);
eat.visit(cat);

但这存在一个问题,我们只能使用具体的Dog Cat 类,而不能使用父类Animal

Animal dog = new Dog();
eat.visit(dog);//animal eat

明明dog 是 Dog类型,调用却到了Animal上,就是因为Java不具备按运行时类型来做不同的函数调用,也就是上面所说的不支持double dispatching,才导致了这样的结果。

如何解决这个问题呢?Java是支持方法的动态调用的(single dispatching),我们可以根据这个来间接实现double dispatching,也就是由Animal 类族做一次转发。

public abstract class Animal{
    public void visit(AnimalVisitor visitor){
        visitor.visit(this);
    }
}
public class Dog extends Animal{
    public void visit(AnimalVisitor visitor){
        visitor.visit(this);
    }
}
public class Cat extends Animal{
    public void visit(AnimalVisitor visitor){
        visitor.visit(this);
    }
}

很多visitor 模式的例子对不同的类使用了不同的方法名,这里我们在每个子类中的代码与父类中的代码一样,但是this的含义是不一样的。

使用时我们就需要由Visitor 调用 Animal变成Animal 调用Visitor了。

EatVisitor eat = new EatVisitor();
Animal dog = new Dog();
dog.visit(eat);

compare

Visitor Patter 将子类中的实现全部拿到了Visitor中来做,如果熟悉函数式编程的人就会觉得这个模式很面熟。其实这就是函数式编程中强大的模式匹配的一部分,根据不同的子类选择的不同的函数而已。

summary

Visitor 模式使用的很稀少,很大一个原因是它的条件太苛刻,它要求被visit的类族是稳定的,但是做过需求的都知道类是不断变化的。网上也有很多人说visitor 模式是一个反模式,不应该使用。我们辩证地来看待,毕竟这是在语言限制的条件做出的不得已选择。

reference

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部