component-core.coffee | |
---|---|
Component.coffee is a minimal library for when classical inheritence isn't enough. Components may be known to you as traits, or mixins, or something else. Multiple inheritance, like in Python, is also somewhat similar. The idea is to make your code conform to the problem space, instead of trying to shoehorn the problem into some design pattern. Here's a concrete example. | |
Classical Inheritance | |
You're making a game. The class structure, using classical inheritance is shown below (arrows denote a parent→child relationship). You have objects which respond to gravity, like the player. You also have bullets, which kill, but don't respond to gravity. Bullet and Player share common code related to rendering and position, and everything is amazing. | |
Now you want to add missiles which respond to gravity. You could do this:
Bullets and missiles can share the code for killing, awesome. But there's a glaring issue here though: code for gravity is duplicated. You probably copy-pasted it, didn't you? Now any changes to gravity have to be done in two places simultaneously, yuck. | |
How about this?:
Gravity isn't repeated, so it's good now, right? Except that bullets now have code related to gravity. This is workable if that gravity code is never called, but that's rather crufty. It's also makes the code harder to read, since inspecting the inheritance chain would have you believe that bullets do react to gravity. | |
Components | |
Let's see how using components solve this problem. The following diagram shows the same code, but using components instead of classical inheritance:
Physics includes Base functionality, as in classical inheritance. But now Missile borrows functionality from both the Physics and Weapon components. Bullets can take just the Base and Weapons components, without Physics, and Player needs to include only the Physics component. | |
Let's see what this looks like in code. JavaScript follows, but in CoffeeScript, this looks like: | |
Or the same thing in JavaScript: | |
Code | |
This is the public interface to component.coffee. Parameters: Any number of object literals or components (constructors)
Note that instances of components have built-in | component = (components...)-> |
Create a base object to serve as the returned component's prototype | comp = new ComponentBase
comp.extend components... |
A constructor function is returned as the component, ensuring that
object instantiation from the component is fast via | F = comp.init ? ->
F.prototype = comp |
Give F the same extension interface as a | F.extend = -> ComponentBase::extend.apply(F.prototype, arguments)
return F |
Instances of | ComponentBase = -> |
Parameters: Any number of object literals or components (constructors) | ComponentBase::extend = (components...)->
for c in components |
Allows extension using both object literals and other components. | c = c.prototype ? c |
Copy all key/value pairs from given object to | for key, val of c
continue unless c.hasOwnProperty key
if this[key] and typeof val is 'function' and not /extend|super/.test key
old = this[key]
this[key] = val
this[key].super = old
else
this[key] = val
return null |
Use this by calling Parameters: Arguments to be passed on | ComponentBase::super = ->
@super.caller.super.apply(this, arguments) |
Export in case CommonJS is being used | if module?.exports?
module.exports = component
|