jeudi 25 janvier 2018

Vistor pattern to visit super classes, interfaces and canceling decent

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