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 sa开发者_如何学Pythonys 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:
class Cat extends Animal
sound: 'meow!'
garfield = new Cat "garfield"
garfield.speak() # "garfield says meow!"
then
for animal in animalDefinitions
animal.class = class extends Animal
sound: animal.sound
mutant = new animalDefinitions[0].class "mutant"
mutant.speak() # "mutant says eek!"
If you want sound
to be overridable, you can do
class Animal
constructor: (@name, sound) ->
@sound = sound if sound?
speak: ->
console.log "#{@name} says #{@sound}"
For 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:
for animal in animalDefinitions
do (animal) ->
animal.class = class extends Animal
constructor: (@name) ->
@sound = animal.sound
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.
精彩评论