1

I have a class Character who has a list of Spell objects that he can cast.

abstract class Spell {
    name: string
    manaCost: number

    abstract cast() { ... }
}

class InvisibilitySpell extends Spell { ... }
class FireballSpell extends Spell { ... }

class Character {
    mana: number
    spells: Spell[]

    addSpell(spell: Spell) {
        this.spells.push(spell)
    }
}

Casting spells requires accessing the correct spell by index in the .spells[] array

let char = new Character()
char.addSpell(new InvisibilitySpell())
char.addSpell(new FireballSpell())
char.spells[0].cast() // casts Invisibility spell
char.spells[1].cast() // casts Fireball spell

To improve the ergonomics of the interface and implement mana requirements, I plan on adding a castSpell method to the Character class that will delegate to the correct spell object, but I am not sure what the best practice is for handling the query.

I could query by the string value of the .name

class Character {
    ...
    castSpell(spellName: string) {
        let spell = this.spells.find(spell => spell.name === spellName)
        if (spell && spell.manaCost <= this.mana) {
            spell.cast()
        }
    }
}
char.castSpell('Invisibility')

Or I could query by the type itself

class Character {
    ...
    castSpell(spellType: typeof Spell) {
        let spell = this.spells.find(spell => spell instanceof spellType)
        if (spell && spell.manaCost <= this.mana) {
            spell.cast()
        }
    }
}
import { InvisibilitySpell } from './spells/InvisibilitySpell'

char.castSpell(InvisibilitySpell)

What software design considerations or technical implications might there be for doing one over the other, and are there approaches even better than these two?

1
  • This is a very painful, and unidiomatic approach. Instance of tests are not reliable but using strings to differentiate makes the use of classes relatively redundant. How about some plain old objects with a kind: "fire" | "invisibility" | "whatever"; tag property that will let the language understand exactly what you're doing? Jul 13, 2023 at 1:39

1 Answer 1

1

Querying by type is bad(tm) because you are fighting against OOP, the idea of which is that you can call spell.Cast() and not have to know the type.

More concretely you might have to subclass say Fireball into FireballAtSea and FireballOnLand or something but not want to have that mapping hardcoded into your character class.

You could also change the array to a Map or hashmap type object for faster lookup.

1
  • spells: { [k: string]: Spell } has a much nicer type than spells: Spell[] if you want to lookup by name.
    – Caleth
    Apr 11, 2023 at 11:00

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.