AFP*_*555 38 java design-patterns visitor visitor-pattern
所以,我只是在阅读访问者模式,我发现访问者和元素之间的来回非常奇怪!
基本上我们称之为元素,我们将其传递给访问者,然后元素将自身传递给访问者。然后访问者操作元素。什么?为什么?感觉太没必要了。我称之为“来回疯狂”。
因此,当需要在所有元素上实施相同的操作时,访问者的意图是将元素与其操作分离。这样做是为了防止我们需要用新动作扩展我们的元素,我们不想进入所有这些类并修改已经稳定的代码。所以我们在这里遵循开放/封闭原则。
为什么会有这一切来回,如果我们没有这些,我们会失去什么?
例如,我编写的这段代码记住了这个目的,但跳过了访问者模式的疯狂交互。基本上我有会跳跃和进食的动物。我想将这些动作与对象分离,所以我将动作移到了访客。吃和跳会增加动物的健康(我知道,这是一个非常愚蠢的例子......)
public interface AnimalAction { // Abstract Visitor
public void visit(Dog dog);
public void visit(Cat cat);
}
public class EatVisitor implements AnimalAction { // ConcreteVisitor
@Override
public void visit(Dog dog) {
// Eating increases the dog health by 100
dog.increaseHealth(100);
}
@Override
public void visit(Cat cat) {
// Eating increases the cat health by 50
cat.increaseHealth(50);
}
}
public class JumpVisitor implements AnimalAction { // ConcreteVisitor
public void visit(Dog dog) {
// Jumping increases the dog health by 10
dog.increaseHealth(10);
}
public void visit(Cat cat) {
// Jumping increases the cat health by 20
cat.increaseHealth(20);
}
}
public class Cat { // ConcreteElement
private int health;
public Cat() {
this.health = 50;
}
public void increaseHealth(int healthIncrement) {
this.health += healthIncrement;
}
public int getHealth() {
return health;
}
}
public class Dog { // ConcreteElement
private int health;
public Dog() {
this.health = 10;
}
public void increaseHealth(int healthIncrement) {
this.health += healthIncrement;
}
public int getHealth() {
return health;
}
}
public class Main {
public static void main(String[] args) {
AnimalAction jumpAction = new JumpVisitor();
AnimalAction eatAction = new EatVisitor();
Dog dog = new Dog();
Cat cat = new Cat();
jumpAction.visit(dog); // NOTE HERE. NOT DOING THE BACK AND FORTH MADNESS.
eatAction.visit(dog);
System.out.println(dog.getHealth());
jumpAction.visit(cat);
eatAction.visit(cat);
System.out.println(cat.getHealth());
}
}
Run Code Online (Sandbox Code Playgroud)
Mar*_*ann 35
OP 中的代码类似于著名的访问者设计模式的变体,称为内部访问者(参见例如,可扩展性。布鲁诺 C. d. S. 奥利维拉和威廉 R. 库克的对象代数的可扩展性)。然而,这种变体使用泛型和返回值(而不是void)来解决访问者模式解决的一些问题。
那是哪个问题,为什么 OP 变化可能不足?
访问者模式解决的主要问题是当您需要处理异类对象时。正如四人帮(Design Patterns的作者)所说,您在以下情况下使用该模式
“一个对象结构包含许多具有不同接口的对象类,并且您希望对依赖于它们具体类的这些对象执行操作。”
这句话中缺少的是,虽然您想“对依赖于它们的具体类的这些对象执行操作”,但您希望将这些具体类视为具有单一的多态类型。
使用动物域很少是说明性的(稍后我会回到这个问题),所以这是另一个更现实的例子。示例在 C# 中 - 我希望它们仍然对您有用。
假设您正在开发一个在线餐厅预订系统。作为该系统的一部分,您需要能够向用户显示日历。该日历可以显示给定日期有多少剩余座位可用,或列出当天的所有预订。
有时,您希望显示某一天,但在其他时候,您希望将整个月显示为单个日历对象。投入一整年以获得良好的衡量标准。这意味着您有三个期间: year、month和day。每个都有不同的接口:
public Year(int year)
public Month(int year, int month)
public Day(int year, int month, int day)
Run Code Online (Sandbox Code Playgroud)
为简洁起见,这些只是三个独立类的构造函数。许多人可能只是将其建模为具有可为空字段的单个类,但这会迫使您处理空字段、枚举或其他类型的麻烦。
上述三个类由于包含不同的数据而具有不同的结构,但您希望将它们视为一个概念 -句点。
为此,定义一个IPeriod接口:
internal interface IPeriod
{
T Accept<T>(IPeriodVisitor<T> visitor);
}
Run Code Online (Sandbox Code Playgroud)
并使每个类都实现接口。这是Month:
internal sealed class Month : IPeriod
{
private readonly int year;
private readonly int month;
public Month(int year, int month)
{
this.year = year;
this.month = month;
}
public T Accept<T>(IPeriodVisitor<T> visitor)
{
return visitor.VisitMonth(year, month);
}
}
Run Code Online (Sandbox Code Playgroud)
这使您能够将三个异构类视为单一类型,并在该单一类型上定义操作,而无需更改接口。
例如,这里是一个计算上一期间的实现:
private class PreviousPeriodVisitor : IPeriodVisitor<IPeriod>
{
public IPeriod VisitYear(int year)
{
var date = new DateTime(year, 1, 1);
var previous = date.AddYears(-1);
return Period.Year(previous.Year);
}
public IPeriod VisitMonth(int year, int month)
{
var date = new DateTime(year, month, 1);
var previous = date.AddMonths(-1);
return Period.Month(previous.Year, previous.Month);
}
public IPeriod VisitDay(int year, int month, int day)
{
var date = new DateTime(year, month, day);
var previous = date.AddDays(-1);
return Period.Day(previous.Year, previous.Month, previous.Day);
}
}
Run Code Online (Sandbox Code Playgroud)
如果您有一个Day,您将获得前一个Day,但如果您有一个Month,您将获得前一个Month,依此类推。
您可以PreviousPeriodVisitor在本文中看到正在使用的类和其他访问者,但这里是使用它们的几行代码:
var previous = period.Accept(new PreviousPeriodVisitor());
var next = period.Accept(new NextPeriodVisitor());
dto.Links = new[]
{
url.LinkToPeriod(previous, "previous"),
url.LinkToPeriod(next, "next")
};
Run Code Online (Sandbox Code Playgroud)
这里,period是一个IPeriod对象,但代码不知道它是 a Day、 andMonth还是 a Year。
需要明确的是,上面的示例使用了内部访问者变体,它与 Church encoding 同构。
使用动物来理解面向对象编程很少有启发性。我认为学校应该停止使用这个例子,因为它更容易混淆而不是帮助。
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;
}
}
Run Code Online (Sandbox Code Playgroud)
然后使用两个不同的health值为猫和狗定义两种创建方法。
由于您现在只有一个类,因此不保证访问者。
SDJ*_*SDJ 23
Visitor 中的来回是模拟一种双重调度机制,根据两个对象的运行时类型选择一个方法实现。
如果类型这是有用的都你的动物和游客都是抽象的(或多态性)。在这种情况下,您有可能有 2 x 2 = 4 种方法实现可供选择,基于 a) 您想要执行的操作(访问)类型,以及 b) 您希望此操作应用于哪种类型的动物。
如果您使用的是具体的和非多态的类型,那么这种来回的部分确实是多余的。
来回,你是这个意思吗?
public class Dog implements Animal {
//...
@Override
public void accept(AnimalAction action) {
action.visit(this);
}
}
Run Code Online (Sandbox Code Playgroud)
这段代码的目的是你可以在不知道具体类型的情况下分派类型,如下所示:
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();
}
}
Run Code Online (Sandbox Code Playgroud)
所以你得到的是:你可以在只知道它是动物的情况下对动物调用正确的个人动作。
如果您遍历复合模式,其中叶节点是Animals 而内部节点是 的聚合(例如 a List),这将特别有用Animals。AList<Animal>不能与您的设计一起处理。
| 归档时间: |
|
| 查看次数: |
3776 次 |
| 最近记录: |