2
\$\begingroup\$

I am creating a game in Java that uses the DND model for a lot of its components. I've hit a roadblock when it comes to creating and implementing abilities. I have thought of two ways I could implement this system:

  • Using inheritance: Having separate classes that extend from the Ability class, each one specifying what kind of ability it is.
  • Using enums: Having an extra parameter in the Ability constructor that specifies what type of ability it is.

Below are the two versions of this system that I could implement. I would appreciate feedback on which one is makes more sense to keep.

OPTION 1: SEPERATE CLASSES

/**
* Checks if the player is high enough level to learn an ability
* Method is in `Player` class. This is the only relevent method
* that needs to be included from the `Player` class as it is the
* only method that deals with the `Ability` class.
*/
public void checkAbilityStatus() {
    switch(this.getLevel()) {
        case 1: this.abilities.add(new MeleeAbility("Attack", 0, this.weapon.getDamage(), (int)(this.weapon.getDamage() / 4))); break;
        case 3: this.abilities.add(new MeleeAbility("Slam", 2, this.weapon.getDamage() + this.getLevel(),(int)((this.weapon.getDamage() + this.getLevel()) / 4))); break;
        case 5: this.abilities.add(new Spell("Fireball", 5, this.weapon.getDamage() + this.getLevel(), (int)((this.weapon.getDamage() + this.getLevel()) / 4))); break;
        case 8: this.abilities.add(new AOEAbility("Thunder Clap", 3, this.weapon.getDamage() + this.getLevel(), (int)((this.weapon.getDamage() + this.getLevel()) / 4))); break;
        case 10: this.abilities.add(new Spell("Pyroblast", 10, this.weapon.getDamage() * this.getLevel(), (int)((this.weapon.getDamage() + this.getLevel()) / 4))); break;
    }
}

public class Ability {

    /* Private instance variables not shown */

    public Ability(String name, int manaCost, int damage, int damageFlux) {
        this.name = name;
        this.manaCost = manaCost;
        this.damage = damage;
        this.damageFlux = damageFlux;

        this.minDamage = this.damage - this.damageFlux;
        this.maxDamage = this.damage + this.damageFlux;

        this.random = new Random();
    }

    /* Misc getters/setters and toString method not shown */

}

class Spell extends Ability {

    public Spell(String name, int manaCost, int damage, int damageFlux) {
        super(name, manaCost, damage, damageFlux);
    }

}

class MeleeAbility extends Ability {

    public MeleeAbility(String name, int manaCost, int damage, int damageFlux) {
        super(name, manaCost, damage, damageFlux);
    }

}

class RangedAbility extends Ability {

    public RangedAbility(String name, int manaCost, int damage, int damageFlux) {
        super(name, manaCost, damage, damageFlux);
    }

}

class AOEAbility extends Ability {

    public AOEAbility(String name, int manaCost, int damage, int damageFlux) {
        super(name, manaCost, damage, damageFlux);
    }

}

OPTION 2: USING ENUMS

/**
* Checks if the player is high enough level to learn an ability
* Method is in `Player` class. This is the only relevent method
* that needs to be included from the `Player` class as it is the
* only method that deals with the `Ability` class.
*/
public void checkAbilityStatus() {
    switch(this.getLevel()) {
        case 1: this.abilities.add(new Ability("Attack", 0, this.weapon.getDamage(), (int)(this.weapon.getDamage() / 4)), AbilityType.MELEE); break;
        case 3: this.abilities.add(new Ability("Slam", 2, this.weapon.getDamage() + this.getLevel(),(int)((this.weapon.getDamage() + this.getLevel()) / 4)), AbilityType.MELEE); break;
        case 5: this.abilities.add(new Ability("Fireball", 5, this.weapon.getDamage() + this.getLevel(), (int)((this.weapon.getDamage() + this.getLevel()) / 4)), AbilityType.SPELL); break;
        case 8: this.abilities.add(new Ability("Thunder Clap", 3, this.weapon.getDamage() + this.getLevel(), (int)((this.weapon.getDamage() + this.getLevel()) / 4)), AbilityType.AOE); break;
        case 10: this.abilities.add(new Ability("Pyroblast", 10, this.weapon.getDamage() * this.getLevel(), (int)((this.weapon.getDamage() + this.getLevel()) / 4)), AbilityType.SPELL); break;
    }
}

public enum AbilityType {
    MELLE, SPELL, RANGED, AOE
}

public class Ability {

    /* Private instance variables not shown */

    public Ability(String name, int manaCost, int damage, int damageFlux, AbilityType abilityType) {
        this.name = name;
        this.manaCost = manaCost;
        this.damage = damage;
        this.damageFlux = damageFlux;
        this abilityType = abilityType;

        this.minDamage = this.damage - this.damageFlux;
        this.maxDamage = this.damage + this.damageFlux;

        this.random = new Random();
    }

    /* Misc getters/setters and toString method not shown */

}
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

As your code stands, I would opt for OPTION 2: USING ENUMS.

Consider inheritance only when the derived classes have specific state/operations that are not compatible with other derived classes. Don't abuse inheritance when a simple property (in this case of an enum type) allows to distinguish a certain feature amongst instances of a given type.

Example where inheritance is justified:

class RangedAbility extends Ability {

    public RangedAbility(String name, int manaCost, int damage, int damageFlux) {
        super(name, manaCost, damage, damageFlux);
    }

    // specific operations for RangedAbility

    public void CastRangedProjectile() { /* ... */ }
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.