avoid instanceof in Java

前端 未结 9 2259
清歌不尽
清歌不尽 2021-02-12 20:40

I have been told at some stage at university (and have subsequently read in upteen places) that using instanceof should only be used as a \'last resort\'. With this

相关标签:
9条回答
  • 2021-02-12 20:48

    I learned about Visitor pattern in Compiler class at university, I think it might apply in your scenario. Consider code below:

    public class GameObjectVisitor {
    
        public boolean visit(GameObject1 obj1) { return true; }
        .
        .
        // one method for each game object
        public boolean visit(GameGroup obj1) { return true; }
    }
    

    And then you can put a method in GameObject interface like this:

    public interface GameObject {
    
        .
        .
        public boolean visit(GameObjectVisitor visitor);
    }
    

    And then each GameObject implements this method:

    public class GameGroup implements GameObject {
    
        .
        .
        .
        public boolean visit(GameObjectVisitor visitor) {
            visitor.visit(this);
        }
    }
    

    This is specially useful when you've complex inheritance hierarchy of GameObject. For your case your method will look like this:

    private void allocateUITweenManager() {
    
        GameObjectVisitor gameGroupVisitor = new GameObjectVisitor() {
            public boolean visit(GameGroup obj1) {
                obj1.setUITweenManager(mUITweenManager);
            }
        };
    
        for(GameObject go:mGameObjects){
          go.visit(gameGroupVisitor);
       }
    }
    
    0 讨论(0)
  • 2021-02-12 20:52

    I have another suggestion of a way to avoid instanceof.

    Unless you are using a generic factory, at the moment when you create a GameObject you know what concrete type it is. So what you can do is pass any GameGroups you create an observable object, and allow them to add listeners to it. It would work like this:

    public class Game {
        private void makeAGameGroup() {
            mGameObjects.add(new GameGroup(mUITweenManagerInformer));
        }
    
        private void allocateUITweenManager() {
            mUITweenManagerInformer.fire(mUITweenManager);
        }
    
        private class OurUITweenManagerInformer extends UITweenManagerInformer {
            private ArrayList<UITweenManagerListener> listeners;
    
            public void addUITweenManagerListener(UITweenManagerListener l) {
                listeners.add(l);
            }
    
            public void fire(UITweenManager next) {
                for (UITweenManagerListener l : listeners)
                    l.changed(next);
            }
        }
        private OurUITweenManagerInformer mUITweenManagerInformer = new OurUITweenManagerInformer();
    }
    
    public interface UITweenManagerInformer {
        public void addUITweenManagerListener(UITweenManagerListener l);
    }
    
    public interface UITweenManagerListener {
        public void changed(UITweenManager next);
    }
    

    What draws me to this solution is:

    • Because a UITweenManagerInformer is a constructor parameter to GameGoup, you cannot forget to pass it one, whereas with an instance method you might forget to call it.

    • It makes intuitive sense to me that information that an object needs (like the way a GameGroup needs knowledge of the current UITweenManager) should be passed as a constructor parameter -- I like to think of these as prerequisites for an object existing. If you don't have knowledge of the current UITweenManager, you shouldn't create a GameGroup, and this solution enforces that.

    • instanceof is never used.

    0 讨论(0)
  • 2021-02-12 20:58

    The reason why instanceof is discouraged is because in OOP we should not examine object's types from outside. Instead, the idiomatic way is to let object themselves act using overriden methods. In your case, one possible solution could be to define boolean setUITweenManager(...) on GameObject and let it return true if setting the manager was possible for a particular object. However if this pattern occurs in many places, the top-level classes can get quite polluted. Therefore sometimes instanceof is "lesser evil".

    The problem with this OPP approach is that each object must "know" all its possible use cases. If you need a new feature that works on your class hierarchy, you have to add it to the classes themselves, you can't have it somewhere separate, like in a different module. This can be solved in a general way using the visitor pattern, as others suggested. The visitor pattern describes the most general way to examine objects, and becomes even more useful when combined with polymorphism.

    Note that other languages (in particular functional languages) use a different principle. Instead of letting objects "know" how they perform every possible action, they declare data types that have no methods on their own. Instead, code that uses them examines how they were constructed using pattern matching on algebraic data types. As far as I know, the closest language to Java that has pattern matching is Scala. There is an interesting paper about how Scala implements pattern matching, which compares several possible approaches: Matching Objects With Patterns. Burak Emir, Martin Odersky, and John Williams.

    Data in object-oriented programming is organized in a hierarchy of classes. The problem of object-oriented pattern matching is how to explore this hierarchy from the outside. This usually involves classifying objects by their run-time type, accessing their members, or determining some other characteristic of a group of objects. In this paper we compare six different pattern matching techniques: object-oriented decomposition, visitors, type-tests/typecasts, typecase, case classes, and extractors. The techniques are compared on nine criteria related to conciseness, maintainability and performance. The paper introduces case classes and extractors as two new pattern-matching methods and shows that their combination works well for all of the established criteria.

    In summary: In OOP you can easily modify data types (like add subclasses), but adding new functions (methods) requires making changes to many classes. With ADT it's easy to add new functions, but modifying data types requires modifying many functions.

    0 讨论(0)
  • 2021-02-12 21:05

    You could declare setUITweenManager in GameObject with an implementation that does nothing.

    You could create an method that returns an iterator for all UITweenable instances in array of GameObject instances.

    And there are other approaches that effectively hide the dispatching within some abstraction; e.g. the Visitor or Adapter patterns.


    ... have I committed some cardinal sin of OOP somewhere along the line that has me using instanceof here?

    Not really (IMO).

    The worst problem with instanceof is when you start using it to test for implementation classes. And the reason that is particularly bad is that it makes it hard to add extra classes, etcetera. Here the instanceof UITweenable stuff doesn't seem to introduce that problem, because UITweenable seems to be more fundamental to the design.


    When you make these sorts of judgement, it is best to understand the reasons why the (supposedly) bad construct or usage is claimed to be bad. Then you look at you specific use-case and make up whether these reasons apply, and whether the alternatively you are looking at is really better in your use-case.

    0 讨论(0)
  • 2021-02-12 21:05

    You could use the mGameObjects container for when you need to do something on all game objects and keep a separate container only for GameGroup objects.

    This will use some more memory, and when you add/remove objects you have to update both containers, but it shouldn't be a noticeable overhead, and it lets you loop very efficiently through all the objects.

    0 讨论(0)
  • 2021-02-12 21:06

    The problem with this approach is that it doesn't usually appear at one place only in your code and thus makes it more or less painful to add another implementations of the interface in the future. Whether to avoid it depends on your consideration. Sometimes YAGNI can be applied an this is the most straightforward way.

    Alternatives had been suggested by others, for example the Visitor pattern.

    0 讨论(0)
提交回复
热议问题