Visitor Pattern

摘要

设计模式是对设计原则的具体实践,在编码过程中我们要牢记设计原则,根据当前需求灵活选用我们要使用的设计模式。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

转载于:https://my.oschina.net/liufq/blog/2966453

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
访问者模式(Visitor Pattern)是一种行为型设计模式,它允许你将算法与对象结构分离,使得能够在不改变对象结构的前提下向对象结构中添加新的操作。 在访问者模式中,有两个核心概念:元素(Element)和访问者(Visitor)。元素表示对象结构中的元素,它们都要实现一个接口或抽象类,该接口或抽象类定义了访问者可以访问的元素的方法。访问者表示一个对元素的操作,它们也都要实现一个接口或抽象类,该接口或抽象类定义了访问者对元素可以执行的操作的方法。 访问者模式的核心思想就是,访问者对象通过调用元素对象的方法,实现对元素对象的访问和操作。通常情况下,元素对象的方法中会传递一个访问者对象,以便访问者对象可以在元素对象的方法中对元素对象进行操作。 在 Java 中,访问者模式的实现通常需要使用到接口、抽象类、继承、多态等特性。具体实现过程中,需要定义一个访问者接口或抽象类,其中定义了对元素对象的操作方法;同时需要定义一个元素接口或抽象类,其中定义了访问者可以访问的元素的方法。然后,在具体的元素类中实现元素接口的方法,在具体的访问者类中实现访问者接口的方法。最后,在客户端中创建具体的元素对象和访问者对象,并将访问者对象传递给元素对象的方法中,实现对元素对象的访问和操作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值