Java 关于访客设计模式的困惑
所以,我只是在阅读关于访问者模式的文章,我发现访问者和元素之间的来回非常奇怪 基本上我们称元素为visitor,然后元素将自己传递给visitor。然后访问者操作元素。什么?为什么?感觉太没必要了。我称之为“来来回回的疯狂” 因此,访问者的意图是在所有元素都需要实现相同的操作时,将元素与其操作分离。这样做是为了防止我们需要用新的操作扩展元素,我们不想进入所有这些类并修改已经稳定的代码。所以我们在这里遵循开/闭原则 为什么会有这么多东西来来回回,如果没有这些东西,我们会失去什么 例如,我编写的这段代码牢记了这一目的,但跳过了访问者模式的交互疯狂。基本上,我有跳跃和吃东西的动物。我想将这些动作与对象分离,因此我将动作移动到访问者。吃和跳可以增进动物的健康(我知道,这是一个非常愚蠢的例子…)Java 关于访客设计模式的困惑,java,design-patterns,visitor,visitor-pattern,Java,Design Patterns,Visitor,Visitor Pattern,所以,我只是在阅读关于访问者模式的文章,我发现访问者和元素之间的来回非常奇怪 基本上我们称元素为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);
}