Java 关于访客设计模式的困惑

Java 关于访客设计模式的困惑,java,design-patterns,visitor,visitor-pattern,Java,Design Patterns,Visitor,Visitor Pattern,所以,我只是在阅读关于访问者模式的文章,我发现访问者和元素之间的来回非常奇怪 基本上我们称元素为visitor,然后元素将自己传递给visitor。然后访问者操作元素。什么?为什么?感觉太没必要了。我称之为“来来回回的疯狂” 因此,访问者的意图是在所有元素都需要实现相同的操作时,将元素与其操作分离。这样做是为了防止我们需要用新的操作扩展元素,我们不想进入所有这些类并修改已经稳定的代码。所以我们在这里遵循开/闭原则 为什么会有这么多东西来来回回,如果没有这些东西,我们会失去什么 例如,我编写的这段

所以,我只是在阅读关于访问者模式的文章,我发现访问者和元素之间的来回非常奇怪

基本上我们称元素为visitor,然后元素将自己传递给visitor。然后访问者操作元素。什么?为什么?感觉太没必要了。我称之为“来来回回的疯狂”

因此,访问者的意图是在所有元素都需要实现相同的操作时,将元素与其操作分离。这样做是为了防止我们需要用新的操作扩展元素,我们不想进入所有这些类并修改已经稳定的代码。所以我们在这里遵循开/闭原则

为什么会有这么多东西来来回回,如果没有这些东西,我们会失去什么

例如,我编写的这段代码牢记了这一目的,但跳过了访问者模式的交互疯狂。基本上,我有跳跃和吃东西的动物。我想将这些动作与对象分离,因此我将动作移动到访问者。吃和跳可以增进动物的健康(我知道,这是一个非常愚蠢的例子…)


OP中的代码类似于著名的访问者设计模式变体,称为内部访问者(参见Bruno C.d.S.Oliveira和William R.Cook的《大众的可扩展性、对象代数的实用可扩展性》)。然而,这种变体使用泛型和返回值(而不是
void
)来解决访问者模式解决的一些问题

这是什么问题?为什么OP变量可能不够

Visitor模式解决的主要问题是,当您有需要处理相同的异构对象时。正如《四人帮》(the Gang of Four)的作者所说,在

对象结构包含许多具有不同接口的对象类,您希望对这些对象执行依赖于其具体类的操作

这句话缺少的是,虽然您希望“对这些依赖于具体类的对象执行操作”,但您希望将这些具体类视为具有单一多态类型

一个时期的例子 使用动物领域很少是说明性的(我稍后会再讨论),所以这里有另一个更现实的例子。示例是C语言的-我希望它们对您仍然有用

假设您正在开发一个在线餐厅预订系统。作为该系统的一部分,您需要能够向用户显示日历。此日历可以显示给定日期剩余的可用座位数,或列出当天的所有预订

有时,您希望显示一天,但有时,您希望将整个月份显示为单个日历对象。投入整整一年的时间来衡量。这意味着您有三个时段:年、月和日。每个都有不同的接口:

public Year(int year)

public Month(int year, int month)

public Day(int year, int month, int day)
为简洁起见,这些只是三个独立类的构造函数。许多人可能只是将其建模为具有可空字段的单个类,但这会迫使您处理空字段、枚举或其他类型的不好

上述三个类具有不同的结构,因为它们包含不同的数据,但您希望将它们视为一个单一的概念-一个周期

为此,请定义
IPeriod
接口:

internal interface IPeriod
{
    T Accept<T>(IPeriodVisitor<T> visitor);
}
这使您能够将三个异构类视为一个单一类型,并在该单一类型上定义操作,而无需更改接口

例如,这里是一个计算上一期间的实现:

在这里,
period
是一个
IPeriod
对象,但代码不知道它是
,还是

为了清楚起见,上面的示例使用了内部访问者变量,即

动物 使用动物来理解面向对象编程很少有启发性。我认为学校应该停止使用这个例子,因为它更容易混淆而不是帮助

OP代码示例没有遇到访问者模式所解决的问题,因此在这种情况下,如果您没有看到好处,也就不足为奇了

Cat
Dog
类不是异构的。它们具有相同的类字段和相同的行为。唯一的区别在于构造函数。您可以简单地将这两个类重构为一个
Animal
类:

public class Animal {
    private int health;

    public Animal(int health) {
        this.health = health;
    }

    public void increaseHealth(int healthIncrement) {
        this.health += healthIncrement;
    }

    public int getHealth() {
        return health;
    }
}
然后使用两个不同的
health
值为猫和狗定义两种创建方法


由于您现在只有一个类,因此不保证访问者。

来回访问,您是说这个吗

public class Dog implements Animal {

    //...

    @Override
    public void accept(AnimalAction action) {
        action.visit(this);
    }
}
此代码的目的是,您可以在不知道具体类型的情况下对类型进行分派,如下所示:

public class Main {

    public static void main(String[] args) {
        AnimalAction jumpAction = new JumpVisitor();
        AnimalAction eatAction = new EatVisitor();


        Animal animal = aFunctionThatCouldReturnAnyAnimal();
        animal.accept(jumpAction);
        animal.accept(eatAction);
    }

    private static Animal aFunctionThatCouldReturnAnyAnimal() {
        return new Dog();
    }
}
因此,你得到的是:你只需要知道动物是一种动物,就可以对动物采取正确的个体行动


这在遍历复合模式时特别有用,其中叶节点是
动物
s,内部节点是
动物
的聚合(例如
列表
)。
列表
不能与您的设计一起处理。

访问者中的来回是为了模拟一种机制,您可以根据两个对象的运行时类型选择方法实现

如果您的动物和访问者的类型都是抽象的(或多态的),这将非常有用。在这种情况下,根据a)您希望执行哪种操作(访问),以及b)您希望将此操作应用于哪种动物,您可以选择2 x 2=4种方法实现


如果您使用的是具体的和非多态类型,那么这种来回转换的部分内容确实是多余的。

访问者模式解决了应用乐趣的问题
var previous = period.Accept(new PreviousPeriodVisitor());
var next = period.Accept(new NextPeriodVisitor());

dto.Links = new[]
{
    url.LinkToPeriod(previous, "previous"),
    url.LinkToPeriod(next, "next")
};
public class Animal {
    private int health;

    public Animal(int health) {
        this.health = health;
    }

    public void increaseHealth(int healthIncrement) {
        this.health += healthIncrement;
    }

    public int getHealth() {
        return health;
    }
}
public class Dog implements Animal {

    //...

    @Override
    public void accept(AnimalAction action) {
        action.visit(this);
    }
}
public class Main {

    public static void main(String[] args) {
        AnimalAction jumpAction = new JumpVisitor();
        AnimalAction eatAction = new EatVisitor();


        Animal animal = aFunctionThatCouldReturnAnyAnimal();
        animal.accept(jumpAction);
        animal.accept(eatAction);
    }

    private static Animal aFunctionThatCouldReturnAnyAnimal() {
        return new Dog();
    }
}
(defmethod visit ((visitor integer) (node string))
  (format t "integer ~s visits string ~s!~%" visitor node))

(defmethod visit ((visitor integer) (node integer))
  (format t "integer ~s visits integer ~s!~%" visitor node))

(defmethod visit ((visitor string) (node string))
  (format t "string ~s visits string ~s!~%" visitor node))

(defmethod visit ((visitor string) (node integer))
  (format t "string ~s visits integer ~s!~%" visitor node))
(defun visitor-pattern (visitor list)
  ;; map over the list, doing the visitation
  (mapc (lambda (item) (visit visitor item)) list)
  ;; return  nothing
  (values))
(visitor-pattern 42 '(1 "abc"))
integer 42 visits integer 1!
integer 42 visits string "abc"!

(visitor-pattern "foo" '(1 "abc"))
string "foo" visits integer 1!
string "foo" visits string "abc"!
StringVisitor::visit(Visited obj)
{
  obj.Accept(self)
}

IntegerVisitor::visit(Visited obj)
{
  obj.Accept(self)
}
IntegerNode::visit(StringVisitor v)
{
   print(`integer @{self.value} visits string @{v.value}`)
}

IntegerNode::visit(IntegerVisitor v)
{
   print(`integer @{self.value} visits string @{v.value}`)
}
class VisitorBase {
  virtual void Visit(VisitedBase);
}

class IntegerVisitor;
class StringVisitor;

class VisitedBase {
  virtual void Accept(IntegerVisitor);
  virtual void Accept(StringVisitor);
}

class IntegerVisitor : inherit VisitorBase {
  Integer value;
  void Visit(VisitedBase);
}

class StringVisitor: inherit VisitorBase {
  String value;
  void Visit(VisitedBase);
}

class IntegerNode : inherit VisitedBase {
  Integer value;
  void Accept(IntegerVisitor);
  void Accept(StringVisitor);
}

class StringNode : inherit VisitedBase {
  String value;
  void Accept(IntegerVisitor);
  void Accept(StringVisitor);
}