1
0
Fork 0
easejs/lib
Mike Gerwitz 3fc0f90e01 Initial implementation of parameterized traits
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.
2014-07-27 01:54:30 -04:00
..
util FallbackSymbol now returns instance of self 2014-07-09 23:50:36 -04:00
warn Extracted warning handlers into their own prototypes 2014-06-11 23:42:20 -04:00
ClassBuilder.js Initial implementation of parameterized traits 2014-07-27 01:54:30 -04:00
FallbackMemberBuilder.js No longer using __dirname in requires 2014-04-20 21:11:38 -04:00
FallbackVisibilityObjectFactory.js No longer using __dirname in requires 2014-04-20 21:11:38 -04:00
MemberBuilder.js Extracted warning handlers into their own prototypes 2014-06-11 23:42:20 -04:00
MemberBuilderValidator.js [copyright] Copyright assignment to the FSF 2014-04-09 19:05:07 -04:00
MethodWrapperFactory.js [copyright] Copyright assignment to the FSF 2014-04-09 19:05:07 -04:00
MethodWrappers.js GNU ease.js and test cases now compile in strict mode 2014-05-04 22:17:28 -04:00
Trait.js Initial implementation of parameterized traits 2014-07-27 01:54:30 -04:00
VisibilityObjectFactory.js No longer using __dirname in requires 2014-04-20 21:11:38 -04:00
VisibilityObjectFactoryFactory.js No longer using __dirname in requires 2014-04-20 21:11:38 -04:00
class.js Visibility object is now encapsulated in symbol key 2014-07-09 00:14:25 -04:00
class_abstract.js No longer using __dirname in requires 2014-04-20 21:11:38 -04:00
class_final.js No longer using __dirname in requires 2014-04-20 21:11:38 -04:00
interface.js Visibility object is now encapsulated in symbol key 2014-07-09 00:14:25 -04:00
prop_parser.js Exposing keyword bit values and bitmasks 2014-04-20 02:40:36 -04:00
util.js Added Global prototype 2014-06-11 23:08:48 -04:00
version.js.in [copyright] Copyright assignment to the FSF 2014-04-09 19:05:07 -04:00
warn.js Extracted warning handlers into their own prototypes 2014-06-11 23:42:20 -04:00