我对访问者模式及其使用感到困惑。我似乎无法想象使用此模式的好处或其目的。如果有人能用例子解释一下,那就太好了。
我真的很困惑访问者模式及其作用。我很难想象使用该模式的好处或目的。如果可能的话,请给出示例解释。我对访问者模式及其使用感到困惑。我似乎无法想象使用此模式的好处或其目的。如果有人能用例子解释一下,那就太好了。
我真的很困惑访问者模式及其作用。我很难想象使用该模式的好处或目的。如果可能的话,请给出示例解释。你可能已经读了数不清的关于访问者模式的解释,但你可能仍然会说:“但是何时使用它!”
传统上,访问者用于在不牺牲类型安全性的情况下实现类型测试,只要你的类型在前面被明确定义并提前知道。假设我们有几个类如下:
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);
}
这段代码能够工作,但存在很多问题:
访问者模式可以优雅地解决这个问题。首先修改我们的基本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);
这样做的好处是:
话虽如此,访问者通常是过度设计,它们往往会极大地复杂化 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。
Visit(Fruit fruit)
方法,并具有将每种水果映射到特定方法的具体实现。(这样其他访问者类可以扩展该具体基类) - jgauffin从前有一个故事...
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; }
}
您将数据与算法分离。您将算法卸载到访问器实现中。您可以通过创建更多的访问者来添加功能,而不是不断修改(和膨胀)保存数据的类。
MusicVisitor
接口上具有不同命名的方法。更好的解决方案是在所有接口上都有一个共同的 getMusic()
方法。 - user3714134访问者模式示例。书籍、水果和蔬菜是类型"Visitable"的基本元素,有两个"Visitors",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);
}
}
}
这是将数据操作与实际数据分开的过程。作为一个额外的好处,您可以重复使用相同的访问者类来处理整个类层次结构,这样就不必携带与实际对象无关的数据操作算法。