There are two main problems with Java-style interfaces in CoffeeScript:
- Every class in Java can have only one parent but implement as many interfaces as you like. CoffeeScript classes can extend only one class. That's it. If CoffeeScript allowed multiple inheritance everything would be as easy as I said before. However, it doesn't.
- Every class in Java is instance of all the interfaces it implements. In CoffeeScript
instanceofoperator returntrueonly for classes in objects' prototype chain. No exclusions, no possibility to override.
What we need to get from interface in CoffeeScript are its' static fields and nested classes. We do not need any instance method declarations to copy.
First, I add a static method to
Function:keywords = ["__included__", "instanceof"]
Function::includes = (Interfaces...) ->
for Interface in Interfaces
for key, value of Interface when key not in keywords
@[key] = value
if !@__included__? then @__included__ = {}
if Interface.__included__?
for key, value of Interface.__included__ when key not in keywords
@__included__[key] = value
@__included__[Interface.name] = Interface
This method takes any number of classes as arguments and does two things with each one: copies all its' static fields (except two reserved keywords) and adds it and all its' included classes to __included__ field. This field is then used in instanceof method of Object prototype, the second function I add:Object.prototype.instanceof = (Class) -> (this instanceof Class) || this.constructor.__included__?[Class.name]?Well, to be honest, I'm not sure that this is the best solution. It is not CoffeeScript-style at all, as it works directly with
Function and prototypes. Also it affects the basic language construction. However it's the best I can do after hours of reading tons of manuals, posts and code samples.Look at the test:
class ParentInterface
@CONSTANT_ONE: 1
class NestedClass
value: undefined
constructor: (@value) ->
@NestedClass: NestedClass
class ChildInterface
@includes ParentInterface
@CONSTANT_TWO: 2
class OtherInterface
@CONSTANT_THREE: 3
class ParentClass
value: undefined
constructor: (@value) ->
class ChildClass extends ParentClass
@includes ChildInterface, OtherInterface
getNestedClassInstance: (value) ->
return new @constructor.NestedClass value
console.assert ChildClass.CONSTANT_ONE == 1
console.assert ChildClass.CONSTANT_TWO == 2
console.assert ChildClass.CONSTANT_THREE == 3
inst = new ChildClass 10
console.assert inst.value == 10
nested = inst.getNestedClassInstance 20
console.assert nested.value == 20
console.assert inst.instanceof ChildClass
console.assert inst.instanceof ParentClass
console.assert inst.instanceof ChildInterface
console.assert inst.instanceof ParentInterface
console.assert inst.instanceof OtherInterface
All these assertions should pass. The only thing I want to add is about little trick with nested classes: look at the line 6. Without this line nested class will not be available for any class that extends or includes the outer class. So do not forget to add it if you want proper inheritance.
Using Object:: is evil because properties will be defined as enumerable. Need to use Object.defineProperty
ReplyDelete