Search code examples
javainterfacesubclassinstanceofvisitor-pattern

Using interface implemented by subclass from a superclass collection without instanceof


I'm coding a little 2D game, all the game elements are subclasses of GameObject. The class Game has a collection of GameObject. My problem is, when the player performs an action, I go through the collection of GameObject to find what's in front of the player and then I want to use ONLY methods of interfaces implemented by the subclasses of GameObject without using instanceof and casting.

Here is a (very) simplified class-diagram of the situation

I tried to implement the visitor pattern but I want the function visit() to take an Activable or an Obstacle as an argument and not a TV or a Wall.

Here a code example :

class Game {
    private ArrayList<GameObject> gameObjects;
    ...
    public void actionPerformed(...) {
        GameObject current;
        //find the focused Game object
        ...

        //What's easiest but I don't want
        if(gameObjects instanceOf Obstacle) {
            ((Obstacle) current).aMethod()
            ...
        } else if (gameObjects instanceOf Activable) {
            ((Activable) current).activate()
            ...
        }
        ...

        //What visitor allow me
        public void watchVisit(TV tv) {
            tv.watch();
        }
        public void hitVisit(Wall w) {
            //...
        }

        //What I want
        public void activableVisit(Activable a) {
            a.activate();
        }
        public void walkVisit(Obstacle o) {
            //...
        }
        ...
}

GameObject :

class GameObject {
    public void acceptWatch(Game g) {
        //Do nothing, only implemented in class TV
    }
    //Same with wall
    ...
}

TV :

class TV extends Item implements Activable {
    public void acceptWatch(Game g) {
        //this works if watchVisit take a "TV" but not if it's a "Activable"
        g.watchVisit(this);
    }
    public void watch() {
        ...
    }
    ...
}

How can I solve this problem?


Solution

  • Instead of making all of these individual methods for watchTV() or hitWall() in GameObject, make GameObject Abstract with any common variables on it (name, activatable, obstacle, etc) with one Abstract method called doButtonOneActivity().

    Then make your other Objects, like TV or Wall, extend GameObject and override the doButtonOneActivity() method with whatever that particular item would do when clicked.

    Now your Game class can just call doButtonOneActivity() on the GameObject and the object itself will figure out what it needs to do, without you having to manually manage that.

    I hope that helps!

    The Game:

    public class Game implements Serializable, IClusterable {
        private static final long serialVersionUID = 1L;
    
        private ArrayList<GameObject> gameObjects;
    
        public void actionPerformed(GameObject current) {
    
            // Let the object do whatever it's supposed to do on button press, in either case.
            current.doButtonOneActivity();
    
            if(current.isActivatable()){
                // Do whatever extra thing you need to do if this one is Activatable...
                System.out.println("Hey, this thing is activatable!");
            } else if (current.isObstacle()){
                // Do something an obstacle needs you to do
                System.out.println("Hey, this thing is an obstacle!");
            }
    
        }
    }
    

    The GameObject, which is Abstract.

    public abstract class GameObject implements Serializable, IClusterable {
        private static final long serialVersionUID = 1L;
    
        public String name;
        private boolean activatable;
        private boolean obstacle;
    
        public GameObject(String name, boolean activatable, boolean obstacle) {
            super();
            this.name = name;
            this.activatable = activatable;
            this.obstacle = obstacle;
        }
    
        public abstract void doButtonOneActivity();
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public boolean isActivatable() {
            return activatable;
        }
    
        public void setActivatable(boolean activatable) {
            this.activatable = activatable;
        }
    
        public boolean isObstacle() {
            return obstacle;
        }
    
        public void setObstacle(boolean obstacle) {
            this.obstacle = obstacle;
        }
    }
    

    The TV, which extends GameObject and fills in the method it needs to.

    public class TV extends GameObject implements Serializable, IClusterable {
        private static final long serialVersionUID = 1L;
    
        public TV(String name, boolean activatable, boolean obstacle) {
            super(name, activatable, obstacle);
        }
    
        @Override
        public void doButtonOneActivity() {
            if(isActivatable()){
                // do whatever I need to do as a TV when I am activated...
               }
               if (isObstacle()){
                // do whatever I need to do as a TV when I am activated as an obstacle...
               }
               System.out.println("I'm a TV and I was called. My name is: " + getName()); 
    
        }
    }
    

    The Wall, which extends GameObject and fills in the method it needs to.

    public class Wall extends GameObject implements Serializable, IClusterable {
        private static final long serialVersionUID = 1L;
    
        public Wall(String name, boolean activatable, boolean obstacle) {
            super(name, activatable, obstacle);
        }
    
        @Override
        public void doButtonOneActivity() {    
            if(isActivatable()){
             // do whatever I need to do as a Wall when I am activated...
            }
            if (isObstacle()){
             // do whatever I need to do as a Wall when I am activated as an obstacle...
            }
            System.out.println("I'm a wall and I was called. My name is: " + getName()); 
    
        }
    }