aboutsummaryrefslogtreecommitdiff
path: root/tools/addon-sdk-1.7/packages/api-utils/docs/base.md
blob: 7a694f2bebacf3e337d43d7c0db7e917e76dbdcf (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
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

### Inheritance ###

Doing [inheritance in JavaScript](https://developer.mozilla.org/en/Introduction_to_Object-Oriented_JavaScript)
is both verbose and painful. Reading or writing such code requires requires
sharp eye and lot's of discipline, mainly due to code fragmentation and lots of
machinery exposed:

    // Defining a simple Class
    function Dog(name) {
      // Classes are for creating instances, calling them without `new` changes
      // behavior, which in majority cases you need to handle, so you end up
      // with additional boilerplate.
      if (!(this instanceof Dog)) return new Dog(name);

      this.name = name;
    };
    // To define methods you need to make a dance with a special 'prototype'
    // property of the constructor function. This is too much machinery exposed.
    Dog.prototype.type = 'dog';
    Dog.prototype.bark = function bark() {
      return 'Ruff! Ruff!'
    };

    // Subclassing a `Dog`
    function Pet(name, breed) {
      // Once again we do our little dance 
      if (!(this instanceof Pet)) return new Pet(name, breed);

      Dog.call(this, name);
      this.breed = breed;
    }
    // To subclass, you need to make another special dance with special
    // 'prototype' properties.
    Pet.prototype = Object.create(Dog.prototype);
    // If you want correct instanceof behavior you need to make a dance with
    // another special `constructor` property of the `prototype` object.
    Object.defineProperty(Pet.prototype, 'contsructor', { value: Pet });
    // Finally you can define some properties.
    Pet.prototype.call = function(name) {
      return this.name === name ? this.bark() : '';
    };

An "exemplar" is a factory for instances. Usually exemplars are defined as
(constructor) functions as in examples above. But that does not necessary has
to be the case. Prototype (object) can form far more simpler exemplars. After
all what could be more object oriented than objects that inherit from objects.

    var Dog = {
      new: function(name) {
        var instance = Object.create(this);
        this.initialize.apply(instance, arguments);
        return instance;
      },
      initialize: function initialize(name) {
        this.name = name;
      },
      type: 'dog',
      bark: function bark() {
        return 'Ruff! Ruff!'
      }
    };
    var fluffy = Dog.new('fluffy');


    var Pet = Object.create(Dog);
    Pet.initialize = function initialize(name, breed) {
      Dog.initialize.call(this, name);
      this.breed = breed;
    };
    Pet.call = function call(name) {
      return this.name === name ? this.bark() : '';
    };

While this small trick solves some readability issues, there are still more. To
address them this module exports `Base` exemplar with few methods predefined:

    var Dog = Base.extend({
      initialize: function initialize(name) {
        this.name = name;
      },
      type: 'dog',
      bark: function bark() {
        return 'Ruff! Ruff!'
      }
    });

    var Pet = Dog.extend({
      initialize: function initialize(name, breed) {
        Dog.initialize.call(this, name);
        this.breed = breed;
      },
      function call(name) {
        return this.name === name ? this.bark() : '';
      }
    });

    var fluffy = Dog.new('fluffy');
    dog.bark();                       // 'Ruff! Ruff!'
    Dog.isPrototypeOf(fluffy);        // true
    Pet.isPrototypeOf(fluffy);        // true

### Composition ###

Even though (single) inheritance is very powerful it's not always enough.
Sometimes it's more useful suitable to define reusable pieces of functionality
and then compose bigger pieces out of them:

    var HEX = Base.extend({
      hex: function hex() {
        return '#' + this.color
      }
    })

    var RGB = Base.extend({
      red: function red() {
        return parseInt(this.color.substr(0, 2), 16)
      },
      green: function green() {
        return parseInt(this.color.substr(2, 2), 16)
      },
      blue: function blue() {
        return parseInt(this.color.substr(4, 2), 16)
      }
    })

    var CMYK = Base.extend(RGB, {
      black: function black() {
        var color = Math.max(Math.max(this.red(), this.green()), this.blue())
        return (1 - color / 255).toFixed(4)
      },
      magenta: function magenta() {
        var K = this.black();
        return (((1 - this.green() / 255).toFixed(4) - K) / (1 - K)).toFixed(4)
      },
      yellow: function yellow() {
        var K = this.black();
        return (((1 - this.blue() / 255).toFixed(4) - K) / (1 - K)).toFixed(4)
      },
      cyan: function cyan() {
        var K = this.black();
        return (((1 - this.red() / 255).toFixed(4) - K) / (1 - K)).toFixed(4)
      }
    })

    // Composing `Color` prototype out of reusable components:
    var Color = Base.extend(HEX, RGB, CMYK, {
      initialize: function initialize(color) {
        this.color = color
      }
    })

    var pink = Color.new('FFC0CB')
    // RGB
    pink.red()        // 255
    pink.green()      // 192
    pink.blue()       // 203

    // CMYK
    pink.magenta()    // 0.2471
    pink.yellow()     // 0.2039
    pink.cyan()       // 0.0000

### Combining composition & inheritance ###

Also it's easy to mix composition with inheritance:

    var Pixel = Color.extend({
      initialize: function initialize(x, y, color) {
        Color.initialize.call(this, color)
        this.x = x
        this.y = y
      },
      toString: function toString() {
        return this.x + ':' + this.y + '@' + this.hex()
      }
    });

    var pixel = Pixel.new(11, 23, 'CC3399');
    pixel.toString()              // 11:23@#CC3399
    Pixel.isPrototypeOf(pixel)    // true

    // Pixel instances inhertis from `Color`
    Color.isPrototypeOf(pixel);   // true

    // In fact `Pixel` itself inherits from `Color`, remember just simple and
    // pure prototypal inheritance where object inherit from objects.
    Color.isPrototypeOf(Pixel);   // true

### Classes ###

Module exports `Class` function. `Class` takes argument of exemplar object
extending `Base` and returns `constructor` function that can be used for
simulating classes defined by given exemplar.

    var CPixel = Class(Pixel);
    var pixel = CPixel(11, 12, '000000');
    pixel instanceof CPixel     // true
    Pixel.prototypeOf(pixel);   // true

    // Use of `new` is optional, but possible.
    var p2 = CPixel(17, 2, 'cccccc');
    p2 instanceof CPixel      // true
    p2.prototypeOf(pixel);    // true