lundi 27 avril 2015

Using GetType/instanceof in C# vs. alternatives

I've come across a problem in a game I am making in C#. It's a simple tile based matching game, and the problem has come up for a power up I am trying to make:

Say we have basic tile types, circles squares and diamonds, which are all subclasses of Tile. Instead of having circles only match to circles, I tried to extract the "matches" behavior to an abstract Tile method: canMatchWith(Tile t). The Tiles also have two methods to add/remove Tiles they can match with.

So say we have a Circle tile in the middle of our game, and we have a powerup that says "Circle tiles can match with square tiles this turn". I would go through all of the Circle tiles and say circleTile.addCanMatchWith(typeof(Square)). Internally, we have a List canMatchWith.

Then later, I want to say "Circles can no longer match with squares" and simply say circleTile.removeCanMatchWith(typeOf(Square)).

This is my current solution, and it works great with no performance drawbacks that I've noticed (It's a tile based matching game, so these types are only evaluated once per 'move', not frame by frame). However, the voice in my head is telling me that this is a bad way to accomplish this behavior. So I have some alternatives:

  1. Enums... Each Tile could be composed with a Tiletype type variable. This would be initialized in the constructor and set to Type.SQUARE for squares, and so on. Then, each Tile would have a List canMatchWith, and the functionality is the same as my original implementation. Except in this case, it's a little trickier. Say I have some circle subclasses, oval and elipse. I want ovals to be able to match with ONLY squares, but elipses can match with all circles and not squares.

The problem here is redundancy, my enum would now have OVAL and ELIPSE as well, and the Elipse class would have (CIRCLE, OVAL, ELIPSE TileTypes) as types it can match with. This is completely redundant, I want to just say "Circle" which I could with the types. I suppose the Tiles could have TileType baseType and TileType actualType.

  1. Some form of behavior composition. Forget Tile subclasses, just give Tiles methods and an instance variable for List. Then, at runtime we can just say someTile.addCanMatch(new CircleMatchBehavior()). This seems silly, as I would have a bunch of classes just saying you can match with a particular shape.

In summary, what I am trying to accomplish is having multiple object types be able to interact with any number of different types. The question is, what should I be using for the Type. Is it okay to use GetType here? Enums? Or is there a better strategy someone would recommend? I'm trying to be as general as possible, these tiles should not have any hardcoded dependencies on other tiles, and must be able to change who they can interact with on the fly. Say I make a new Tile subclass, pentagon... well, Pentagons can match with Squares, Circles, and Pentagons. Easy with my implementation, but something is telling me this is a dirty OOP practice.

I feel like I have to use Types/Enums because I am not trying to say thisTile.addCanMatch(Tile someOtherObject). That is too specific, I want thisTile to be able to match with all tiles who are instances of a particular class.





Aucun commentaire:

Enregistrer un commentaire