如果可能的话,有人可以简单地向我解释访问者模式的用途
我对访客模式及其用途感到困惑。 我似乎无法想象使用这种模式或其目的的好处。 如果有人能够用可能的例子来解释,那就太棒了。 =)
提前致谢
很久以前…
class MusicLibrary { private Set<Music> collection ... public Set<Music> getPopMusic() { ... } public Set<Music> getRockMusic() { ... } public Set<Music> getElectronicaMusic() { ... } }
然后你意识到你希望能够通过其他stream派来过滤图书馆的馆藏。 你可以不断添加新的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; } }
您将数据从algorithm中分离出来。 您将algorithm卸载到访客实现。 您可以通过创build更多的访问者来添加function,而不是不断地修改(和放大)保存数据的类。
所以你可能已经读了一些关于访客模式的不同的解释,你可能还在说“但是你什么时候用它呢?
传统上,访问者习惯于在不牺牲types安全性的情况下实施typestesting,只要您的types事先已经定义清楚并且事先已知。 假设我们有几个类,如下所示:
abstract class Fruit { } class Orange : Fruit { } class Apple : Fruit { } class Banana : Fruit { }
假设我们创build一个Fruit[]
:
var fruits = new Fruit[] { new Orange(), new Apple(), new Banana(), new Banana(), new Banana(), new Orange() };
我想把列表分成三个列表,每个列表包含橙子,苹果或香蕉。 你会怎么做? 那么, 简单的解决scheme将是一个typestesting:
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); }
它工作,但这个代码有很多问题:
- 它的丑陋,一开始。
- 它不是types安全的,直到运行时才会发现types错误。
- 它不可维护。 如果我们添加一个新的水果派生实例,我们需要对每个执行水果typestesting的地方进行全局search,否则我们可能会错过types。
访客模式优雅地解决问题。 从修改我们的基础水果课程开始:
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); } }
现在,您可以在不经过型式testing的情况下分割水果:
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);
这具有以下优点:
- 相对干净,易于阅读代码。
- types安全,types错误在编译时被捕获。
- 可维护性。 如果我添加一个删除具体的Fruit类,我可以修改我的IFruitVisitor接口来相应地处理types,编译器会立即find我们实现接口的所有地方,这样我们可以进行适当的修改。
有了这个说法,访问者通常是矫枉过正的,他们有一个倾向使APIs非常复杂,为每一种新的行为定义一个新的访问者可能是非常麻烦的。
通常,应该使用简单的inheritance模式来代替访问者。 例如,原则上我可以写一个类:
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,并且想要给消费者一种向对象添加新的临时function的方法。 它们也是通过特殊function扩展密封类的一种便捷方式。
-
您执行一类对象的操作,并希望避免运行时typestesting。 当遍历具有不同属性的不同对象的层次结构时,通常是这种情况。
在以下情况下不要使用访客
-
您支持对派生types未知的对象类进行操作。
-
对象的操作是事先定义好的,特别是如果它们可以从基类inheritance或在接口中定义的话。
-
客户更容易使用inheritance为类添加新的function。
-
您正在遍历具有相同属性或接口的对象的层次结构。
-
你想要一个相对简单的API。
它提供了另一层抽象。 降低对象的复杂性并使其更加模块化。 Sorta就像使用一个接口(实现是完全独立的,没有人关心它是如何完成的)。
现在我从来没有使用过它,但是对于:实现一个需要在不同子类中完成的特定函数是有用的,因为每个子类需要以不同的方式实现它,而另一个类将实现所有的function。 有点像一个模块,但只为一个类的集合。 维基百科有一个很好的解释: http : //en.wikipedia.org/wiki/Visitor_pattern他们的例子有助于解释我想说的。
希望有助于澄清一点。
编辑**对不起,我链接到维基百科你的答案,但他们真的有一个体面的例子:)不要试图成为那个说,去find它自己。
访客模式的例子。 Book,Fruit&Vegetable是“Visitable”types的基本要素,有两个“ Visitor ” , BillingVisitor&OfferVisitor,每个访问者都有自己的用途。计算账单和algorithm计算这些元素的报价是封装在相应的访问者和访问者(元素)保持不变。
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); } } }
将数据操作与实际数据分开。 作为奖励,您可以在类的整个层次结构中重复使用相同的访问者类,这样可以避免使用与实际对象无关的数据操作algorithm。
我认为访问者模式的主要目的是具有很高的可扩展性。 直觉是你买了一个机器人。 机器人已经完全实现了基本function,前进,左转,右转,回去,select一些东西,说出一个阶段,…
有一天,你想让你的机器人可以去你的邮局。 所有这些基本function都可以实现,但是您需要将机器人带到商店并“更新”您的机器人。 商店卖家不需要修改机器人,只需要把一个新的更新芯片放到机器人上,它就可以做你想做的事情。
有一天,你想让你的机器人去超市。 同样的过程,你必须把你的机器人带到商店并更新这个“高级”function。 无需修改机器人本身。
等等 …
所以,访客模式的思想是给定所有实现的基本function,您可以使用访客模式添加无限数量的复杂function。 在这个例子中,机器人是你的工人class,而“更新芯片”是访问者。 每次需要一个新的“更新”function,你不要修改你的工人类,但你添加一个访问者。