3fc0f90e01
This is an important feature to permit trait reuse without excessive subtyping---composition over inheritance. For example, consider that you have a `HttpPlainAuth` trait that adds authentication support to some transport layer. Without parameterized traits, you have two options: 1. Expose setters for credentials 2. Trait closure 3. Extend the trait (not yet supported) The first option seems like a simple solution: ```javascript Transport.use( HttpPlainAuth )() .setUser( 'username', 'password' ) .send( ... ); ``` But we are now in the unfortunate situation that our initialization procedure has changed. This, for one, means that authentication logic must be added to anything that instantiates classes that mix in `HttpPlainAuth`. We'll explore that in more detail momentarily. More concerning with this first method is that, not only have we prohibited immutability, but we have also made our code reliant on *invocation order*; `setUser` must be called before `send`. What if we have other traits mixed in that have similar conventions? Normally, this is the type of problem that would be solved with a builder, but would we want every configurable trait to return a new `Transport` instance? All that on top of littering the API---what a mess! The second option is to store configuration data outside of the Trait acting as a closure: ```javascript var _user, _pass; function setCredentials( user, pass ) { _user = user; _pass = pass; } Trait( 'HttpPlainAuth', { /* use _user and _pass */ } ) ``` There are a number of problems with this; the most apparent is that, in this case, the variables `_user` and `_pass` act in place of static fields---all instances will share that data, and if the data is modified, it will affect all instances; you are therefore relying on external state, and mutability is forced upon you. You are also left with an awkward `setCredentials` call that is disjoint from `HttpPlainAuth`. The other notable issue arises if you did want to support instance-specific credentials. You would have to use ease.js' internal identifiers (which is undocumented and subject to change in future versions), and would likely accumulate garbage data as mixin instances are deallocated, since ECMAScript does not have destructor support. To recover from memory leaks, you could instead create a trait generator: ```javascript function createHttpPlainAuth( user, pass ) { return Trait( { /* ... */ } ); } ``` This uses the same closure concept, but generates new traits at runtime. This has various implications depending on your engine, and may thwart future ease.js optimization attempts. The third (which will be supported in the near future) is prohibitive: we'll add many unnecessary traits that are a nightmare to develop and maintain. Parameterized traits are similar in spirit to option three, but without creating new traits each call: traits now support being passed configuration data at the time of mixin that will be passed to every new instance: ```javascript Transport.use( HttpPlainAuth( user, pass ) )() .send( ... ); ``` Notice now how the authentication configuration is isolated to the actual mixin, *prior to* instantiation; the caller performing instantiation need not be aware of this mixin, and so the construction logic can remain wholly generic for all `Transport` types. It further allows for a convenient means of providing useful, reusable exports: ```javascript module.exports = { ServerFooAuth: HttpPlainAuth( userfoo, passfoo ), ServerBarAuth: HttpPlainAuth( userbar, passbar ), ServerFooTransport: Transport.use( module.exports.ServerFooAuth ), // ... }; var module = require( 'foo' ); // dynamic auth Transport.use( foo.ServerFooAuth )().send( ... ); // or predefined classes module.ServerFooTransport().send( ... ); ``` Note that, in all of the above cases, the initialization logic is unchanged---the caller does not need to be aware of any authentication mechanism, nor should the caller care of its existence. So how do you create parameterized traits? You need only define a `__mixin` method: Trait( 'HttpPlainAuth', { __mixin: function( user, pass ) { ... } } ); The method `__mixin` will be invoked upon instantiation of the class into which a particular configuration of `HttpPlainAuth` is mixed into; it was named differently from `__construct` to make clear that (a) traits cannot be instantiated and (b) the constructor cannot be overridden by traits. A configured parameterized trait is said to be an *argument trait*; each argument trait's configuration is discrete, as was demonstrated by `ServerFooAuth` and `ServerBarAuth` above. Once a parameterized trait is configured, its arguments are stored within the argument trait and those arguments are passed, by reference, to `__mixin`. Since any mixed in trait can have its own `__mixin` method, this permits traits to have their own initialization logic without the need for awkward overrides or explicit method calls. |
||
---|---|---|
doc | ||
lib | ||
test | ||
tools | ||
.gitignore | ||
.mailmap | ||
COPYING | ||
Makefile.am | ||
README | ||
README.hacking | ||
README.md | ||
README.todo | ||
README.traits | ||
configure.ac | ||
index.js | ||
package.json.in |
README.md
GNU ease.js
GNU ease.js is a classical object-oriented framework for Javascript, intended to eliminate boilerplate code and "ease" the transition into JavaScript from other object-oriented languages.
Current support includes:
- Simple and intuitive class definitions
- Classical inheritance
- Abstract classes and methods
- Interfaces
- Traits as mixins
- Visibility (public, protected, and private members)
- Static and constant members
Documentation
Comprehensive documentation and examples are available on the GNU ease.js website and in its manual.
Bug Reports / Feature Requests
Please direct bug reports and feature requests to bug-easejs@gnu.org or the project page on Savannah.
Why Classical OOP in JavaScript?
GNU ease.js was created (historically) for a number of reasons:
- To "ease" object-oriented developers into JavaScript by providing a familiar environment.
- To provide the maintenance and development benefits of classical OOP.
- To provide features not included in the language, such as proper encapsulation through private/protected members, interfaces, traits, intuitive inheritance, and other conveniences.
- To encapsulate the hacks commonly used to perform the above tasks.
Many JS purists believe that classical object-oriented programming should be left out of JavaScript and that one should stick strictly to prototypal development. While the two are related (they are both object-oriented), they can be applied to different problem domains in order to achieve results that are more natural or intuitive to developers; GNU ease.js works seamlessly with existing prototypes, allowing the developer to choose whether or not they want to use "classes".
License
GNU ease.js is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
N.B.: Versions prior to 0.2.0 were released under the LGPLv3+. Upon becoming
a GNU project, it was relicensed under the GPLv3+ to help the FSF stand strong
in its fight against proprietary JavaScript. For more information, please see
the NEWS file (which can be built with make NEWS
).