3

I am aware that there is a similar question to this here. It considered a more generic question of class specific behaviour than my question here.

Consider the following simple implementation of a Composite pattern:

interface Item {
    int getWeight();
}

class SimpleItem implements Item {
    private int weight;
    public int getWeight() {
        return weight;
    }
}

class Container implements Item {
    private List<Item> items;
    private int weight;
    public void add(Item item) {
        items.add(item);
    }
    public int getWeight() {
        return weight + items.stream().mapToInt(Item::getWeight).sum();
    }
}

Now consider how a user of Item can determine whether it is a container. For example a method is required in Container pushAdd that pushes an item down the hierarchy to a container that has no containers inside it. The container only knows about Items, it doesn't know if those items are Containers or SimpleItems or some other class that implements Item.

There are three potential solutions:

1. Using instance of and casting

public void pushAdd(Item item) {
    Optional<Container> childContainer = items.stream()
        .filter(item instanceof Container)
        .map(item -> (Container)item)
        .findAny();
    if (childContainer.isPresent()) {
        childContainer.get().pushAdd(item);
    } else {
        add(item);
    }
}

2. Implement is/as methods

public pushAdd(Item item) {
    Optional<Container> childContainer = items.stream()
        .filter(Item::isContainer)
        .map(Item::asContainer);
    ....
}

3. Visitor pattern (I've omitted the simple accept implementation).

interface ItemVisitor {
    default void visit(SimpleItem simpleItem) { throw ...}
    default void visit(Container container) { throw ... };
}

public pushAdd(Item item) {
    Optional<Container> childContainer = ... (using instanceOf);
    if (childContainer.isPresent()) {
        childContainer.get().accept(new ItemVisitor(item) {
            void visit(Container container) {
                container.pushAdd(item);
            }
        };
    } else {
        add(item);
    }
}

The first is evil because it uses instanceof and casting. The second is evil because it forces knowledge of Container into Item - and it gets a lot worse when other subclasses of item are created. The third doesn't help you know whether you can add to the Item before calling the visitor. You can catch the exception but that seems to be a misuse of exceptions to me: much better to have a way to check before visiting.

So my question is: is there another pattern I can use to avoid casts and instanceof without having to push knowledge of subclasses up the hierarchy?

Community
  • 1
  • 1
sprinter
  • 27,148
  • 6
  • 47
  • 78
  • As suggested in an answer for the linked question, the *Visitor* pattern can help out and goes naturally with the *Composite* pattern. Implementing it is quite some code to write, compared with a dirty little `instanceof`, though. – 5gon12eder Jan 09 '15 at 04:12
  • 2
    Also note that your example code doesn't implement the *Composite* pattern. Your `Item`s and `Container`s are not implementing any common interface. – 5gon12eder Jan 09 '15 at 04:14
  • @5gon12eder that's a good point - in reality I use the pattern correctly in my application using a common interface - I made Item a class here to simplify the question. I've edited the question to remove reference to the pattern. – sprinter Jan 09 '15 at 04:37
  • I would have hoped that you'd edit the code instead… I cannot make much sense of your question any more now. Your `Container` cannot possibly contain another `Container` because all it has is a list of `Item`s and an `Item` is never a `Container`. Put another way: Your `Container`s are flat and cannot be used to build trees. The only common ancestor of both is `Object`. So what is the problem? – 5gon12eder Jan 09 '15 at 19:08
  • I forgot to include the extends. I'll edit to make it a true composite so it is clearer. – sprinter Jan 09 '15 at 21:38
  • @5gon12eder I've added the visitor option as well (which are now a LOT nicer to use with default methods). I guess the question now comes down to how to use visitors without instanceof or catching exceptions. – sprinter Jan 09 '15 at 21:55
  • 1
    I've never heard a good argument for why the use of instanceof is inherently wrong or bad. Frankly, of your current options it is the simplest, most readable and least prone to design issues. (By the way, I would be interested in why people think instanceof is so dirty) – Rudi Kershaw Jan 09 '15 at 22:07
  • Why would a client need to add anything to the hierarchy without having a pointer to the `Container` it needs to add it to already? Does the client need to blindly insert an item? What if there are no containers in the hierarchy at all? What can the client do at that point? – Giovanni Botta Jan 09 '15 at 22:07
  • @Rudi I agree that in this case using `instanceof` is not too bad. However, using `instanceof` is a sign of a questionable design and error prone: if someone adds a different `Item` not handled in any of the `instanceof` cases then the code might breakwith no way for clients to recover or even worse, fail silently. – Giovanni Botta Jan 09 '15 at 22:09
  • @GiovanniBotta - Oh I see, so the reason using instanceof excessively is bad, is because it limits code to a set number of 'instance of' that were known when the code was originally written. Someone using said API could then create a new instance that was not accounted for. Thanks. – Rudi Kershaw Jan 09 '15 at 22:12
  • @Rudi Yeah I believe that is the main concern, that is, unless your hierarchy is hidden from clients, in which case you're the only one who can add new classes. In many cases you can obtain the same result by explicitly adding a method to the interface so clients are forced to override it. – Giovanni Botta Jan 09 '15 at 22:16
  • @GiovanniBotta I think I explained this in the question. Containers themselves have a list of Items - the container does not know if they are containers or items (which is the point of the Composite pattern). So how do you implement a method in container that pushes items to the bottom of the hierarchy? That method does not know anything about the items inside it except that they implement Item. – sprinter Jan 09 '15 at 22:17
  • 1
    As @GiovanniBotta says, if a client doesn't know for sure whether it's dealing with a `Container`, it shouldn't attempt to add anything to it. (Otherwise the client code is broken and *that* should be fixed instead of your interface.) The logic to find the leaf container should be encapsulated in the `Container` class. There, we can always be sure we *will* find a `Container`: either one of the `items` or – if none of them is a `Container` – then `this`. – 5gon12eder Jan 09 '15 at 22:17
  • @sprinter I guess in that case you can't really get away from using `instanceof`. I also don't understand how the visitor pattern will help you since you are still going to need to call `instanceof` to cast the `Item` to the correct type. The "tag" could work and be (somewhat) transparent to clients if you make the `isContainer` method package visible only. – Giovanni Botta Jan 09 '15 at 22:20
  • @5gon12eder could you post an answer with some code to demonstrate that? By implementing a member of container with a `addItemToBottom` method. I suspect you will find it's not as obvious as you think. – sprinter Jan 09 '15 at 22:21
  • @GiovanniBotta no Visitor does not need any casting. The point of it is that the visitor interface has a separate method for each class the visitor needs to deal with so it is dispatching to the correct method based on the type of argument passed to `visit`. – sprinter Jan 09 '15 at 22:22
  • 1
    @sprinter All you need to do is implementing DFS and finding the leaf containers with the highest depth. The problem is deciding which container to use if there is more than one container at the same level! – Giovanni Botta Jan 09 '15 at 22:22
  • @sprinter But you only have `Item`s: your code can't know at runtime which method to call without you explicitly telling it which one to call. – Giovanni Botta Jan 09 '15 at 22:23
  • @GiovanniBotta yes it can: the `accept` method does exactly that. Have a look at the Visitor pattern on wikipedia. I omitted the `accept` method which is overriden in each implementation of Item to call the correct `visit` method. I did that just to keep the question focused but I might have unintentionally confused people who aren't aware of how visitors work. – sprinter Jan 09 '15 at 22:26
  • @GiovanniBotta please post your example code. I'm not concerned about finding the deepest node - I agree that is trivial and it wasn't the point of my example. The point was that the method needs to know if it's dealing with a container or an item. Use a simpler example if you wish - I'm interested in how you would implement it while keeping to your policy that 'if a client doesn't know for sure whether it's dealing with a container it shouldn't attempt to add anything to it.' – sprinter Jan 09 '15 at 22:28
  • I see. So your visitor only works for the subset of the class hierarchy that have the right method defined? And I implied that you would use reflection for the container to know what type the items are. – Giovanni Botta Jan 09 '15 at 22:34
  • @GiovanniBotta yes you've got it. Knowing about the hierarchy is a known disadvantage of Visitor. And as you say you still need `instanceof` to know whether you can use the Visitor - which is exactly why I asked this question! – sprinter Jan 09 '15 at 22:47
  • Well you don't need `instanceof` with the visitor at all. That's why it works with languages that don't have reflection like C++. – Giovanni Botta Jan 12 '15 at 14:37
  • I apologize for polluting the comments, but I'd like to go back to the design rationale behind the need for a visitor, just to see if there are alternatives in this case. – Giovanni Botta Jan 12 '15 at 15:18
  • Also, did you see [this](http://stackoverflow.com/questions/985960/alternative-to-the-visitor-pattern)? – Giovanni Botta Jan 12 '15 at 15:27
  • Thanks for the link - I agree visitor is overly complex in Java and clutters up code with implementation details. Good to avoid. I agree it doesn't need instanceof to work: my point in the question is that you still need instanceof to decide if you can use a visitor (to add items in this case). – sprinter Jan 12 '15 at 21:53

2 Answers2

1

I think I speak for any java person when I say that the visitor pattern is not exactly popular in java. So the above could be implemented like this (I will use interfaces here because in my experience they are more flexible):

interface Item { /* methods omitted */ }

interface SimpleItem extends Item { /* methods omitted */ }

interface ContainerItem extends Item { /* methods omitted */ }

// telling clients that this can throw an exception or not
// here is a whole different design question :)
interface ItemVisitor { void visit(Item i) /* throws Exception */; }

class MyVisitor implements ItemVisitor {
  void visit(Item i) {
    if (i instanceof SimpleItem) {
      // handle simple items
    } else if (i instanceof ContainerItem) {
      // handle containers using container specific methods
    } else {
      // either throw or ignore, depending on the specifications
    }
  }
}

The cost of instanceof is pretty low on the latest JVM so I would not worry about that too much, unless you can prove that the traditional visitor is considerably faster.

The readability and maintainability of the code is arguably identical, with a few differences. First of all, if a new interface is added to the hierarchy that does not modify existing visitors, those visitors don't need to be changed. On the other hand, it's easier to overlook a visitor that does need to be changed (especially in client code that you don't control) because the visitor does not explicitly require clients to do so, but, hey that's the nature of maintaining code and the general shortcoming of a design that requires visiting.

Another advantage of this pattern is that clients that don't need visiting don't need to worry about it (there's no accept method), i.e., a shorter learning curve.

Finally, I think this pattern is closer to the "purist" OOD in the sense that the interface hierarchy does not contain spurious methods (visit, canAddItems, etc.), i.e., there are no "tags" of sorts.

Giovanni Botta
  • 9,626
  • 5
  • 51
  • 94
  • Thanks for posting this. So your conclusion is that if `instanceof` can lead to avoid cluttering the code with several methods that are there pretty much to perform the same role then go ahead and use it. I can buy that. – sprinter Jan 12 '15 at 21:56
  • Yeah you can put it that way. Java is a language for lazy people! :) Jokes aside, java is meant to be as "pure" as it gets when it comes to OOD (although not always succeeding). – Giovanni Botta Jan 12 '15 at 21:58
  • As a matter of interest I assume you disagree with the accepted answer at http://stackoverflow.com/questions/2750714/is-instanceof-considered-bad-practice-if-so-under-what-circumstances-is-instan That concludes that using legacy libraries is the only useful use case. – sprinter Jan 12 '15 at 21:58
  • I do agree with that answer. I think we should strive to avoid reflection in general. However, for your specific problem and when it comes to the visitor pattern, I think that using `instanceof` makes things a little clearer. I do believe if you need to add an operation that requires such complexity, there might be a better design choice higher in the abstraction for your class hierarchy. It would be useful to know what your use case is and what the specs and the API are. Better design always trumps good code. – Giovanni Botta Jan 12 '15 at 22:11
0

Well it seems from the fact that no one has posted an answer that there are no better options than the 3 I put up.

So I'll post my preferred solution and see if anyone can improve on it. In my view the best option is a combination of options 2 and 3. I don't think it's too evil to have a canAddItems member of Item - it's arguable that it's reasonable that implementers of Item tell you if you can add Items to them. But visitors seem like a good way of hiding the details of how to items are added.

So, fwiw this is my best compromise to the problem I posed. I'm still not 100% happy with it. In particular the visitor to add items will break if another class is implemented that can add items. But that's probably what you want because it's changing the semantics of pushAdd.

interface Item {

    int getWeight();

    void accept(ItemVisitor visitor);

    default boolean canAddItems() {
        return false;
    }

}

interface ItemVisitor {

    default void visit(SimpleItem simpleItem) {
        throw new IllegalArgumentException("ItemVisitor does not accept SimpleItem");
    }

    default void visit(Container container) {
        throw new IllegalArgumentException("ItemVisitor does not accept Container");
    }
}

class SimpleItem implements Item {

    private int weight;

    public int getWeight() {
        return weight;
    }

    public void accept(ItemVisitor visitor) {
        visitor.visit(this);
    }
}

class Container implements Item {

    private List<Item> items;
    private int weight;

    public void add(Item item) {
        items.add(item);
    }

    public int getWeight() {
        return weight + items.stream().mapToInt(Item::getWeight).sum();
    }

    public void accept(ItemVisitor visitor) {
        visitor.visit(this);
    }

    public void pushAdd(Item item) {
        Optional<Item> child = items.stream().filter(Item::canAddItems).findAny();
        if (child.isPresent()) {
            child.get().accept(new ItemVisitor() {
                public void visit(Container container) {
                    container.add(item);
                }
            });
        } else {
            add(item);
        }
    }

}
sprinter
  • 27,148
  • 6
  • 47
  • 78
  • That looks good. I have but one suggestion. Depending on the specific design, it might make sense to write the `ItemVisitor` in term of interfaces rather than concrete classes. You could for example have a `ContainerItemInterface` that has additional methods to iterate through contained items and size of the container so clients can implement their own if needed and you can easily change your implementations later as well. Thoughts? – Giovanni Botta Jan 12 '15 at 14:42
  • Also honestly if this was C++ this would be the only solution. However, any java person would probably stay away from the visitor pattern (no `accept` method) and use reflection instead. Same shortcomings (need to explicitly handle each class in the hierarchy), slightly less code to write, no risk to break clients if the visitor class needs to change. – Giovanni Botta Jan 12 '15 at 14:46