extending singleton class in es6 - javascript

I need to extend a singleton class in JavaScript .
The problem that I am facing is that I get the class instance which I am extending from instead of only getting the methods of the class.
I have tried to remove super to not get the instance but then I got an error
Must call super constructor in derived class before accessing 'this' or returning from derived constructor
Code example:
let instanceA = null;
let instanceB = null;
class A {
constructor(options) {
if (instanceA === null) {
this.options = options;
instanceA = this;
}
return instanceA;
}
}
class B extends A {
constructor(options) {
if (instanceB === null) {
super()
console.log('real class is ' + this.constructor.name)
this.options = options
instanceB = this;
}
return instanceB;
}
}
const a = new A({
i_am_a: "aaaaaa"
});
const b = new B({
i_am_b: "bbbbbb"
}) // this change a
console.log(b.options)
console.log(a.options)

So, first of all there's a misconception here:
I have tried to remove super to not get the instance but then I got an error
super() calls the parent class' constructor on the created instance of the child class (i.e. what this is referencing). It does not return a parent class instance. See here for more information.
So, calling super() does not violate the singleton property of the parent class at all. It may well be only constructed a single time if implemented correctly.
With that in mind, you should improve your code a little bit.
A sensible change would be to remove the instance management from the constructors. One solution would be to use static constructors which either create the singleton if no instance exists or return the created instance.
Another one is to drop the arguments to the singleton class constructors. It doesn't really make sense to pass arguments to a class which is supposed to be instantiated once (you're never gonna do anything with the constructor arguments again). You could just make the arguments properties of the singleton right away. Here's a SO answer supporting this point for Java singletons.
A complete example with static constructors and without arguments looks like this:
let instanceA = null;
let instanceB = null;
let counters = { A: 0, B: 0 }; // count class instantiations
class A {
static getInstance() {
if (instanceA === null) {
instanceA = new A();
}
return instanceA;
}
whoami() {
const name = this.constructor.name;
return `${name} #${counters[name]}`;
}
constructor() {
counters[this.constructor.name] += 1;
}
}
class B extends A {
static getInstance() {
if (instanceB === null) {
instanceB = new B();
}
return instanceB;
}
constructor() {
super();
}
}
const a1 = A.getInstance();
const a2 = A.getInstance();
const a3 = A.getInstance();
const b1 = B.getInstance();
const b2 = B.getInstance();
const b3 = B.getInstance();
console.log(a1.whoami());
console.log(a2.whoami());
console.log(a3.whoami());
console.log(b1.whoami());
console.log(b2.whoami());
console.log(b3.whoami());
Note that B inherits whoami from A and that the constructor call counters are never incremented past 1.
Obviously with this approach you can make no guarantee the singleton property holds for each class unless only the static constructors are used to generate instances (since the constructors are still accessible). I think it's a good compromise though.

In JavaScript, a singleton is just an object literal.
const a = {
options: {
i_am_a: "aaaaaa"
}
};
const b = {
options: {
i_am_b: "bbbbbb"
}
};
If you really need a constructor function, you can just write a function that returns an object.
function makeSingleton(options) {
return {
options
}
}
const a = makeSingleton({i_am_a: "aaaaaa"});
const b = makeSingleton({i_am_b: "bbbbbb"});
There's no inheritance chain here, just two object literals. If you absolutely need a class, you can just create one, but it's an unnecessary waste of resources and typing.
class Singleton {
constructor(options) {
this.options = options;
}
}
const a = new Singleton({i_am_a: "aaaaaa"});
const b = new Singleton({i_am_b: "bbbbbb"});
In terms of inheriting, if that's something you really need, you can use Object.create() or Object.assign(), depending on your needs. Be aware that both are shallow - they only work a single layer deep so modifying the child's options property would modify the parent's options property as well.
const a = {
options: {
i_am_a: "aaaaaa"
},
getOptions() {
return this.options;
}
};
const b = Object.create(a);
b.options.i_am_b: "bbbbbb";
a.options.i_am_b; // -> "bbbbbb"
b.getOptions(); // -> { i_am_a: "aaaaaa", i_am_b: "bbbbbb" }
Of course, you could use Object.create() or Object.assign() on the options as well.
To be honest, I think you either need a couple of instances of the same class, or a simple object literal without any inheritance.

const instances = {}
class Singleton {
constructor() {
const instance = instances[this.constructor];
if (instance == null) {
return instances[this.constructor] = this;
}
return instance;
}
}
class Foo extends Singleton {
constructor() {
super();
this.foo = "foo";
}
}
class Bar extends Singleton {
constructor() {
super();
this.foo = "bar";
}
}
const foo1 = new Foo();
const foo2 = new Foo();
const bar1 = new Bar();
const bar2 = new Bar();
console.log(foo1 === foo2, bar1 === bar2, foo1 === bar1, foo1.foo = 123, foo2, bar1);

well i don't know if it the best solution but what i did is to check if the constructor name is different then the class name. if so then i let it create a new instance because that mean i try to extend the class
here is a working example of my test
let instanceA = null;
let instanceB = null;
class A {
constructor(options) {
this.options = options;
if (instanceA === null) {
instanceA = this;
}
if(this.constructor.name !== "A"){
return this;
}
return instanceA;
}
method1(){
console.log(this.constructor.name)
}
}
class B extends A {
constructor(options) {
if (instanceB === null) {
super(options)
instanceB = this;
}
return instanceB;
}
}
const a = new A({
i_am_a: "aaaaaa"
});a
const b = new B({
i_am_b: "bbbbbb"
})
const c = new A({
i_am_c: "ccccc"
});
const d = new B({
i_am_d: "ddddd"
})
console.log(a.options)
console.log(b.options)
console.log(c.options)
console.log(d.options)
a.method1();
b.method1();
c.method1();
d.method1();

Related

Javascript class composition : access attribute of secondary class in attribute of main class

Let's take this exemple :
class A {
attrA = 3;
}
class B {
constructor() {
this.a = new A;
}
attrB = this.a.attrA;
methB() { console.log(this.a.attrA);}
}
const test = new B;
console.log(test.attrB);
test.methB();
I can access the class A attribute through method of class B, but I can't use it with attribute of class B, I have an "undefined" error.
The only way to do this is :
class A {
attrA = 3;
}
class B {
constructor() {
this.a = new A;
this.z = this.a.attrA
}
methB() { console.log(this.a.attrA);}
}
const test = new B;
console.log(test.z);
test.methB();
Why I need to put the attribute in the constructor and not the method?
Thanks!
You can think of class fields as being hoisted, meaning this:
class Example {
constructor() {/*...*/}
property = "value";
}
Is "actually" this:
class Example {
property = "value";
constructor() {/*...*/}
}
Or (similar to) this:
class Example {
constructor() {
this.property = "value";
/*...*/
}
}
Further, identifiers are resolved upon access. So by the time you execute test.methB(), test.a has been initialized, allowing the method to correctly resolve this.a.attrA. It works the same as this code:
let variable = null;
const logVariable = () => console.log(variable); // You may think this logs null, but ...
variable = "value";
logVariable(); // ... it actually logs `variable`'s value at the time of calling.
As you have observed, mixing property initializations from within the constructor and using field initializers may be confusing. Therefore, a more intuitive way to write your code would be:
class A {
attrA = 3;
}
class B {
a = new A;
attrB = this.a.attrA;
constructor() {}
methB() {
console.log(this.a.attrA);
}
}
const test = new B;
console.log(test.attrB); // Works now!
test.methB();
Personal recommendations:
Declare all instance and class fields.
Prefer field initializers.
Only reassign/initialize fields in the constructor for non-default values.
You may want to read on for more details for a better technical understanding.
Class syntax
Classes are just uncallable constructor functions:
class Example {}
console.log(typeof Example); // "function"
Example(); // Error: cannot be invoked without `new`
This is a quirk of the ES6 class syntax. Technically, class constructors are still "callable", but only internally (which happens when using new). Otherwise constructors would serve no purpose.
Another aspect of the class syntax is the ability to call super() in the constructor. This only comes into play when a class inherits, but that comes with yet its own quirks:
You cannot use this before calling super:
class A {};
class B extends A {
constructor() {
this; // Error: must call super before accessing this
super();
}
}
new B();
Reason being, before calling super no object has been created, despite having used new and being in the constructor.
The actual object creation happens at the base class, which is in the most nested call to super. Only after calling super has an object been created, allowing the use of this in the respective constructor.
Class fields
With the addition of instance fields in ES13, constructors became even more complicated: Initialization of those fields happens immediately after the object creation or the call to super, meaning before the statements that follow.
class A /*no heritage*/ {
property = "a";
constructor() {
// Initializing instance fields
// ... (your code)
}
}
class B extends A {
property = "b";
constructor() {
super();
// Initializing instance fields
// ... (your code)
}
}
Further, those property "assignments" are actually no assignments but definitions:
class Setter {
set property(value) {
console.log("Value:", value);
}
}
class A extends Setter {
property = "from A";
}
class B extends Setter {
constructor() {
super();
// Works effectively like class A:
Object.defineProperty(this, "property", { value: "from B" });
}
}
class C extends Setter {
constructor() {
super();
this.property = "from C";
}
}
new A(); // No output, expected: "Value: from A"
new B(); // No output, expected: "Value: from B"
new C(); // Output: "Value: from C"
Variables
Identifiers are only resolved upon access, allowing this unintuitive code:
const logNumber = () => console.log(number); // References `number` before its declaration, but ...
const number = 0;
logNumber(); // ... it is resolved here, making it valid.
Also, identifiers are looked up from the nearest to the farthest lexical environment. This allows variable shadowing:
const variable = "initial-value";
{
const variable = "other-value"; // Does NOT reassign, but *shadows* outer `variable`.
console.log(variable);
}
console.log(variable);

Modify the Constructor Function of a Class

I already have a function for modifing other class methods:
class MyClass {
constructor() {
this.num = 1;
}
inc(){
this.num++;
}
}
runAfterMethod(classHandle, methodName, executeAfter) {
const oldHandle = classHandle.prototype[methodName];
classHandle.prototype[methodName] = function () {
const returnValue = oldHandle.apply(this, arguments);
//#ts-ignore
executeAfter.apply(this, arguments);
return returnValue;
};
}
runAfterMethod(MyClass, "inc", function() {console.log(this.num)})
new MyClass().inc(); // Prints 2
However, this does not work for the constructor of the class (because it is technically the class itself)
What I would really be happy with is something that mimics this behavior:
class MyClass {
constructor() {
this.num = 1;
}
}
extendConstructor(MyClass, function(){
this.str = "hello world"
})
new MyClass(); // num: 1, str: "hello world"
I have looked at things like How to modify the constructor of an ES6 class however, they all require = somewhere, which does not work inside of a function (where that only changes the value of the class inside the function, not the class itself)
Mutating the existing class is really weird. What would be much less of a code smell IMO would be to create a wrapper around the class that instantiates the class, then calls it with your custom callback, and then returns the instance - it'd be a lot more straightforward. Would that work for your purposes?
class MyClass {
constructor() {
this.num = 1;
}
}
const extendConstructor = (theClass, callback) => function(...args) {
const instance = new theClass(...args);
callback.call(instance);
return instance;
};
const Extended = extendConstructor(MyClass, function(){
this.str = "hello world"
})
console.log(new Extended()); // num: 1, str: "hello world"

Is there a way to instantiate a new instance of a subclass from inside a super class method?

I would like to be able to instantiate a new instance of a subclass from inside a superclass method.
If I have just a single class with no inheritance, it is straight forward:
class A {
static build(opts) {
return new A(opts)
}
makeAnother() {
return A.build(...)
}
}
const a = new A()
const a2 = a.makeAnother()
This works. However, it doesn't work with subclassing:
class B extends A { ... }
const b = new B()
const b2 = b.makeAnother() // this returns an instance of A, not B
I suppose I could add the build & makeAnother methods to each subclass, but I would rather not repeat things.
You can reference this.constructor inside the super class to get to the constructor of the subclass (or the super class itself, if the method is called on a super instance rather than a sub instance):
class A {
static build(theClass) {
return new theClass()
}
makeAnother() {
return A.build(this.constructor)
}
}
const a = new A()
const a2 = a.makeAnother()
console.log(a2 instanceof A);
class B extends A { }
const b = new B()
const b2 = b.makeAnother()
console.log(b2 instanceof B);
You'll want to use
class A {
static build(opts) {
return new this(opts)
}
makeAnother() {
return this.constructor.build(...)
}
}
Unless build does more than shown here, you don't need it at all of course, you'd rather directly return new this.constructor(...) in makeAnother.

Nested ES6 classes?

It seems possible to nest a class in a constructor which can then be instantiated from anywhere within the class, is this official?
[EDIT] E.g.,
class C {
constructor() {
class D {
constructor() { }
}
}
method() {
var a = new D(); // works fine
}
}
//var a = new D(); // fails in outer scope
The traceur generated JS https://google.github.io/traceur-compiler/demo/repl.html
$traceurRuntime.ModuleStore.getAnonymousModule(function() {
"use strict";
var C = function C() {
var D = function D() {};
($traceurRuntime.createClass)(D, {}, {});
};
($traceurRuntime.createClass)(C, {method: function() {
var a = new D();
}}, {});
return {};
});
//# sourceURL=traceured.js
No, there are no nested class scopes in ES6, and there is no such thing as private members in the class syntax anyway if you mean that.
Of course you can put a second class as a static property on another class, like this:
class A {
…
}
A.B = class {
…
};
or you use an extra scope:
var C;
{
class D {
constructor() { }
}
C = class C {
constructor() { }
method() {
var a = new D(); // works fine
}
}
}
(There seems to be a bug with traceur as it uses a hoisted var for the class declaration instead of block scope)
With the class field syntax, it will also be possible to write a single expression or declaration:
class A {
…
static B = class {
…
}
};
something like that?
class A {
constructor () {
this.B = class {
echo () {
console.log('I am B class');
}
}
}
echo () {
this.b = new this.B;
this.b.echo();
}
}
var a = new A;
a.echo();
You could use a getter:
class Huffman {
constructor() { /* ... */ }
static get Node() {
return class Node {
constructor() {
var API = this;
API.symbol = 0; API.weight = 0;
return API;
}
};
}
get Node() {
return Huffman.Node;
}
encode() { /* ... */ }
decode() { /* ... */ }
/* ... */
}
// usage
huffman = new Huffman;
new huffman.Node;
new Huffman.Node;
Which in latest Chrome Dev 44.0.2376.0 on Apple 10.10.2 gives in console
new huffman.Node
Node {symbol: 0, weight: 0}
new Huffman.Node
Node {symbol: 0, weight: 0}
In other news, getters are the secret sauce that let's you do a whole bunch of cool things in ES6.
Please Note The above construction breaks instanceof for Node (why? because a whole new class is defined with every get call). To not break instanceof define Node outside of the scope of a single getter, either in the constructor (disabling the Huffman.Node class property and causing instanceof to work within the namespace of a single Huffman instance, and break outside that), or define Node in a sibling or ancestor scope to Huffman (allowing instanceof to work in all scopes below that the one where Node is defined).
When you create a nested child class in a constructor of a parent class, this means every instance of the parent class has its own child class. Typically this is not what you want. Instead you want a child class, which is shared among all instances of the parent class. This means the nested class must be static. This is an example:
class Parent
{
static Child = class Child {
constructor (name) { console.log (`Child: ${name}`); }
}
constructor (...names) {
console.log ('Parent');
this.children = names.map (name => new Parent.Child (name));
}
}
var p = new Parent ('Alice', 'Bob');
console.log (`same type? ${p.children[0].constructor === p.children[1].constructor}`);

Inheriting more than once, keeping members private per instance

Basically, what I'm trying to do, is what has been discussed many times already. However, most examples feature only ClassA inheriting from ClassB. My situation is quite trivial, however doesn't seem to be addressed in the JavaScript topics that I have found.
ClassB extends ClassA, which has a member. ClassC and ClassD extend ClassB. However, when setting the only member in an instance of ClassC, this member is also set in members of ClassD. Let's look at an example:
function ClassA(data) {
var This = this;
This._data = {};
This._construct = function(data) {
if( undefined === data ) {
This._data = {};
return true;
}
This._data = data;
}
This._construct(data);
}
function ClassB() {
}
ClassB.prototype = new ClassA();
ClassB.prototype.constructor = ClassB;
function ClassC() { // Extends ClassB
}
ClassC.prototype = new ClassB();
ClassC.prototype.constructor = ClassC;
function ClassD() { // Extends ClassB
}
ClassD.prototype = new ClassB();
ClassD.prototype.constructor = ClassD;
var objectC = new ClassC();
var objectD = new ClassD();
objectC._data['somevar'] = 'asdasdasd';
console.log(objectC._data); // Object { somevar="asdasdasd"}
console.log(objectD._data); // Object { somevar="asdasdasd"}
If you set a breakpoint on line console.log(objectC._data);, you may see in the Watch window of FireBug that all prototypes are set correctly. It's really strange, because it is clear that the prototypes of ClassC and ClassD are separate instances created with the new keyword, and thus should not share members. Can someone explain please?
The _construct() function can be removed from ClassA, with no effect.
The var This = this line can be removed, and all consequent references to This can be changed to this, with no effect.
You need to have a call to super in each class, to have this pattern work. This is how you can ao it.
//Simplifying the construction part a bit.
function ClassA(data) {
this._data = data || {};
}
function ClassB(data) {
ClassA.call(this, data); //call super class (ClassA) constructor
//here you can initialize any ClassB specific variables
}
ClassB.prototype = new ClassA();
ClassB.prototype.constructor = ClassB;
function ClassC(data) { // Extends ClassB
ClassB.call(this, data); //call super class (ClassB) constructor
//here you can initialize any ClassC specific variables
}
ClassC.prototype = new ClassB();
ClassC.prototype.constructor = ClassC;
function ClassD(data) { // Extends ClassB
ClassB.call(this, data); //call super class (ClassB) constructor
//here you can initialize any ClassD specific variables
}
ClassD.prototype = new ClassB();
ClassD.prototype.constructor = ClassD;
var objectC = new ClassC();
var objectD = new ClassD();
objectC._data['somevar'] = 'asdasdasd';
console.log(objectC._data); // Object { somevar="asdasdasd"}
console.log(objectD._data); // Object {} -- empty for objectD
Now you can see that _data is not shared across instances.

Categories

Resources