Visitor Pattern

最近看了这篇讲 Visitor Pattern 的文章,让我对 Visitor Pattern 有了更深的认识。其实网上很多关于设计模式的文章都很皮毛,没有讲到这个模式在实际中能解决什么样的问题,可见这些文章是就模式论模式而已。这篇文章栗子举得还可以,可能也是因为我最近遇到了类似的问题,使我更容易理解。不过例子毕竟是例子,原文中的例子还是有些太简单,容易让人误解。所以在我的这篇文章里再补充一下例子,加一些解释,希望大家能更好地理解 Visitor Pattern,并在工作中应用以解决实际问题。

更改后的示例代码

Feeder.java

public class Feeder {
    void feed(Dog dog) {
        dog.eat();
        System.out.println("Dog is very happy");
        dog.bark();
    }

    void feed(Cat cat) {
        cat.eat();
        System.out.println("Cat is very happy");
        cat.mew();
    }

    /**
     * Process as the visitor pattern way.
     */
    void processSmart(Animal animal) {
        animal.accept(this);
    }

    void processFoo(Animal animal) {
        if (animal instanceof Dog) {
            feed((Dog) animal);
        }

        if (animal instanceof Cat) {
            feed((Cat) animal);
        }
    }

    public static void main(String[] args) {
        Feeder feeder = new Feeder();
        feeder.processSmart(new Dog());
        feeder.processSmart(new Cat());

        // Of course, the foo way also can success. But very foo.
        feeder.processFoo(new Dog());
        feeder.processFoo(new Cat());
    }
}

Cat.java

public class Cat implements Animal {
    @Override
    public void eat() {
        System.out.println("Cat eat food.");
    }

    public void mew() {
        System.out.println("Cat is mewing");
    }

    @Override
    public void accept(Feeder feeder) {
        feeder.feed(this);
    }
}

Dog.java

package me.yanglifan.demoall.design_pattern.visitor;

public class Dog implements Animal {
    @Override
    public void eat() {
        System.out.println("Dog eat food.");
    }

    public void bark() {
        System.out.println("Dog is barking");
    }

    @Override
    public void accept(Feeder feeder) {
        feeder.feed(this);
    }
}

对示例代码的几点说明

  1. Feeder 类和 FeederVisitor 类可以合二为一。原文示例为了更清楚的讲解 Visitor Pattern 所以分开两者。而在实际代码中,将两者合二为一是更好的选择。feed 方法其实就是 visit 方法。
  2. feed 方法的内容在原文示例中非常简单,只是调用了 Dog 或 Cat 的 eat 方法。所以自然也有人会问,直接把 feed 方法的参数换位 Animal 不就可以了,何必劳 Visitor Pattern 大驾。这其实是过度简单的示例代码误导读者,使其曲解了作者的意图。在实际代码中,feed 方法中的内容不可能只是调用 eat 方法这么简单。Overload 的意图就是不同的 Overload 形式为的是实现不同的逻辑,所以在实际中,feed 方法两种不同的 Overload 形式里面的逻辑应该不同才对。多数逻辑不同,少数逻辑相同才是常见的情况。在这种情况下,显然不能通过把参数改为 Animal 来解决问题。所以我在修改过的示例代码中,各自加入了 Dog 和 Cat 所特有的方法,以更接近于实际情况。

Java 中的多态

Visitor Pattern 实际是为了解决 Java 多态相关的一个问题的。feed 方法可以接受的参数类型是 Dog 或者是 Cat。而如果你要传给它一个 Animal 类型的参数,因为 Animal 不是 DogCat,也有可能是 Fish,所以你这样做会让 Java 无所适从,自然也就会编译错误。所以,你在调用 feed 方法是,必须传给一个它所能接受的参数。你也许会想 JVM 你自己用 instanceof 判断一下不就行了。可问题是 JVM 有怎么知道都有哪些类是 Animal 的子类呢?难不成扫描 classpath 下的所有类不成?为了一个方法调用付出这么大的代价,实在得不偿失。

所以上面所说的要求完全是不合理的,想想像 Groovy 这样的动态编程语言都没法实现上面的要求,为什么要求 Java 去做呢?

Visitor Pattern 中的思想

对于上面的问题,使用 Visitor Pattern 就很好解决。如果你要把 Animal 硬塞给 Feeder.feed 方法,那么 Feeder 是没有办法知道你给它的究竟是什么 Animal,自然也就没有办法选择相应的方法了。Visitor Pattern 的思想就是,既然我自己(Feeder)没有办法知道来的动物究竟是那种,那我就拜访你,你来告诉我好了。所以,Animal 上添加了一个 accept 方法,参数是 Feeder。因为 Feeder 只有一个,所以不存在多态的问题(问题:如果 Feeder 也会有多个,那怎么办?)。accept 方法调用 Feeder.feed 方法,这时再由 Animal 来告诉 Feeder。因为这时已经是某种具体的 Animal,所以自然也就能调用到正确的 Feeder.feed 的 Overload 形式了。

你可能感兴趣的:(Visitor Pattern)