1

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?

INDRAJITH EKANAYAKE
  • 3,894
  • 11
  • 41
  • 63
niboro14
  • 13
  • 3

2 Answers2

0

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()); 

    }
}
Miss Kitty
  • 162
  • 1
  • 3
  • 16
  • I was working on something like that but I got other problems.. I have to revise my design anyway. Thanks you ! – niboro14 Apr 13 '19 at 08:59
0

I think it's going to be hard to find just one answer to your question. It gets into design and intentions and there's always trade offs in that space, seldom definite answers.

However I think you should look at the Composite Pattern. The composite pattern essentially takes a group of objects that are different, and treats them in the same way. It does this by implementing all interfaces on all a higher level object, Component, so that all object inherit a common set of methods and attributes.

In your example, you could define GameObject as your Component, and implement both the Obstacle and Activatable interfaces. Also add some helper methods, like isObstacle and isActivatable so you can test without casting. Now you can have a visitor walk the list and only perform actions on certain objects, which is what I think you are after.

markspace
  • 10,621
  • 3
  • 25
  • 39
  • I'm gonna look at the Composite Pattern but i'm not sure i understand correctly. Does it make sense that GameObject implements both Obstacle and Activatable ? What if these two interface were implementing incompatibles behaviors ? And also is this good to use these helper ? (you know : "tell, don't ask") – niboro14 Apr 12 '19 at 16:20
  • I think its fine. It can be common to need to make compromises over what some might consider a "pure" design. I think most objects that one can interact with would also be obstacles (you can't walk through them). And I know many classes in the Java API use "isSomething" methods so I think those are fine too, if they are needed. – markspace Apr 12 '19 at 17:14