I'm working on a variation of the standard visitor pattern with the following three requirements:
- For each node, all super classes of the node should be visited first
- For each class, all implemented interfaces should be visited first
- Each visit to a class or interface should be able to cancel the visit to subclasses/implementing classes
My question is twofold. 1) is this a known method or is there a similar pattern around with a different name? 2) are there any obvious improvements or problems with this approach?
I will here detail my approach with an abstract example.
The class hierarchy visited is a Collection
of ConcreteItems
. The ConcreteItem
implements a CrossCuttingConcern
and extends an AbstractItem
class.
class Collection implements Visitable {
public final List<ConcreteItem> concreteItems;
public Collection(ConcreteItem ...concreteItems) {
this.concreteItems = asList(concreteItems);
}
public Decent accept(Visitor visitor) {
return visitor.visit(this);
}
}
class AbstractItem implements Visitable {
public Decent accept(Visitor visitor) {
return visitor.visit(this);
}
}
interface CrossCuttingConcern {
default Decent acceptCrossCuttingConcern(Visitor visitor) {
return visitor.visit(this);
}
}
class ConcreteItem extends AbstractItem implements CrossCuttingConcern {
public Decent accept(Visitor visitor) {
// This will visit the abstract super type, interface, and concrete class in turn.
// If any of those returns Decent.STOP, then the remaining ones are not visited.
return Decent.allUntilStop(
() -> super.accept(visitor),
() -> this.acceptCrossCuttingConcern(visitor),
() -> visitor.visit(this)
);
}
}
Now the Visitor
and Visitable
implementations are modified to return a type called Decent
(yeah, maybe not the best name for it). A visit
method returns STOP if it wants the visitor to stop descending down the class hierarchy. i.e. if you only want to visit AbstractItems
you return Decent.STOP
from visit(AbstractItem)
.
interface Visitor {
Decent visit(Collection collection);
Decent visit(AbstractItem abstractItem);
Decent visit(CrossCuttingConcern interfaceItem);
Decent visit(ConcreteItem concreteItem);
}
interface Visitable {
Decent accept(Visitor visitor);
}
enum Decent {
STOP,
CONTINUE;
public static Decent allUntilStop(Supplier<Decent> ...fns) {
for (Supplier<Decent> fn : fns) {
if (fn.get() == STOP) {
return STOP;
}
}
return CONTINUE;
}
public static BinaryOperator<Decent> product() {
return (a, b) -> a == CONTINUE && b == CONTINUE ? CONTINUE : STOP;
}
}
Now the default visitor adapter implementation returns Decent.CONTINUE
and prints debugging information used in the example below.
class VisitorAdapter implements Visitor {
@Override
public Decent visit(Collection collection) {
System.out.println("visiting Collection: " + collection);
// iterate over all concrete items and return STOP if one of the visit(items) does so
return collection.concreteItems.stream()
.map(a -> a.accept(this))
.reduce(Decent.product())
.orElse(CONTINUE); // return CONTINUE if collection contains zero items
}
@Override
public Decent visit(AbstractItem abstractItem) {
System.out.println("visiting AbstractItem: " + abstractItem);
return CONT;
}
@Override
public Decent visit(CrossCuttingConcern interfaceItem) {
System.out.println("visiting CrossCuttingConcern: " + interfaceItem);
return CONT;
}
@Override
public Decent visit(ConcreteItem concreteItem) {
System.out.println("visiting ConcreteItem: " + concreteItem);
return CONT;
}
}
This example demonstrates the working requirements:
public static void main(String[] args) {
Collection collection = new Collection(new ConcreteItem(), new ConcreteItem());
System.out.println("Visit all");
new VisitorAdapter().visit(collection);
System.out.println("");
System.out.println("Stop at AbstractItem")
new VisitorAdapter() {
public Decent visit(AbstractItem abstractItem) {
super.visit(abstractItem);
return STOP;
}
}.visit(collection);
System.out.println("");
System.out.println("Stop at CrossCuttingConcern");
new VisitorAdapter() {
public Decent visit(CrossCuttingConcern interfaceItem) {
super.visit(interfaceItem);
return STOP;
}
}.visit(collection);
System.out.println("");
}
Providing the following output:
Visit all
visiting Collection: Collection@7f31245a
visiting AbstractItem: ConcreteItem@16b98e56
visiting CrossCuttingConcern: ConcreteItem@16b98e56
visiting ConcreteItem: ConcreteItem@16b98e56
visiting AbstractItem: ConcreteItem@7ef20235
visiting CrossCuttingConcern: ConcreteItem@7ef20235
visiting ConcreteItem: ConcreteItem@7ef20235
Stop at AbstractClass
visiting Collection: Collection@7f31245a
visiting AbstractItem: ConcreteItem@16b98e56
visiting AbstractItem: ConcreteItem@7ef20235
Stop at Interface
visiting Collection: Collection@7f31245a
visiting AbstractItem: ConcreteItem@16b98e56
visiting CrossCuttingConcern: ConcreteItem@16b98e56
visiting AbstractItem: ConcreteItem@7ef20235
visiting CrossCuttingConcern: ConcreteItem@7ef20235
So; does this look familiar, or is there a simpler way to achieve my requirements?
Aucun commentaire:
Enregistrer un commentaire