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
instanceof
operator returntrue
only 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] = InterfaceThis 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 OtherInterfaceAll 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