Suppose I'm creating a game played on a 2D coordinate grid. The game has 3 types of enemies which all move in different ways:
Drunkard: moves using type 1 movement.Mummy: moves using type 1 movement, except when it's near the main character, in which case it will use type 2 movement.Ninja: moves using type 3 movement.
Here are the ideas I've come up with in organizing the class hierarchy:
Proposal 1
A single base class where each enemy is derived from:
abstract class Enemy: show() // Called each game tick update() // Called each game tick abstract move() // Called in update class Drunkard extends Enemy: move() // Type 1 movement class Mummy extends Enemy: move() // Type 1 + type 2 movement class Ninja extends Enemy: move() // Type 3 movement Problems:
- Violates DRY since code isn't shared between
DrunkardandMummy.
Proposal 2
Same as proposal 1 but Enemy does more:
abstract class Enemy: show() // Called each game tick update() // Called each game tick move() // Tries alternateMove, if unsuccessful, perform type 1 movement abstract alternateMove() // Returns a boolean class Drunkard extends Enemy: alternateMove(): return False class Mummy extends Enemy: alternateMove() // Type 2 movement if in range, otherwise return false class Ninja extends Enemy: alternateMove() // Type 3 movement and return true Problems:
Ninjareally only has one move, so it doesn't really have an "alternate move." Thus,Enemyis a subpar representation of all enemies.
Proposal 3
Extending proposal 2 with a MovementPlanEnemy.
abstract class Enemy: show() // Called each game tick update() // Called each game tick abstract move() // Called in update class MovementPlanEnemy: move() // Type 1 movement abstract alternateMove() class Drunkard extends MovementPlanEnemy: alternateMove() // Return false class Mummy extends MovementPlanEnemy: alternateMove() // Tries type 2 movement class Ninja extends Enemy: move() // Type 3 movement Problems:
- Ugly and possibly over-engineered.
Question
Proposal 1 is simple but has a lower level of abstraction. Proposal 3 is complex but has a higher level of abstraction.
I understand the whole thing about "composition over inheritance" and how it can solve this whole mess. However, I have to implement this for a school project which requires us to use inheritance. So given this restriction, what would be the best way to organize this class hierarchy? Is this just an example of why inheritance is inherently bad?
I guess since my restriction is that I have to use inheritance, I'm really asking the broader question: in general, when is it appropriate to introduce a new layer of abstraction at the cost of complicating the program architecture?