Visitor Pattern solution: few visitors have same interface but should work with different objects
Asked Answered
B

4

4

I have following class diagram (visitor pattern implementation):

enter image description here

Expected result:
1) WiredVisitor should visit only Router and WiredNetworkCard
2) WirelessVisitor should visit only Router and WirelessNetworkCard

So, my question is: How should I change design (or code) for achieving my expected result?

P.S. My current solution is to add following piece of code to each visit(card:INetworkCard) method in both visitors:

// in WiredVisitor
if (card.getClass.equals(WiredNetworkCard.class)){
    // do logic of visit method
}

// in WirelessVisitor
if (card.getClass.equals(WirelessNetworkCard.class)){
    // do logic of visit method
}
Blat answered 10/7, 2012 at 22:56 Comment(0)
S
3

In the spirit of the acyclic visitor pattern, separate your visitors into subclass specific ones. Note that you'll still need the type check, but it's contained to the type being visited:

Here are the visitor interfaces:

interface IVisitor {
}

interface IRouterVisitor extends IVisitor {
  void visit(Router router);
}

interface INetworkCardVisitor extends IVisitor {
}

interface IWirelessNetworkCardVisitor extends INetworkCardVisitor {
  void visit(WirelessNetworkCard card);
}

interface IWiredNetworkCardVisitor extends INetworkCardVisitor {
  void visit(WiredNetworkCard card);
}

The concrete visitors will look like this:

class WiredVisitor implements IWiredNetworkCardVisitor, IRouterVisitor  {
  // ...
}

class WirelessVisitor implements IWirelessNetworkCardVisitor, IRouterVisitor {
  // ...
}

And the visited objects:

interface INetworkElement {
  void accept(IVisitor visitor);
}

class Router implements INetworkElement {
  @Override
  public void accept(IVisitor visitor) {
    if (visitor instanceof IRouterVisitor) {
      ((IRouterVisitor)visitor).visit(this);
    }
  }
}

interface INetworkCard extends INetworkElement {}

class WiredNetworkCard implements INetworkCard {
  @Override
  public void accept(IVisitor visitor) {
    if (visitor instanceof IWiredNetworkCardVisitor) {
      ((IWiredNetworkCardVisitor)visitor).visit(this);
    }
  }
}

class WirelessNetworkCard implements INetworkCard {
  @Override
  public void accept(IVisitor visitor) {
    if (visitor instanceof IWirelessNetworkCardVisitor) {
      ((IWirelessNetworkCardVisitor)visitor).visit(this);
    }
  }
}

In those type checks, you can also throw an error if the type is not the expected one, depending on what you'd like to happen in that case.

Shilashilha answered 11/7, 2012 at 17:14 Comment(1)
I believe this is still cyclic Router <--> IRouterVisitorMerry
Y
0

You could do this for example:

public interface IVisitor<T extends INetworkCard> {
   public void visit( T card );
}

And then you'd define your visitors as:

public class WiredVisitor implements IVisitor<WiredNetworkCard> {
...
}


public class WirelessVisitor implements IVisitor<WirelessNetworkCard> {
...
}

This may not be the textbook solution but it's very clean and very readable.

Yahweh answered 10/7, 2012 at 23:0 Comment(3)
hi biziclop, thanks for answer. But what about method accept(v:IVisitor), how it should look like?Blat
This is called the acyclic visitor pattern (pdf), but I wonder how this generic version would play with type erasure in Java...Ornithorhynchus
This only works in C#. Doesn't work in Java due to type erasure.Fondafondant
J
0

I think the problem here is not with the Visitor but when the Client calls accept on WiredNetworkCard by passing a WirelessVisitor which is a bug. It should be handled then by raising an exception at that point in time.

But all this is happening at runtime and biziclop's solution handles it at compile time itself. But what I don't like about biziclop's solution is that WiredVisitor which implements IVisitor<WiredNetworkCard> will also have visit(router:Router) method which is in no way related to WiredNetworkCard

Jointly answered 11/7, 2012 at 6:31 Comment(1)
Yes, that is a concern of mine too. But seeing as Router is visited by both, you should have a separate public void visit( Router r ) to deal with that.Yahweh
D
0

you can just define some interfaces and slap one on each of the sets of elements that you want it to visit...the advantages of doing it this way are:

  • it is type safe
  • no runtime checks (since they're not needed); no runtime exceptions

here is an implementation of what i'm talking about for your example:

  • visitors:

    interface  Wired {
        <R> R accept(Visitor<R> v);
    
        interface Visitor<R> {
            R visit(Router router);
    
            R visit(WiredNetworkCard wiredNetworkCard);
        }
    }
    
    interface Wireless {
        <R> R accept(Visitor<R> v);
    
        interface Visitor<R> {
            R visit(Router router);
    
            R visit(WirelessNetworkCard wirelessNetworkCard);
        }
    }
    
  • elements:

    class Router implements Wired, Wireless {
        @Override
        public <R> R accept(Wired.Visitor<R> v) {
            return v.visit(this);
        }
    
        @Override
        public <R> R accept(Wireless.Visitor<R> v) {
            return v.visit(this);
        }
    }
    
    class WiredNetworkCard implements Wired {
        @Override
        public <R> R accept(Wired.Visitor<R> v) {
            return v.visit(this);
        }
    }
    
    class WirelessNetworkCard implements Wireless {
        @Override
        public <R> R accept(Wireless.Visitor<R> v) {
            return v.visit(this);
        }
    }
    
Danedanegeld answered 27/6, 2018 at 21:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.