What is the best way to dynamically create classes in CoffeeScript, in order to later instantiate objects of them?
I have found ways to do it, but I am not sure if there is maybe an even better (or simpler) way to achieve it. Please let me know your thoughts on my code.
Let's start with simple non-dynamic classes:
class Animal
constructor: (@name) ->
speak: ->
alert "#{@name} says #{@sound}"
class Cat extends Animal
constructor: (@name) ->
@sound = "meow!"
garfield = new Cat "garfield"
garfield.speak()
As expected, garfield says meow!
But now we want to dynamically generate classes for more animals, which are defined as follows:
animalDefinitions = [
kind: 'Mouse'
sound: 'eek!'
,
kind: 'Lion'
sound: 'roar!'
]
The first naive attempt fails:
for animal in animalDefinitions
animal.class = class extends Animal
constructor: (@name) ->
@sound = animal.sound
mutant = new animalDefinitions[0].class "mutant"
mutant.speak()
The animal we just created, mutant
, should be a mouse. However, it says roar! This is because animal.sound only gets evaluated when we instantiate the class. Luckily, from JavaScript we know a proven way to solve this: a closure:
for animal in animalDefinitions
makeClass = (sound) ->
class extends Animal
constructor: (@name) ->
@sound = sound
animal.class = makeClass(animal.sound)
mickey = new animalDefinitions[0].class "mickey"
mickey.speak()
simba = new animalDefinitions[1].class "simba"
simba.speak()
Now it works as desired, mickey mouse says eek! and simba the lion says roar! But it looks somewhat complicated already. I am wondering if there is an easier way to achieve this result, maybe by accessing the prototype directly. Or am I completely on the wrong track?
Since
sound
is a default value for an Animal instance, you can set it as a property on class definition:then
If you want
sound
to be overridable, you can doFor your immediate problem (which is that the closure in the loop does not capture the current value, but the latest one), there is the
do
construct:I somehow expected CoffeeScript to take care of that automatically, since this is a common error in JavaScript, but at least with
do
there is a concise way to write it.