今天我们将介绍访问者模式,在我目前使用过的模式当中,访问者模式是最强大最方便的。

现实世界中的访问者

一个现实世界的类比总能帮助我们理解一个设计模式。我所见过访问者模式起作用的例子是出租车例子。消费者打电话定一辆出租车,出租车到底消费者家门口,一旦人坐进去,就由出租车来控制运送用户。
在超市购物是另一个普遍的例子,其中购物车是你的元素集合,当你去结账的时候,收银员充当一个访问者,处理不同种类的元素(你购买的物品),一些具有直接价格,一些则需要称重,然后提供给你总价。
这是一个现实世界中很难解释的模式,但随着我们引入模式的定义和如何在代码中使用它,事情会愈加明了。

访问者模式

访问者模式是一个行为模式,因为它被用来管理对象间的算法,关系和职责。'四人帮'这本书中提供的访问者模式原始定义为:

允许一个或多个操作在运行时应用到一组对象上,将操作从对象结果中分离出来

访问者实际做的是创建一个使用其他类中数据的外部类。如果你需要在一组对象上进行操作,访问者可能就是适合你的模式。'四人帮'书中讲到访问者模式可以在不改变一个类的同时赋予它额外的功能。让我们来看看这是如何工作的,首先看一下访问者模式的类图定义。
此处输入图片的描述
这个模式的核心是Visitor接口,这个接口为对象结构中每一类ConcreteElement定义类一个访问操作。同时,ConcreteVisitor实现了在Visitor接口中定义的这些操作。具体的访问者遍历这些元素时将保存局部状态。元素接口简单定义了一个accept方法用来允许访问者运行这个元素的一些操作-ConcreteElement将实现这个accept方法。

使用该模式的场景

这个模式应该运用在你以完全不同且不相关的操作处理一组对象的场景。它会避免往你保持良好分离的对象结构中增加代码,因此它鼓励更整洁的代码。你可能想在一组具有不同接口的对象上运行操作。如果你需要以一些不相关的操作处理类的话,访问者模式也是可以起作用的。
总之,如果你想从你用来输入的元素中解耦一些逻辑代码,访问者可能是适合这个工作的最好的模式。

所以它如何在Java中工作?

下面的例子显示了该模式在Java中的一个简单实现。这里我们使用的例子是一个邮费系统。我们的元素集合是在我们购物车里的物品。邮费取决于每件物品的类型和重量,当然也取决于物品如何被邮寄。
让我们为每一个邮寄领域创造一个分离的访问者。这样,我们可以将计算总邮费的逻辑从物品自身中抽离出来。这意味着我们单独的物品不需要知道关于邮费策略的任何事,因此可以从逻辑中很好的耦合。
首先,我们创建通用的被访问者接口:

//Element interface
public interface Visitable{    
    public void accept(Visitor visitor);
}

现在,我们来创建接口的一个具体实现,书

//concrete element 
public class Book implements Visitable{    
    private double price;    
    private double weight;   
    //accept the visitor   
    public void accept(Visitor vistor)   {      
        visitor.visit(this);   
    }   
    public double getPrice()   {      
        return price;   
    }   
    public double getWeight()   {      
        return weight;   
    }
}

可以看到它只是一个简单的POJO,增加了一个额外的accept方法用来让访问者访问该元素。我们可以增加其他的类来处理其他的物品,例如CD,DVD,或者游戏。

现在我们转移到访问者接口。对于每一个不同类别的具体元素,我们都需要创建一个访问方法。因为我们到现在只处理了Book类别,代码很简单:

public interface Visitor{   
    public void visit(Book book);   
    //visit other concrete items    
    public void visit(CD cd);   
    public void visit(DVD dvd);   
}

访问者的实现可以处理当我们访问一本书时想做的具体细节。

public class PostageVisitor implements Visitor{   
    private double totalPostageForCart;        
    //collect data about the book     
    public void visit(Book book){      
    //assume we have a calculation here related to weight and price      
    //free postage for a book over 10            
        if(book.getPrice() < 10.0){         
            totalPostageForCart += book.getWeight() * 2;         
        }    
    }    
    //add other visitors here    
    public void visit(CD cd){...}    
    public void visit(DVD dvd){...}    
    //return the internal state    
    public double getTotalPostage(){      
        return totalPostageForCart;        
    }
}

如你看到的它只是一个简单的公司,但重点是有关书的邮费的所有计算都在一个集中的地方完成。
为了使用这个访问者,我们可能需要一个方式来遍历我们的购物车,如下:

public class ShoppingCart{   
    //normal shopping cart stuff    
    private ArrayList<Visitable> items;   
    public double calculatePostage(){      
        //create a visitor       
        PostageVisitor visitor = new PostageVisitor();       
        //iterate through all items       
        for(Visitable item: items){         
            item.accept(visitor);      
        }     
        double postage = visitor.getTotalPostage();     
        return postage;  
    }
}

记住如果你这里有其他类别的物品,只要访问者实现了一个访问该物品的方法,我们可以很容易地计算出总的邮费。
因此,虽然访问者起初看起来有一点怪,但你可以看出来它能使你的代码多么的整洁。这正是这个模式的全部要点-允许你将特定的逻辑从对象本身抽离出来,以保证你的数据类简洁。

注意它的缺点

访问方法的参数和返回值类型需要预先知晓,因此访问者模式不适合被访问的类有变化倾向的情况。每增加一个新类别的元素,每一个访问者派生的类都必须进行修改。
还有,没有在脑中很好地设计完访问者模式很难将它转化为代码。并且当你确定要增加访问者代码时,它看上去很晦涩。访问者模式很强大,但你应该确保只有必要时才使用它。


原文链接:http://java.dzone.com/articles/design-patterns-visitor