🔍目的
允许将新功能添加到现有的类层次结构中,而不会影响这些层次结构,也不会有四人帮访客模式中那样循环依赖的问题。
🔍解释
真实世界例子
我们有一个调制解调器类的层次结构。 需要使用基于过滤条件的外部算法(是Unix或DOS兼容的调制解调器)来访问此层次结构中的调制解调器。
通俗描述
非循环访问者允许将功能添加到现有的类层次结构中,而无需修改层次结构
维基百科
非循环访客模式允许将新功能添加到现有的类层次结构中,而不会影响这些层次结构,也不会创建四人帮访客模式中固有的循环依赖问题。
程序示例
调制解调器的层次结构:
public abstract class Modem {
public abstract void accept(ModemVisitor modemVisitor);
}
public class Zoom extends Modem {
...
@Override
public void accept(ModemVisitor modemVisitor) {
if (modemVisitor instanceof ZoomVisitor) {
((ZoomVisitor) modemVisitor).visit(this);
} else {
LOGGER.info("Only ZoomVisitor is allowed to visit Zoom modem");
}
}
}
public class Hayes extends Modem {
...
@Override
public void accept(ModemVisitor modemVisitor) {
if (modemVisitor instanceof HayesVisitor) {
((HayesVisitor) modemVisitor).visit(this);
} else {
LOGGER.info("Only HayesVisitor is allowed to visit Hayes modem");
}
}
}
调制解调器访问者
类结构:
public interface ModemVisitor {
}
public interface HayesVisitor extends ModemVisitor {
void visit(Hayes hayes);
}
public interface ZoomVisitor extends ModemVisitor {
void visit(Zoom zoom);
}
public interface AllModemVisitor extends ZoomVisitor, HayesVisitor {
}
public class ConfigureForDosVisitor implements AllModemVisitor {
...
@Override
public void visit(Hayes hayes) {
LOGGER.info(hayes + " used with Dos configurator.");
}
@Override
public void visit(Zoom zoom) {
LOGGER.info(zoom + " used with Dos configurator.");
}
}
public class ConfigureForUnixVisitor implements ZoomVisitor {
...
@Override
public void visit(Zoom zoom) {
LOGGER.info(zoom + " used with Unix configurator.");
}
}
代码实践 :
var conUnix = new ConfigureForUnixVisitor();
var conDos = new ConfigureForDosVisitor();
var zoom = new Zoom();
var hayes = new Hayes();
hayes.accept(conDos);
zoom.accept(conDos);
hayes.accept(conUnix);
zoom.accept(conUnix);
程序输出:
// Hayes modem used with Dos configurator.
// Zoom modem used with Dos configurator.
// Only HayesVisitor is allowed to visit Hayes modem
// Zoom modem used with Unix configurator.
🔍类图
Acyclic Visitor
🔍适用场景
- 需要在现有层次结构中添加新功能而无需更改或影响该层次结构时。
- 当某些功能在层次结构上运行,但不属于层次结构本身时。 例如 ConfigureForDOS / ConfigureForUnix / ConfigureForX问题。
- 当您需要根据对象的类型对对象执行非常不同的操作时。
- 当访问的类层次结构将经常使用元素类的新派生进行扩展时。
- 当重新编译,重新链接,重新测试或重新分发派生元素非常昂贵时。
🔍结论
好处:
- 类层次结构之间没有依赖关系循环。
- 如果添加了新访客,则无需重新编译所有访客。
- 如果类层次结构具有新成员,则不会导致现有访问者中的编译失败。
坏处:
- 通过证明它可以接受所有访客,但实际上仅对特定访客感兴趣,从而违反Liskov的替代原则
- 必须为可访问的类层次结构中的所有成员创建访问者的并行层次结构。
🔍扩展:
Liskov的替代原则
1.子类必须保留父类的行为
子类应该继承并保持父类的所有属性和方法,以确保它们在同样的上下文中能够正常工作。
2.子类可以扩展父类的行为
子类可以在不破坏父类原有行为的前提下,添加新的方法或属性来扩展功能。
3.子类覆盖父类方法时遵循约定
如果子类需要覆盖(重写)父类的方法,那么子类的方法参数、返回类型和异常处理等要与父类方法保持一致,以确保可以无缝替换。
4.避免破坏类的不变性
如果父类拥有某些不变性质或约束,子类也应当遵守这些约束,不应该破坏这些约定。
遵循里氏替换原则有助于构建更健壮、灵活且易于维护的代码。如果违反了这一原则,可能会导致意外的行为,使代码变得不稳定或难以理解。通过正确地应用里氏替换原则,可以确保你的代码在继承体系中保持稳定和一致。