aboutsummaryrefslogtreecommitdiff
path: root/tools/addon-sdk-1.4/packages/api-utils/docs/light-traits.md
blob: 1257c2aceefe305a71fac2bd2122b6a221b88b94 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291

[Traits](http://en.wikipedia.org/wiki/Trait_%28computer_science%29) are a simple
mechanism for structuring object-oriented programs. They represent reusable and
composable building blocks of functionality that factor out the common
attributes and behavior of objects.

They are a more robust alternative to
[mixins](http://en.wikipedia.org/wiki/Mixins) and
[multiple inheritance](http://en.wikipedia.org/wiki/Multiple_inheritance),
because name clashes must be explicitly resolved and composition is commutative
and associative (i.e. the order of traits in a composition is irrelevant).

Use traits to share functionality between similar objects without duplicating
code or creating complex inheritance chains.

## Trait Creation ##

To create a trait, call the `Trait` constructor function exported by this
module, passing it a JavaScript object that specifies the properties of the
trait.

    let Trait = require('light-traits').Trait;
    let t = Trait({
      foo: "foo",
      bar: function bar() {
        return "Hi!"
      },
      baz: Trait.required
    });

Traits can both provide and require properties. A *provided* property is a
property for which the trait itself provides a value. A *required* property is a
property that the trait needs in order to function correctly but for which
it doesn't provide a value.

Required properties must be provided by another trait or by an object with a
trait. Creation of an object with a trait will fail if required properties are
not provided. Specify a required property by setting the value of the property
to `Trait.required`.

## Object Creation ##

Create objects with a single trait by calling the trait's `create` method. The
method takes a single argument, the object to serve as the new object's
prototype. If no prototype is specified, the new object's prototype will be
`Object.prototype`.

    let t = Trait({
      foo: 'foo',
      bar: 2
    });
    let foo1 = t.create();
    let foo2 = t.create({ name: 'Super' });

## Trait Composition ##

Traits are designed to be composed with other traits to create objects with the
properties of multiple traits. To compose an object with multiple traits, you
first create a composite trait and then use it to create the object. A composite
trait is a trait that contains all of the properties of the traits from which it
is composed. In the following example, MagnitudeTrait is a composite trait.

    let EqualityTrait = Trait({
      equal: Trait.required,
      notEqual: function notEqual(x) {
        return !this.equal(x)
      }
    });
    
    let ComparisonTrait = Trait({
      less: Trait.required,
      notEqual: Trait.required,
      greater: function greater(x) {
        return !this.less(x) && this.notEqual(x)
      }
    });
    
    let MagnitudeTrait = Trait.compose(EqualityTrait, ComparisonTrait);

<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xl="http://www.w3.org/1999/xlink" version="1.1" viewBox="-11 121 490 190" width="490px" height="190px">
  <defs>
    <marker orient="auto" overflow="visible" markerUnits="strokeWidth" id="SharpArrow_Marker" viewBox="-4 -4 10 8" markerWidth="10" markerHeight="8" color="black">
      <g>
        <path d="M 5 0 L -3 -3 L 0 0 L 0 0 L -3 3 Z" fill="currentColor" stroke="currentColor" stroke-width="1px"/>
      </g>
    </marker>
  </defs>
  <g stroke="none" stroke-opacity="1" stroke-dasharray="none" fill="none" fill-opacity="1">
    <g>
      <rect x="9" y="165.33334" width="141" height="14"/>
      <rect x="9" y="165.33334" width="141" height="14" stroke="black" stroke-width="1px"/>
      <text transform="translate(14 165.33334)" fill="black">
        <tspan font-family="Helvetica" font-size="12" font-weight="500" x="0" y="11" textLength="47.373047">notEqual</tspan>
      </text>
      <rect x="9" y="151.33334" width="141" height="14"/>
      <rect x="9" y="151.33334" width="141" height="14" stroke="black" stroke-width="1px"/>
      <text transform="translate(14 151.33334)" fill="red">
        <tspan font-family="Helvetica" font-size="12" font-weight="500" fill="red" x="0" y="11" textLength="29.361328">equal</tspan>
      </text>
      <rect x="9" y="137.33334" width="141" height="14"/>
      <rect x="9" y="137.33334" width="141" height="14" stroke="black" stroke-width="1px"/>
      <text transform="translate(14 137.33334)" fill="black">
        <tspan font-family="Helvetica" font-size="12" font-weight="bold" x="38.49707" y="11" textLength="54.00586">EqualityTrait</tspan>
      </text>
      <rect x="9" y="273" width="141" height="14"/>
      <rect x="9" y="273" width="141" height="14" stroke="black" stroke-width="1px"/>
      <text transform="translate(14 273)" fill="black">
        <tspan font-family="Helvetica" font-size="12" font-weight="500" x="0" y="11" textLength="38.021484">greater</tspan>
      </text>
      <rect x="9" y="259" width="141" height="14"/>
      <rect x="9" y="259" width="141" height="14" stroke="black" stroke-width="1px"/>
      <text transform="translate(14 259)" fill="red">
        <tspan font-family="Helvetica" font-size="12" font-weight="500" fill="red" x="0" y="11" textLength="47.373047">notEqual</tspan>
      </text>
      <rect x="9" y="245" width="141" height="14"/>
      <rect x="9" y="245" width="141" height="14" stroke="black" stroke-width="1px"/>
      <text transform="translate(14 245)" fill="red">
        <tspan font-family="Helvetica" font-size="12" font-weight="500" fill="red" x="0" y="11" textLength="21.339844">less</tspan>
      </text>
      <rect x="9" y="231" width="141" height="14"/>
      <rect x="9" y="231" width="141" height="14" stroke="black" stroke-width="1px"/>
      <text transform="translate(14 231)" fill="black">
        <tspan font-family="Helvetica" font-size="12" font-weight="bold" x=".15332031" y="11" textLength="112.67578">ComparisonTrait</tspan>
      </text>
      <rect x="317.75" y="235.5" width="141" height="14"/>
      <rect x="317.75" y="235.5" width="141" height="14" stroke="black" stroke-width="1px"/>
      <text transform="translate(322.75 235.5)" fill="black">
        <tspan font-family="Helvetica" font-size="12" font-weight="500" x="0" y="11" textLength="38.021484">greater</tspan>
      </text>
      <rect x="317.75" y="221.5" width="141" height="14"/> 
      <rect x="317.75" y="221.5" width="141" height="14" stroke="black" stroke-width="1px"/>
      <text transform="translate(322.75 221.5)" fill="red">
        <tspan font-family="Helvetica" font-size="12" font-weight="500" fill="red" x="0" y="11" textLength="21.339844">less</tspan>
      </text>
      <rect x="317.75" y="207.5" width="141" height="14"/>
      <rect x="317.75" y="207.5" width="141" height="14" stroke="black" stroke-width="1px"/>
      <text transform="translate(322.75 207.5)" fill="black">
        <tspan font-family="Helvetica" font-size="12" font-weight="500" x="0" y="11" textLength="47.373047">notEqual</tspan>
      </text>
      <rect x="317.75" y="193.5" width="141" height="14"/>
      <rect x="317.75" y="193.5" width="141" height="14" stroke="black" stroke-width="1px"/>
      <text transform="translate(322.75 193.5)" fill="red">
        <tspan font-family="Helvetica" font-size="12" font-weight="500" fill="red" x="0" y="11" textLength="29.361328">equal</tspan>
      </text>
      <rect x="317.75" y="179.5" width="141" height="14"/>
      <rect x="317.75" y="179.5" width="141" height="14" stroke="black" stroke-width="1px"/>
      <text transform="translate(322.75 179.5)" fill="black">
        <tspan font-family="Helvetica" font-size="12" font-weight="bold" x="31.83789" y="11" textLength="67.32422">MagnitudeTrait</tspan>
      </text>
      <path d="M 150 248.83887 L 158.89999 248.83887 L 235.9 248.83887 L 235.9 224.66113 L 308.85 224.66113 L 310.85 224.66113" marker-end="url(#SharpArrow_Marker)" stroke="black" stroke-linecap="butt" stroke-linejoin="miter" stroke-width="1px"/>
      <path d="M 150 171.15845 L 158.89999 171.15845 L 233.9 171.15845 L 233.9 201.6749 L 308.85 201.6749 L 310.85 201.6749" marker-end="url(#SharpArrow_Marker)" stroke="black" stroke-linecap="butt" stroke-linejoin="miter" stroke-width="1px"/>
    </g>
  </g>
</svg>

## Trait Resolution ##

Composite traits have conflicts when two of the traits in the composition
provide properties with the same name but different values (when compared using
the `===` strict equality operator). In the following example, `TC` has a
conflict because `T1` and `T2` both define a `foo` property:

    let T1 = Trait({
      foo: function () {
        // do something
      },
      bar: 'bar',
      t1: 1
    });
    
    let T2 = Trait({
      foo: function() {
        // do something else
      },
      bar: 'bar',
      t2: 2
    });
    
    let TC = Trait.compose(T1, T2);

Attempting to create an object from a composite trait with conflicts throws a
`remaining conflicting property` exception. To create objects from such traits,
you must resolve the conflict.

You do so by excluding or renaming the conflicting property of one of the
traits. Excluding a property removes it from the composition, so the composition
only acquires the property from the other trait. Renaming a property gives it a
new, non-conflicting name at which it can be accessed.

In both cases, you call the `resolve` method on the trait whose property you
want to exclude or rename, passing it an object. Each key in the object is the
name of a conflicting property; each value is either `null` to exclude the
property or a string representing the new name of the property.

For example, the conflict in the previous example could be resolved by excluding
the `foo` property of the second trait.

    let TC = Trait(T1, T2.resolve({ foo: null }));

It could also be resolved by renaming the `foo` property of the first trait to
`foo2`:

    let TC = Trait(T1.resolve({ foo: "foo2" }), T2);

When you resolve a conflict, the same-named property of the other trait (the one
that wasn't excluded or renamed) remains available in the composition under its
original name.

## Constructor Functions ##

When your code is going to create more than one object with traits, you may want
to define a constructor function to create them. To do so, create a composite
trait representing the traits the created objects should have, then define a
constructor function that creates objects with that trait and whose prototype is
the prototype of the constructor:

    let PointTrait = Trait.compose(T1, T2, T3);
    function Point(options) {
      let point = PointTrait.create(Point.prototype);
      return point;
    }

## Property Descriptor Maps ##

Traits are designed to work with the new object manipulation APIs defined in
[ECMAScript-262, Edition
5](http://www.ecma-international.org/publications/standards/Ecma-262.htm) (ES5).
Traits are also property descriptor maps that inherit from `Trait.prototype` to
expose methods for creating objects and resolving conflicts.

The following trait definition:

    let FooTrait = Trait({
      foo: "foo",
      bar: function bar() {
        return "Hi!"
      },
      baz: Trait.required
    });

Creates the following property descriptor map:

    {
      foo: {
        value: 'foo',
        enumerable: true,
        configurable: true,
        writable: true
      },
    
      bar: {
        value: function b() {
          return 'bar'
        },
        enumerable: true,
        configurable: true,
        writable: true
      },
    
      baz: {
        get baz() { throw new Error('Missing required property: `baz`') }
        set baz() { throw new Error('Missing required property: `baz`') }
      },
    
      __proto__: Trait.prototype
    }

Since Traits are also property descriptor maps, they can be used with built-in
`Object.*` methods that accept such maps:

    Object.create(proto, FooTrait);
    Object.defineProperties(myObject, FooTrait);

Note that conflicting and required properties won't cause exceptions to be
thrown when traits are used with the `Object.*` methods, since those methods are
not aware of those constraints. However, such exceptions will be thrown when the
property with the conflict or the required but missing property is accessed.

Property descriptor maps can also be used in compositions. This may be useful
for defining non-enumerable properties, for example:

    let TC = Trait.compose(
      Trait({ foo: 'foo' }),
      { bar: { value: 'bar', enumerable: false } }
    );

_When using property descriptor maps in this way, make sure the map is not the
only argument to `Trait.compose`, since in that case it will be interpreted as
an object literal with properties to be defined._