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
Drunkard
andMummy
.
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:
Ninja
really only has one move, so it doesn't really have an "alternate move." Thus,Enemy
is 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?