Reputation: 34288
I have defined pure objects in JS which expose certain static methods which should be used to construct them instead of the constructor. How can I make a constructor for my class private in Javascript?
var Score = (function () {
// The private constructor
var Score = function (score, hasPassed) {
this.score = score;
this.hasPassed = hasPassed;
};
// The preferred smart constructor
Score.mkNewScore = function (score) {
return new Score(score, score >= 33);
};
return Score;
})();
Update: The solution should still allow me to test for x instanceof Score
. Otherwise, the solution by @user2864740 of exposing only the static constructor works.
Upvotes: 34
Views: 33756
Reputation: 1
Here is a more elegant solution based on class hierarchy:
class ParentClass{
#p1=10;
constructor(){
this.#p1=100;
}
setP1(value){
this.#p1=value;
}
parentMethod(){
console.log(this.#p1);
}
}
class ClassScore extends ParentClass {
constructor(score){
throw new Error('The constructor is private');
}
static #AsalClass = class ClassScore extends ParentClass{
score;
hasPassed;
constructor(JaaliClass, score){
super();
this.score = score;
this.hasPassed = score>39;
this.constructor = JaaliClass;
}
getScore(){
console.log('asal class');
return this.score;
}
};
static mkNewInstance = function (score) {
return new ClassScore.#AsalClass(ClassScore, score);
};
}
let c= ClassScore.mkNewInstance(40);
console.log(c);
console.log(c.constructor);
console.log(c.getScore());
c.parentMethod();
console.log(c instanceof ClassScore);
console.log(c instanceof ParentClass);
console.log("-------------------b");
let b= ClassScore.mkNewInstance(30);
console.log(b);
console.log("-------------------d");
let d=new c.constructor(60);
console.log(d);
Upvotes: -2
Reputation: 1280
In order to create a private constructor in JS, I like to create a private key that is only accessible in the class (function) file and provide a static factory function as the only allowed way to construct said class:
// in PrivateConstructorClass.js
// Use a Symbol as this will always be unique.
// If you don't have Symbol in your runtime,
// use a random string that nobody can reliably guess,
// such as the current time plus some other random values.
const PRIVATE_CONSTRUCTOR_KEY = Symbol()
class PrivateConstructorClass {
constructor(arg1, arg2, argN, constructorKey) {
if (constructorKey !== PRIVATE_CONSTRUCTOR_KEY) {
throw new Error('You must use the PrivateConstructorClass.create() to construct an instance.')
}
this.arg1 = arg1
this.arg2 = arg2
this.argN = argN
}
static create(arg1, arg2, argN) {
return new PrivateConstructorClass(arg1, arg2, argN, PRIVATE_CONSTRUCTOR_KEY)
}
}
// From Another JS File:
try {
const myFailedInstanceA = new PrivateConstructorClass('foo', 123, {
size: 'n'
})
} catch (err) {
console.error('Failed:', err.message)
}
const myFactoryInstance = PrivateConstructorClass.create('foo', 123, {
size: 'n'
})
console.log('Success:', myFactoryInstance)
Upvotes: 16
Reputation: 97
So to be fair the simplest answer is usually the best. An object literal is always a single instance. Not much reason for anything more complex other than, perhaps allocation of memory on demand.
That being said, here is a classical implementation of a singleton using ES6.
Also of note. It’s important to understand what the keyword this means in different contexts.
In the constructor, this points to the instance being created.
In the static getInstance method, this points to the left of the dot, Universe constructor function which, is an object like most things in JS and can hold properties.
class Universe {
constructor() {
if (!((new Error).stack.indexOf("Universe.getInstance") > -1)) {
throw new Error("Constructor is private. Use static method getInstance.");
}
this.constructor.instance = this;
this.size = 1;
}
static getInstance() {
if (this.instance) {
return this.instance;
}
return new this;
}
expand() {
this.size *= 2;
return this.size;
}
}
//console.log(Universe.getInstance())
//console.log(Universe.getInstance().expand())
//console.log(Universe.getInstance())
//console.log(new Universe())
const getInstance= () => { console.log('hi');
console.log("From singleton: ", Universe.getInstance()); return new Universe() };
console.log(getInstance())
Upvotes: -1
Reputation: 7687
Another possible simple approach is to use predicate function instead of instanceof. For typescript it can be a type guard and type synonym instead of a class can be exported:
// class is private
class _Score {
constructor() {}
}
export type Score = _Score
export function isScore(s): s is Score {
return s instanceof _Score
}
Upvotes: -1
Reputation: 664297
Is there a solution which will allow me to say
x instanceof Score
?
Yes. Conceptually, @user2864740 is right, but for instanceof
to work we need to expose (return
) a function instead of a plain object. If that function has the same .prototype
as our internal, private constructor, the instanceof
operator does what is expected:
var Score = (function () {
// the module API
function PublicScore() {
throw new Error('The constructor is private, please use Score.makeNewScore.');
}
// The private constructor
var Score = function (score, hasPassed) {
this.score = score;
this.hasPassed = hasPassed;
};
// Now use either
Score.prototype = PublicScore.prototype; // to make .constructor == PublicScore,
PublicScore.prototype = Score.prototype; // to leak the hidden constructor
PublicScore.prototype = Score.prototype = {…} // to inherit .constructor == Object, or
PublicScore.prototype = Score.prototype = {constructor:null,…} // for total confusion :-)
// The preferred smart constructor
PublicScore.mkNewScore = function (score) {
return new Score(score, score >= 33);
};
return PublicScore;
}());
> Score.mkNewScore(50) instanceof Score
true
> new Score
Error (…)
Upvotes: 10
Reputation: 61865
Simply don't expose the constructor function. The core issue with the original code is the "static method" is defined as a property of the constructor (which is used as a "class") as opposed a property of the module.
Consider:
return {
mkNewScore: Score.mkNewScore
// .. and other static/module functions
};
The constructor can still be accessed via .constructor
, but .. meh. At this point, might as well just let a "clever user" have access.
return {
mkNewScore: function (score) {
var s = new Score(score, score >= 33);
/* Shadow [prototype]. Without sealing the object this can
be trivially thwarted with `del s.constructor` .. meh.
See Bergi's comment for an alternative. */
s.constructor = undefined;
return s;
}
};
Upvotes: 6
Reputation: 34288
One can use a variable (initializing
) inside a closure which can throw an error if the constructor was called directly instead of via a class method:
var Score = (function () {
var initializing = false;
var Score = function (score, hasPassed) {
if (!initializing) {
throw new Error('The constructor is private, please use mkNewScore.');
}
initializing = false;
this.score = score;
this.hasPassed = hasPassed;
};
Score.mkNewScore = function (score) {
intializing = true;
return new Score(score, score >= 33);
};
return Score;
})();
Upvotes: 22