There have been many times where I've wanted to expose multiple "constructors" through static factory methods, and have the default constructor only be used internally by the factories.
As a concrete example:
class Counter {
#nextNumb
// This should not get used directly.
constructor({ nextNumb = 0 } = {}) {
this.#nextNumb = nextNumb
}
static create = () => new Counter()
countUp = () => ++this.#nextNumb
clone = () => new Counter({ nextNumb: this.#nextNumb })
}
Ideally, the constructor shouldn't be used from outside this class, but there's no easy way to self-document that.
I'm proposing that javascript adds the ability to have private constructors, like this:
#constructor({ nextNumb = 0 } = {}) { ... }
Only internal methods would have the ability to create instances of this class. Outside users have to instantiate it through one of the static factory methods.
I realize there's some technecal hurdles that need to be overcome in order to make this possible. The biggest issue is that in javascript, the constructor is the class, so there isn't an easy way to just prevent access to the constructor from the outside world.
The following solution should solve this issue: Like with other properties, both a public and a private constructor can exist simultaneously. The public constructor gets invoked with the new
operator, while the private constructor gets invoked with a new "new#
" operator. More specifically, new#
will use MyClass.prototype.#constructor
as the constructor. If a private constructor is defined, and a public one is omitted, then the default public constructor will simply be a function that throws an error. If a private constructor calls super()
, it'll call the parent class's public constructor.
Here's a concrete example. Because a private constructor is present, and a public constructor is not provided, the public constructor defaults to a function that throws an error.
class Counter {
#nextNumb
#constructor({ nextNumb = 0 } = {}) {
this.#nextNumb = nextNumb
}
static create = () => new# Counter()
countUp = () => ++this.#nextNumb
clone = () => new# Counter({ nextNumb: this.#nextNumb })
}
new Counter() // This default constructor throws an error.
new# Counter() // Doesn't work - can't access a private constructor outside of its class.
Counter.create() // returns a new instance.
An alternative version, with both a public and a private constructor
class Counter {
#nextNumb
#constructor({ nextNumb }) {
this.#nextNumb = nextNumb
}
constructor() {
this.#nextNumb = 0
}
countUp = () => ++this.#nextNumb
clone = () => new# Counter({ nextNumb: this.#nextNumb })
}
new Counter() // Returns a new instance
new# Counter() // Doesn't work - can't access a private constructor outside of its class.
Thoughts? Is such a feature valuable enough to introduce a new operator? Are there other, better ways to achieve this functionality?
One alternative would be to have "internal-use-only" constructors instead of private constructors. The objective is the same, but the implementation is different. The #constructor() {}
syntax will not create a private constructor (meaning it'll still place this function on MyClass.prototype.constructor
, not MyClass.prototype.#constructor
) - instead, it sets some internal field on the constructor, specifying that it can only be used from within the class. When the new keyword is used on one of these marked constructors, an error will be thrown, unless "new" is used within the same class. This changes what "#" means for this specific situation, but it might be a more intuitive meaning, as this implementation would be closer to how private constructors are implemented in other languages.