2010-04-08 143 views
63

我真的很困惑访客模式及其用途。我似乎无法想象使用这种模式或其目的的好处。如果有人能够用例子来解释,如果可能的话,那会很好。访客模式的用途与例子

+6

您是否检查过关于这个主题的维基百科? http://en.wikipedia.org/wiki/Visitor_pattern哪些部分完全不清楚?或者你在寻找真实世界的例子吗? – BalusC 2010-04-08 23:50:04

回答

51

曾几何时......

class MusicLibrary { 
    private Set<Music> collection ... 
    public Set<Music> getPopMusic() { ... } 
    public Set<Music> getRockMusic() { ... } 
    public Set<Music> getElectronicaMusic() { ... } 
} 

然后你意识到你希望能够通过其他类型过滤图书馆的馆藏。你可以不断添加新的getter方法。或者你可以使用访问者。

interface Visitor<T> { 
    visit(Set<T> items); 
} 

interface MusicVisitor extends Visitor<Music>; 

class MusicLibrary { 
    private Set<Music> collection ... 
    public void accept(MusicVisitor visitor) { 
     visitor.visit(this.collection); 
    } 
} 

class RockMusicVisitor implements MusicVisitor { 
    private final Set<Music> picks = ... 
    public visit(Set<Music> items) { ... } 
    public Set<Music> getRockMusic() { return this.picks; } 
} 
class AmbientMusicVisitor implements MusicVisitor { 
    private final Set<Music> picks = ... 
    public visit(Set<Music> items) { ... } 
    public Set<Music> getAmbientMusic() { return this.picks; } 
} 

您将数据从算法中分离出来。您将算法卸载到访客实现。您可以通过创建更多访问者来添加功能,而不是不断修改(并鼓胀)保存数据的类。

+29

的声音对不起,这对Visitor模式来说太简单并不是一个很好的例子。访问者模式的主要机制之一,通过访问元素的类型(双派遣)选择功能没有显示-1 – 2010-04-11 12:50:00

+0

在参加了一个编译器课程之后,我也意识到这个例子是多么毫无意义。 – 2011-05-06 19:27:04

+3

@HaraldScheirich访客可能会也可能不会选择按类型选择功能。即使没有这些,我发现访客是非常有用的。 – DJClayworth 2011-12-01 15:57:29

6

它提供了另一层抽象。降低对象的复杂性并使其更加模块化。 Sorta就像使用一个接口一样(实现是完全独立的,没有人关心它是如何完成的)。

现在我从来没有使用过它,但它对于:实现一个需要的特定函数在不同的子类中完成,因为每个子类都需要以不同的方式实现它,所以另一个类将实现所有的功能。有点像一个模块,但仅用于一组类。维基百科有一个很好的解释:http://en.wikipedia.org/wiki/Visitor_pattern 他们的例子有助于解释我想说的。

希望能帮助我们澄清一点。

编辑**对不起,我链接到维基百科你的答案,但他们确实有一个体面的例子:)不要试图成为那个说去找到它自己的人。

+0

这种解释类似于战略模式 – Waclock 2016-08-18 16:15:36

1

它是将数据操作与实际数据分开。作为奖励,您可以在类的整个层次结构中重复使用相同的访问者类,这样可以避免携带与您的实际对象无关的数据操作算法。

158

因此,您可能已经阅读了关于访问者模式的不同解释,并且您可能仍然在说“但是您什么时候可以使用它!”

传统上,访问者习惯于在不牺牲类型安全性的情况下实施类型测试,只要您的类型事先已知并且事先已知就可以了。比方说,我们有几类,如下所示:

abstract class Fruit { } 
class Orange : Fruit { } 
class Apple : Fruit { } 
class Banana : Fruit { } 

而且我们说,我们创建一个Fruit[]

var fruits = new Fruit[] 
    { new Orange(), new Apple(), new Banana(), 
     new Banana(), new Banana(), new Orange() }; 

我想在三个列表分区列表,每个都包含橘子,苹果,或香蕉。你会怎么做?那么,容易解决方案将是一个类型的测试:

List<Orange> oranges = new List<Orange>(); 
List<Apple> apples = new List<Apple>(); 
List<Banana> bananas = new List<Banana>(); 
foreach (Fruit fruit in fruits) 
{ 
    if (fruit is Orange) 
     oranges.Add((Orange)fruit); 
    else if (fruit is Apple) 
     apples.Add((Apple)fruit); 
    else if (fruit is Banana) 
     bananas.Add((Banana)fruit); 
} 

它的工作原理,但也有很多与此代码的问题:

  • 一开始,它的丑陋。
  • 它不是类型安全的,我们不会在运行时捕获类型错误。
  • 它不可维护。如果我们添加一个新的水果衍生实例,我们需要对每个执行水果类型测试的地方进行全局搜索,否则我们可能会错过类型。

访客模式优雅地解决问题。通过修改我们的基础水果类开始:

interface IFruitVisitor 
{ 
    void Visit(Orange fruit); 
    void Visit(Apple fruit); 
    void Visit(Banana fruit); 
} 

abstract class Fruit { public abstract void Accept(IFruitVisitor visitor); } 
class Orange : Fruit { public override void Accept(IFruitVisitor visitor) { visitor.Visit(this); } } 
class Apple : Fruit { public override void Accept(IFruitVisitor visitor) { visitor.Visit(this); } } 
class Banana : Fruit { public override void Accept(IFruitVisitor visitor) { visitor.Visit(this); } } 

看起来我们是复制粘贴代码,但要注意的派生类都调用不同的重载(该Apple电话Visit(Apple)Banana电话Visit(Banana),等等) 。

实现访问者:

class FruitPartitioner : IFruitVisitor 
{ 
    public List<Orange> Oranges { get; private set; } 
    public List<Apple> Apples { get; private set; } 
    public List<Banana> Bananas { get; private set; } 

    public FruitPartitioner() 
    { 
     Oranges = new List<Orange>(); 
     Apples = new List<Apple>(); 
     Bananas = new List<Banana>(); 
    } 

    public void Visit(Orange fruit) { Oranges.Add(fruit); } 
    public void Visit(Apple fruit) { Apples.Add(fruit); } 
    public void Visit(Banana fruit) { Bananas.Add(fruit); } 
} 

现在你可以不用型式试验分区的水果:

FruitPartitioner partitioner = new FruitPartitioner(); 
foreach (Fruit fruit in fruits) 
{ 
    fruit.Accept(partitioner); 
} 
Console.WriteLine("Oranges.Count: {0}", partitioner.Oranges.Count); 
Console.WriteLine("Apples.Count: {0}", partitioner.Apples.Count); 
Console.WriteLine("Bananas.Count: {0}", partitioner.Bananas.Count); 

这样做的优点是:

  • 是相对干净,容易阅读代码。
  • 类型安全,类型错误在编译时被捕获。
  • 可维护性。如果我添加一个删除具体的Fruit类,我可以修改我的IFruitVisitor接口来相应地处理类型,编译器会立即找到我们实现接口的所有地方,以便我们进行适当的修改。

随着中说,游客通常是矫枉过正,而且他们有一种倾向非常复杂的API,它可以是非常繁琐定义一个新的访问者为每一个新类的行为。

通常,应使用类似继承的简单模式来代替访问者。例如,在原则上我可以写一个类,如:

class FruitPricer : IFruitVisitor 
{ 
    public double Price { get; private set; } 
    public void Visit(Orange fruit) { Price = 0.69; } 
    public void Visit(Apple fruit) { Price = 0.89; } 
    public void Visit(Banana fruit) { Price = 1.11; } 
} 

它的工作原理,但有什么优势这个简单的修改:

abstract class Fruit 
{ 
    public abstract void Accept(IFruitVisitor visitor); 
    public abstract double Price { get; } 
} 

所以,当以下条件成立,你应该使用用户:

  • 你有一个明确的,已知的一组将被访问的类。

  • 对所述类的操作没有明确定义或事先已知。例如,如果某人正在使用您的API,并且想要为消费者提供一种向对象添加新的临时功能的方法。它们也是通过临时功能扩展密封类的一种便捷方式。

  • 您执行一类对象的操作并希望避免运行时类型测试。遍历具有不同属性的不同对象的层次结构时,通常会出现这种情况。

时,不要使用用户:

  • 您支持的一类对象,它们的派生类型事先是不知道的操作。

  • 对象的操作是事先定义好的,特别是如果它们可以从基类继承或在接口中定义。

  • 它更容易让客户使用继承为类添加新功能。

  • 您正在遍历具有相同属性或接口的对象层次结构。

  • 你想要一个相对简单的API。

+6

除访问者未被修改(开放/封闭原则)之外。任何新的水果必须有一个新的方法。访问者最好只有一个“访问(果实)”方法和一个将每个果实映射到特定方法的具体实现。 (以便其他访问者类可以扩展具体的基础) – jgauffin 2012-09-03 18:03:59

+4

@jgauffin Visitor模式的正式结果之一是,无论何时添加一个可访问的新对象,都会创建一个新方法,因此违反了Open/Closed暗示着这种模式的后果。具有类型解析的Visit()方法会带来诸多缺点,包括不允许IDE,编译器,RTE等验证实现。 – Fleep 2016-07-27 20:45:09

2

访客模式的示例。图书,水果蔬菜& 是类型的基本要素“的Visitable” 有两种“访问者”BillingVisitor & OfferVisitor每个游客都有自己的目的.Algo计算账单和算法中计算这些元素的提议封装在各自的访问者和Visitables(元素)保持不变。

import java.util.ArrayList; 
import java.util.List; 


public class VisitorPattern { 

    public static void main(String[] args) { 
     List<Visitable> visitableElements = new ArrayList<Visitable>(); 
     visitableElements.add(new Book("I123",10,2.0)); 
     visitableElements.add(new Fruit(5,7.0)); 
     visitableElements.add(new Vegetable(25,8.0)); 
     BillingVisitor billingVisitor = new BillingVisitor(); 
     for(Visitable visitableElement : visitableElements){ 
      visitableElement.accept(billingVisitor); 
     } 

     OfferVisitor offerVisitor = new OfferVisitor(); 
     for(Visitable visitableElement : visitableElements){ 
      visitableElement.accept(offerVisitor); 
     } 
     System.out.println("Total bill " + billingVisitor.totalPrice); 
     System.out.println("Offer " + offerVisitor.offer); 

    } 

    interface Visitor { 
     void visit(Book book); 
     void visit(Vegetable vegetable); 
     void visit(Fruit fruit); 
    } 

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


    static class OfferVisitor implements Visitor{ 
     StringBuilder offer = new StringBuilder(); 

     @Override 
     public void visit(Book book) { 
      offer.append("Book " + book.isbn + " discount 10 %" + " \n"); 
     } 

     @Override 
     public void visit(Vegetable vegetable) { 
      offer.append("Vegetable No discount \n"); 
     } 

     @Override 
     public void visit(Fruit fruit) { 
      offer.append("Fruits No discount \n"); 
     } 

    } 

    static class BillingVisitor implements Visitor{ 
     double totalPrice = 0.0; 

     @Override 
     public void visit(Book book) { 
      totalPrice += (book.quantity * book.price); 
     } 

     @Override 
     public void visit(Vegetable vegetable) { 
      totalPrice += (vegetable.weight * vegetable.price); 
     } 

     @Override 
     public void visit(Fruit fruit) { 
      totalPrice += (fruit.quantity * fruit.price); 
     } 

    } 

    static class Book implements Visitable{ 
     private String isbn; 
     private double quantity; 
     private double price; 

     public Book(String isbn, double quantity, double price) { 
      this.isbn = isbn; 
      this.quantity = quantity; 
      this.price = price; 
     } 

     @Override 
     public void accept(Visitor visitor) { 
      visitor.visit(this); 
     } 
    } 

    static class Fruit implements Visitable{ 
     private double quantity; 
     private double price; 

     public Fruit(double quantity, double price) { 
      this.quantity = quantity; 
      this.price = price; 
     } 

     @Override 
     public void accept(Visitor visitor) { 
      visitor.visit(this); 
     } 
    } 

    static class Vegetable implements Visitable{ 
     private double weight; 
     private double price; 

     public Vegetable(double weight, double price) { 
      this.weight = weight; 
      this.price = price; 
     } 


     @Override 
     public void accept(Visitor visitor) { 
      visitor.visit(this);    
     } 
    } 


} 
1

我认为访问者模式的主要目的是它具有很高的可扩展性。直觉是你买了一个机器人。机器人已经完全实现了基本功能,如前进,左转,右转,返回,挑选某物,说出相位,...

有一天,你想让你的机器人可以去你的邮局。所有这些基本功能都可以实现,但您需要将机器人带到商店并“更新”您的机器人。商店卖家不需要修改机器人,只需将一个新的更新芯片放到机器人上,它就可以做你想做的事。

有一天,你想让你的机器人去超市。同样的过程,你必须把你的机器人带到商店并更新这个“高级”功能。无需修改机器人本身。

等等...

所以Visitor模式的想法是,给所有实施基本功能,您可以使用访问者模式来添加复杂的功能无限多的。在这个例子中,机器人是你的工人班,而“更新芯片”是访客。每次需要功能的新“更新”时,您都不会修改您的工作人员类别,但会添加访问者。