java - 对访客设计模式感到困惑
问题描述
所以,我只是在阅读访问者模式,发现访问者和元素之间的来回非常奇怪!
基本上我们称之为元素,我们将其传递给访问者,然后元素将自身传递给访问者。然后访问者操作元素。什么?为什么?感觉太没必要了。我称之为“来回疯狂”。
因此,当需要在所有元素上实施相同的操作时,访问者的目的是将元素与其操作分离。这样做是为了防止我们需要用新的操作来扩展我们的元素,我们不想进入所有这些类并修改已经稳定的代码。所以我们在这里遵循开放/封闭原则。
为什么会有这么多来回,如果我们没有这些,我们会失去什么?
例如,我编写的这段代码牢记了这个目的,但跳过了访问者模式的疯狂交互。基本上我有会跳和吃的动物。我想将这些动作与对象分离,因此我将这些动作移至访客。进食和跳跃会增加动物的健康(我知道,这是一个非常愚蠢的例子......)
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());
}
}
解决方案
OP 中的代码类似于众所周知的访问者设计模式的变体,称为内部访问者(参见例如,Bruno C. d. S. Oliveira 和 William R. Cook的面向大众的可扩展性。对象代数的实用可扩展性)。然而,这种变体使用泛型和返回值(而不是void
)来解决访问者模式解决的一些问题。
那是哪个问题,为什么 OP 变化可能不够?
访问者模式解决的主要问题是当您有需要处理的异构对象时。正如四人组(Design Patterns的作者)所说,您在以下情况下使用该模式
“一个对象结构包含许多具有不同接口的对象类,并且您希望对这些对象执行依赖于它们的具体类的操作。”
这句话缺少的是,虽然您希望“对这些依赖于其具体类的对象执行操作”,但您希望将这些具体类视为具有单个多态类型。
一个时期的例子
使用动物域很少是说明性的(我稍后会谈到),所以这里有另一个更现实的例子。示例在 C# 中 - 我希望它们对您仍然有用。
想象一下,您正在开发一个在线餐厅预订系统。作为该系统的一部分,您需要能够向用户显示日历。该日历可以显示给定日期的剩余座位数,或列出当天的所有预订。
有时,您希望显示一天,但在其他时候,您希望将整个月份显示为单个日历对象。投入一整年以获得良好的衡量标准。这意味着您有三个时期:年、月和日。每个都有不同的接口:
public Year(int year)
public Month(int year, int month)
public Day(int year, int month, int day)
为简洁起见,这些只是三个独立类的构造函数。许多人可能只是将其建模为具有可为空字段的单个类,但这会迫使您处理空字段、枚举或其他类型的讨厌。
上述三个类具有不同的结构,因为它们包含不同的数据,但您希望将它们视为一个概念 - 一个period。
为此,请定义一个IPeriod
接口:
internal interface IPeriod
{
T Accept<T>(IPeriodVisitor<T> visitor);
}
并使每个类实现接口。这是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);
}
}
这使您能够将三个异构类视为单一类型,并在该单一类型上定义操作,而无需更改接口。
例如,这里是一个计算前一周期的实现:
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);
}
}
如果你有 a Day
,你会得到 previous Day
,但如果你有 a Month
,你会得到 previous 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")
};
这里,period
是一个IPeriod
对象,但代码不知道它是 a Day
、 andMonth
还是 a Year
。
需要明确的是,上面的示例使用了 Internal Visitor 变体,它与 Church 编码同构。
动物
使用动物来理解面向对象编程很少有启发性。我认为学校应该停止使用这个例子,因为它更容易混淆而不是帮助。
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
然后使用两个不同的值为猫和狗定义两种创建方法。
由于您现在只有一个班级,因此不需要访问者。
推荐阅读
- javascript - 随机生成字符不包括
- html - Angular10 - [(ngModel)] 和 (ngModelChange) 在保存到数据库后不读取值
- reactjs - 生产环境和测试环境不同的IP地址如何自动处理
- c# - 在构造函数中运行异步方法会停止 ObservableCollection 显示数据
- bash - Bash:如果“cat myfile”命令没有返回任何内容,我如何回显字符串?
- azure-active-directory - 仅来自逻辑应用连接器的 Azure AD 条件访问
- javascript - 无法使用 Exif 读取元数据字段
- javascript - 条纹浏览器兼容
- javascript - 当我运行 vue-cli -service build -mode test 时出现错误。未定义进程。但是使用 vue-cli -service build 不会报这个错误
- typo3 - 如何为 TYPO3 站点创建级联页面错误处理程序