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