开发者

Dynamic class generation in CoffeeScript

开发者 https://www.devze.com 2023-04-07 12:37 出处:网络
What is the best way to dynamically create classes in CoffeeScript, in order to later instantiate objects of them?

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.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号