Java 访问者模式的目的与示例
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2604169/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Visitor pattern's purpose with examples
提问by Victor
I'm really confused about the visitor pattern and its uses. I can't really seem to visualize the benefits of using this pattern or its purpose. If someone could explain with examples if possible that would be great.
我真的对访问者模式及其用途感到困惑。我似乎真的无法想象使用这种模式或其目的的好处。如果可能的话,如果有人可以用例子来解释,那就太好了。
采纳答案by Noel Ang
Once upon a time...
曾几何时...
class MusicLibrary {
private Set<Music> collection ...
public Set<Music> getPopMusic() { ... }
public Set<Music> getRockMusic() { ... }
public Set<Music> getElectronicaMusic() { ... }
}
Then you realize you'd like to be able to filter the library's collection by other genres. You could keep adding new getter methods. Or you could use Visitors.
然后您意识到您希望能够按其他流派过滤图书馆的收藏。您可以不断添加新的 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; }
}
You separate the data from the algorithm. You offload the algorithm to visitor implementations. You add functionality by creating morevisitors, instead of constantly modifying (and bloating) the class that holds the data.
您将数据与算法分开。您将算法卸载到访问者实现。您可以通过创建更多访问者来添加功能,而不是不断修改(和膨胀)保存数据的类。
回答by Kaili
It provides another layer of abstraction. Reduces complexity of an object and makes it more modular. Sorta like using an interface(implementation being completely independent and no one cares how it is done just that it gets done.)
它提供了另一层抽象。降低对象的复杂性并使其更加模块化。有点像使用接口(实现是完全独立的,没有人关心它是如何完成的,只是它完成了。)
Now I have never used it but it would be useful for: Implementing a particular function that needs to be done in different subclasses, since each of the sub classes needs to implement it in different ways another class would implement all the functions. Kinda like a module but only for a collection of classes. Wikipedia has a pretty good explanation: http://en.wikipedia.org/wiki/Visitor_patternAnd their example helps explain what I am trying to say.
现在我从未使用过它,但它对以下方面很有用: 实现需要在不同子类中完成的特定功能,因为每个子类需要以不同的方式实现它,另一个类将实现所有功能。有点像一个模块,但只适用于一组类。维基百科有一个很好的解释:http: //en.wikipedia.org/wiki/Visitor_pattern他们的例子有助于解释我想说的。
Hope that helps clear it up a bit.
希望这有助于澄清一点。
EDIT**Sorry I linked to wikipedia for your answer but they really do have a decent example :) Not trying to be that guy that says go find it yourself.
编辑**对不起,我链接到维基百科以获得您的答案,但他们确实有一个不错的例子:) 不要试图成为那个说自己去找的人。
回答by Egor Pavlikhin
It is to separate the data manipulation from the actual data. As a bonus you can reuse the same visitor class for the whole hierarchy of your classes, which again saves you from carrying around the data manipulation algorithms that are irrelevant to your actual objects.
它是将数据操作与实际数据分开。作为奖励,您可以为类的整个层次结构重用相同的访问者类,这再次使您免于携带与实际对象无关的数据操作算法。
回答by Juliet
So you've probably read a bajillion different explanations of the visitor pattern, and you're probably still saying "but when would you use it!"
因此,您可能已经阅读了大量关于访问者模式的不同解释,并且您可能仍在说“但是您什么时候会使用它!”
Traditionally, visitors are used to implement type-testing without sacrificing type-safety, so long as your types are well-defined up front and known in advance. Let's say we have a few classes as follows:
传统上,访问者习惯于在不牺牲类型安全性的情况下实现类型测试,只要您的类型预先明确定义并提前知道。假设我们有几个类,如下所示:
abstract class Fruit { }
class Orange : Fruit { }
class Apple : Fruit { }
class Banana : Fruit { }
And let's say we create a Fruit[]
:
假设我们创建了一个Fruit[]
:
var fruits = new Fruit[]
{ new Orange(), new Apple(), new Banana(),
new Banana(), new Banana(), new Orange() };
I want to partition the list in to three lists, each containing oranges, apples, or bananas. How would you do it? Well, the easysolution would be a type-test:
我想将列表分成三个列表,每个列表包含橙子、苹果或香蕉。你会怎么做?好吧,简单的解决方案是类型测试:
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);
}
It works, but there are lots of problems with this code:
它可以工作,但是此代码存在很多问题:
- For a start, its ugly.
- Its not type-safe, we won't catch type errors until runtime.
- Its not maintainable. If we add a new derived instance of Fruit, we need to do a global search for every place which performs a fruit type-test, otherwise we might miss types.
- 首先,它很丑。
- 它不是类型安全的,我们不会在运行时捕获类型错误。
- 它不可维护。如果我们添加一个新的 Fruit 派生实例,我们需要对每个执行水果类型测试的地方进行全局搜索,否则我们可能会错过类型。
Visitor pattern solves the problem elegantly. Start by modifying our base Fruit class:
访问者模式优雅地解决了这个问题。首先修改我们的基本 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); } }
It looks like we're copy pasting code, but note the derived classes are all calling different overloads (the Apple
calls Visit(Apple)
, the Banana
calls Visit(Banana)
, and so on).
看起来我们正在复制粘贴代码,但请注意派生类都在调用不同的重载(Apple
调用Visit(Apple)
、Banana
调用Visit(Banana)
等)。
Implement the visitor:
实现访问者:
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); }
}
Now you can partition your fruits without a type-test:
现在,您无需进行类型测试即可对水果进行分区:
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);
This has the advantages of:
这具有以下优点:
- Being relatively clean, easy to read code.
- Type-safety, type errors are caught at compile time.
- Maintainability. If I add or remove a concrete Fruit class, I could modify my IFruitVisitor interface to handle the type accordingly, and the compiler will immediately find all places where we implement the interface so we can make the appropriate modifications.
- 相对干净,易于阅读的代码。
- 类型安全,类型错误在编译时被捕获。
- 可维护性。如果我添加或删除一个具体的 Fruit 类,我可以修改我的 IFruitVisitor 接口来相应地处理类型,编译器会立即找到我们实现该接口的所有位置,以便我们进行适当的修改。
With that said, visitors are usually overkill, and they have a tendency to grossly complicate APIs, and it can be very cumbersome to define a new visitor for every new kind of behavior.
话虽如此,访问者通常是矫枉过正的,他们倾向于使 API 非常复杂,并且为每种新行为定义新访问者可能非常麻烦。
Usually, simpler patterns like inheritance should be used in place of visitors. For example, in principle I could write a class like:
通常,应该使用更简单的模式(如继承)来代替访问者。例如,原则上我可以写一个类:
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; }
}
It works, but what's the advantage over this trivial modification:
它有效,但与这种微不足道的修改相比有什么优势:
abstract class Fruit
{
public abstract void Accept(IFruitVisitor visitor);
public abstract double Price { get; }
}
So, you should use visitors when the following conditions hold:
因此,当满足以下条件时,您应该使用访问者:
You have a well-defined, known set of classes which will be visited.
Operations on said classes are not well-defined or known in advance. For example, if someone is consuming your API and you want to give consumers a way to add new ad-hoc functionality to objects. They're also a convenient way to extend sealed classes with ad-hoc functionaity.
You perform operations of a class of objects and want to avoid run-time type testing. This is usually the case when you traverse a hierarchy of disparate objects having different properties.
您有一组定义明确的已知类将被访问。
对所述类的操作没有明确定义或事先知道。例如,如果有人正在使用您的 API,而您希望为消费者提供一种向对象添加新的临时功能的方法。它们也是扩展具有特殊功能的密封类的便捷方式。
您执行一类对象的操作并希望避免运行时类型测试。当您遍历具有不同属性的不同对象的层次结构时,通常会出现这种情况。
Don't use visitors when:
在以下情况下不要使用访客:
You support operations on a class of objects whose derived types are not known in advance.
Operations on objects are well-defined in advance, particularly if they can be inherited from a base class or defined in an interface.
Its easier for clients to add new functionality to classes using inheritance.
You are traversing a hierarchy of objects which have the same properties or interface.
You want a relatively simple API.
您支持对派生类型事先未知的对象类进行操作。
对象上的操作是预先定义好的,特别是如果它们可以从基类继承或在接口中定义。
客户端使用继承向类添加新功能更容易。
您正在遍历具有相同属性或接口的对象层次结构。
您需要一个相对简单的 API。
回答by Shakti
Example of visitor pattern. Book, Fruit & Vegetable are basic elements of type "Visitable"and there are two "Visitors", BillingVisitor & OfferVisitoreach of the visitor has its own purpose .Algo to calculate the bill and algo to calculate the offers on these elements is encapsulated in the respective visitor and the Visitables ( Elements) remain the same.
访问者模式示例。Book、Fruit& Veve是“Visitable”类型的基本元素,有两个“ Visitor ”,BillingVisitor 和 OfferVisitor,每个访问者都有自己的目的。计算账单的算法和计算这些元素的报价的算法封装在相应的访问者和可访问对象(元素)保持不变。
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);
}
}
}
回答by ctNGUYEN
I think the main purpose of visitor pattern is it has high extensibility. The intuition is you've bought a robot. The robot already has fully implemented elementary functionalities as go ahead, turn left, turn right, go back, pick something, speak a phase, …
我认为访问者模式的主要目的是它具有很高的可扩展性。直觉是你买了一个机器人。机器人已经完全实现了基本功能,如前进、左转、右转、返回、挑选东西、说一个阶段……
One day, you want your robot can go to post office for you. With all of these elementary functionalities, it can do, but you need to bring you robot to the shop and "update" your robot. The shop seller do not need to modify the robot, but simply put a new update chip to your robot and it can do what you want.
有一天,您希望您的机器人可以为您去邮局。有了所有这些基本功能,它都可以做到,但您需要将机器人带到商店并“更新”您的机器人。店家不需要修改机器人,只需给你的机器人装上一个新的更新芯片,它就可以为所欲为。
An other day, you want your robot to go to supermarket. Same process, you has to bring your robot to the shop and update this "advanced" functionality. No need to modify the robot itself.
有一天,你想让你的机器人去超市。同样的过程,您必须将机器人带到商店并更新此“高级”功能。无需修改机器人本身。
and so on …
等等 …
So the idea of Visitor pattern is, given all implemented elementary functionalities, you can use visitor pattern to add an infinite number of sophisticated functionalities. In the example, the robot is your worker classes, and the "update chip" are visitors. Each time need a new "update" of functionality, you don't modify your worker class, but you add a visitor.
所以访问者模式的想法是,给定所有实现的基本功能,您可以使用访问者模式添加无限数量的复杂功能。在示例中,机器人是您的工人班级,而“更新芯片”是访客。每次需要新的功能“更新”时,您都不需要修改您的工人类,而是添加一个访问者。