* Fix languages * Missed change for version number * Add language field for presentation * Revert change in README for double buffer Co-authored-by: Jackie Nim <=>
162 lines
4.6 KiB
Markdown
162 lines
4.6 KiB
Markdown
---
|
|
layout: pattern
|
|
title: Acyclic Visitor
|
|
folder: acyclic-visitor
|
|
permalink: /patterns/acyclic-visitor/
|
|
categories: Behavioral
|
|
language: en
|
|
tags:
|
|
- Extensibility
|
|
---
|
|
|
|
## Intent
|
|
|
|
Allow new functions to be added to existing class hierarchies without affecting those hierarchies, and without creating
|
|
the troublesome dependency cycles that are inherent to the GoF Visitor Pattern.
|
|
|
|
## Explanation
|
|
|
|
Real world example
|
|
|
|
> We have a hierarchy of modem classes. The modems in this hierarchy need to be visited by an external algorithm based
|
|
> on filtering criteria (is it Unix or DOS compatible modem).
|
|
|
|
In plain words
|
|
|
|
> Acyclic Visitor allows functions to be added to existing class hierarchies without modifying the hierarchies.
|
|
|
|
[WikiWikiWeb](https://wiki.c2.com/?AcyclicVisitor) says
|
|
|
|
> The Acyclic Visitor pattern allows new functions to be added to existing class hierarchies without affecting those
|
|
> hierarchies, and without creating the dependency cycles that are inherent to the GangOfFour VisitorPattern.
|
|
|
|
**Programmatic Example**
|
|
|
|
Here's the `Modem` hierarchy.
|
|
|
|
```java
|
|
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");
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
Next we introduce the `ModemVisitor` hierarchy.
|
|
|
|
```java
|
|
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.");
|
|
}
|
|
}
|
|
```
|
|
|
|
Finally, here are the visitors in action.
|
|
|
|
```java
|
|
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);
|
|
```
|
|
|
|
Program output:
|
|
|
|
```
|
|
// 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.
|
|
```
|
|
|
|
## Class diagram
|
|
|
|

|
|
|
|
## Applicability
|
|
|
|
This pattern can be used:
|
|
|
|
* When you need to add a new function to an existing hierarchy without the need to alter or affect that hierarchy.
|
|
* When there are functions that operate upon a hierarchy, but which do not belong in the hierarchy itself. e.g. the ConfigureForDOS / ConfigureForUnix / ConfigureForX issue.
|
|
* When you need to perform very different operations on an object depending upon its type.
|
|
* When the visited class hierarchy will be frequently extended with new derivatives of the Element class.
|
|
* When the recompilation, relinking, retesting or redistribution of the derivatives of Element is very expensive.
|
|
|
|
## Consequences
|
|
|
|
The good:
|
|
|
|
* No dependency cycles between class hierarchies.
|
|
* No need to recompile all the visitors if a new one is added.
|
|
* Does not cause compilation failure in existing visitors if class hierarchy has a new member.
|
|
|
|
The bad:
|
|
|
|
* Violates [Liskov's Substitution Principle](https://java-design-patterns.com/principles/#liskov-substitution-principle) by showing that it can accept all visitors but actually only being interested in particular visitors.
|
|
* Parallel hierarchy of visitors has to be created for all members in visitable class hierarchy.
|
|
|
|
## Related patterns
|
|
|
|
* [Visitor Pattern](https://java-design-patterns.com/patterns/visitor/)
|
|
|
|
## Credits
|
|
|
|
* [Acyclic Visitor by Robert C. Martin](http://condor.depaul.edu/dmumaugh/OOT/Design-Principles/acv.pdf)
|
|
* [Acyclic Visitor in WikiWikiWeb](https://wiki.c2.com/?AcyclicVisitor)
|