This is the form of constructor which Douglas Crockford suggests in his book "How Javascript works" and in his lectures.
const constructor_x = function (spec) {
let { a } = spec // private state
// methods can modify private state
const method_x = function () { a = '...' }
// methods are exposed as public interface
return Object.freeze({ method_x })
}
He suggests the following pattern for composition:
const constructor_y = function (spec) {
let { b } = spec // private state
// we can call other constructor and borrow functionality
const { method_x } = constructor_x(spec)
// we define new methods
const method_y = function () { b = '...' }
// we can merge borrowed and new functionality
// and expose everything as public interface
return Object.freeze({ method_x, method_y })
}
So here we see how to compose constructor_x and constructor_y. But my problem with this example (and all examples used when this pattern is presented) is that constructor_x and constructor_y make separate private states. constructor_x works on variable a, while constructor_y works on variable b. What if we want our constructors to share state? What if constructor_y also wants to work with variable a?
const constructor_y = function (spec) {
let { a, b } = spec
const { method_x } = constructor_x(spec)
const method_y = function () { b = '...' }
const method_z = function () {
// we may want to read `a` and maybe write to it
a = '...'
}
return Object.freeze({ method_x, method_y, method_z })
}
Of course this doesn't achieve what I want because a which constructor_y sees is not the same a constructor_x sees. If I used this, I could have achieved that maybe like so:
const constructor_x = function (spec) {
return {
_a: spec.a,
method_x () { this._a = '...' }
}
}
const constructor_y = function (spec) {
return {
...constructor_x(spec),
_b: spec.b
method_y () { this._b = '...' },
method_z () { this._a = '...' }
}
}
But here I have lost privacy of variables _a and _b since they are attached to instance and are accessible just like methods. The best I can do is add underscore prefix which Douglas Crockford calls a sign of incompetence. I also lost instance's rigidness because it can no longer be frozen.
I could have exposed accessors for variable a in constructor_x like so:
const constructor_x = function (spec) {
let { a } = spec // private state
// methods can modify private state
const method_x = function () { a = '...' }
// methods are exposed as public interface
return Object.freeze({
method_x,
get_a () { return a },
set_a (val) { a = val }
})
}
const constructor_y = function (spec) {
let { a, b } = spec
const { method_x, get_a, set_a } = constructor_x(spec)
const method_y = function () { b = '...' }
const method_z = function () { set_a('...') }
return Object.freeze({ method_x, method_y, method_z })
}
These accessors can now be used by constructor_y to access private state of constructor_x. They are something like protected members in classical inheritance model. This makes constructor_x in some way special: It is not to be used as normal constructor, but only for composition inside other constructors. Another problem is that if we had another constructor like constructor_x which works on private variable a, we couldn't use them together in composition:
// another constructors which wants to work on `a`
const constructor_x2 = function (spec) => {
let { a } = spec
const method_z = function () { a = '...' }
return Object.freeze({
method_z,
get_a () { return a },
set_a (val) { a = val }
})
}
const constructor_y = function (spec) {
let { a, b } = spec
const { method_x, get_a, set_a } = constructor_x(spec)
const { method_x2, get_a: get_a2, set_a: set_a2 } = constructor_x2(spec)
// How do I use variable a now? There are two of them
// and constructors x and x2 don't share them.
}
All of this would not be a problem if I used this and modified state on the instance.