I was dabbling with singletons and extending classes and came up with this 'working' code.
let instance = null;
class Motorcycle {
constructor(engine, color) {
if(!instance) {
this.engine = engine;
this.color = color;
instance = this;
} else {
return instance;
}
}
}
class Car extends Motorcycle {
constructor(engine, color) {
if(!instance) {
super(engine, color);
this.doors = 4;
} else {
return instance;
}
}
}
class SUV extends Car {
constructor(engine, color, doors) {
if(!instance) {
super(engine,color);
instance.doors = doors == undefined ? 5 : doors;
} else {
return instance;
}
}
}
I realized that in the constructor for sub-classes I can set doors with instance.doors and this.doors.
So I have two questions:
Does it matter enough to have a best practice?
What is that best practice?
Does it matter enough to have a best practice?
Not in your case probably, but there is a difference between them.
What is that best practice?
Do not use singletons. Especially not when subclassing. Your code is unable to create an instance of a separate subclass, is unable to create multiple cars, is unable to create a new SUV after creating a new Car (and also a Car is not a MotorCycle).
Your singleton objects should exist outside the constructor; preferably inside a map and accessed by their class name. Constructors should really only call their super constructor or set instance variables.
You will also need a way to dynamically bind constructor arguments, this is taken care of in the ctorBind function below.
You could also call getSingleton(SUV) a second time without args. This example just shows that the original SUV was not modified upon returning.
// REFLECTION UTILS
const getClassName = (obj) => obj.toString().match(/ (\w+)/)[1]
const ctorBind = (ctor, args) => new (ctor.bind.apply(ctor, [null].concat(args)))()
// MAIN FUNCTION
const main = () => {
console.log(getSingleton(SUV, 'V6', 'Red', 5))
console.log(getSingleton(SUV, 'V8', 'Blue', 6)) // Not updated...
}
// BEGIN SINGLETON MAP AND LOOKUP
const vehicleSingletons = {};
function getSingleton(vehicleClass, ...args) {
const className = getClassName(vehicleClass)
if (vehicleSingletons[className] == null) {
vehicleSingletons[className] = ctorBind(vehicleClass, args);
}
return vehicleSingletons[className];
}
// BEGIN CLASS DEFINITIONS
class Vehicle {
constructor(engine, color) {
this.engine = engine;
this.color = color;
}
}
class Motorcycle extends Vehicle {
constructor(engine, color) {
super(engine, color);
}
}
class Car extends Vehicle {
constructor(engine, color, doors) {
super(engine, color);
this.doors = doors;
}
}
class SUV extends Car {
constructor(engine, color, doors) {
super(engine, color, doors);
}
}
main()
.as-console-wrapper { top: 0; max-height: 100% !important; }
Related
I've seen a tutorial where a possible function is passed to the parent like below:
class Car {
constructor(door, engine, color){
this.door = door;
this.engine = engine;
this.color = color;
}
carStats(){
console.log('in car stats...');
}
}
class SUV extends Car {
constructor(door, engine, color, brand, carStats){
super(door, engine, color, carStats){
this.brand = brand;
this.wheels = 5;
this.cheap = true;
}
}
Is there any possibility of overwriting the parent method if you pass another function to it?
Thanks
When calling super, you actually call the constructor for Car, but the Car constructor only expects 3 arguments, not 5.
You should be able to override the carStats method by redefining it, but I don't think this is considered to be best practice.
class SUV extends Car {
constructor(door, engine, color, brand, carStats){
super(door, engine, color);
this.carStats = carStats;
this.brand = brand;
}
}
I have a question about super() in javascript.
Should i call the parent class method 'stringy()' with super() in the child class or i can use it like this:
function solve() {
class Melon {
constructor(weight, melonSort) {
if (new.target === Melon) {
throw new Error('Abstract class cannot be instantiated directly.')
}
this.weight = weight;
this.melonSort = melonSort;
this._elementIndex = weight * Number(melonSort.length);
}
stringy() {
return `Element: ${this.element}\nSort: ${this.melonSort}\nElement Index: ${this._elementIndex}`
}
}
class Watermelon extends Melon {
constructor(weight, melonSort) {
super(weight, melonSort);
this.element = 'Water';
}
}
or
function solve() {
class Melon {
constructor(weight, melonSort) {
if (new.target === Melon) {
throw new Error('Abstract class cannot be instantiated directly.')
}
this.weight = weight;
this.melonSort = melonSort;
this._elementIndex = weight * Number(melonSort.length);
}
stringy() {
return `Element: ${this.element}\nSort: ${this.melonSort}\nElement Index: ${this._elementIndex}`
}
}
class Watermelon extends Melon {
constructor(weight, melonSort) {
super(weight, melonSort);
this.element = 'Water';
}
stringy(){
return super.stringy();
}
}
which one is correct and what is the difference.
There's no need to include stringy in Watermelon unless you want to change the behavior. Watermelon instances will inherit the Melon version if you don't. Your two versions of Watermelon are very nearly identical, and calls to stringy on instances of either version will create and return the same string. If stringy used arguments, your overwritten one would need to be sure to pass all arguments it received along, but stringy doesn't use any, so...
(Just for completeness: The only slight difference is that in your second version of Watermelon, there is a Watermelon.prototype.stringy function, whereas in your first version there isn't. It's fine that there isn't, because Watermelon.prototype inherits from Melon.prototype, which has stringy.)
I'm trying to create a class which should be able the extend several base classes. In the following example I would like to use either stone or wood as base classes for class house. For that I tried creating a material class which would select the proper base class. But I don't get it to work.
const EventEmitter = require('events').EventEmitter;
class Stone extends EventEmitter{
constructor(weight, color){
super();
this.color = color;
this.weight = weight;
this.hard = true
this.start();
}
testEmitterFunction(){
this.emit('test');
}
start(){
setInterval(this.testFunc.bind(this), 500);
}
}
class Wood{
constructor(weight, color){
this.color = color;
this.weight = weight;
this.hard = false;
}
}
class Material{
constructor(mat, weight, color){
switch(mat){
case 'wood': return new Wood(weight, color);
case 'stone': return new Stone(weight, color);
}
}
}
class House extends Material{
constructor(mat, weight, color, name){
super(mat, weight, color)
this.name = name;
this.on('test', (arg) => {
console.log('1')
});
this.test();
}
test(){
console.log('test house function');
}
}
class House2 extends Stone{
constructor(weight, color, name){
super(weight, color)
this.name = name;
this.on('test', (arg) => {
console.log('2')
});
this.test();
}
test(){
console.log('test house2 function');
}
}
const home = new House('stone', 8, 'green', 'homesweethome');
const home2 = new House2(8, 'green', 'homesweethome');
I would like that instance home would have the same behaviour as instance home2. But in this case the test function which does the console.log('test house function') does not work. I tried other solutions, but then the EventEmitter would not work or the stone properties would not be available.
As I mentioned in my comment, what you're trying to do might be better done by using composition over inheritance.
As a general simplification if you can say "My X is a type of Y" or "My X is a Y" and it makes sense, that's inheritance, but if you say "My X is made of Y" or "My X contains Y" then you should use composition. Applied to your case, Stone and Wood are both a type of Material. Is a House a type of Material? I wouldn't say so, but a House is made of Stone or Wood, or rather, a House is made of Material, which means we should use composition for that.
If you want to keep the ability to pass a string to the House constructor that sets the material, then you can still do that. See House#setMaterial in the code sample at the bottom, though a factory pattern might work better for you in the future.
Another problem with your structure is that it kills polymorphism. If you wanted methods that did the same thing in both Stone and Wood, say "breaking", then you'd have to copy-paste the same code over, but if they both inherited from a generic Material type, then you only need to create the method once in the base class.
I want to be able to use the EventEmitter from stone for my house as well. i.e. house.on(...) instead of house.stone.on(...)
When it comes to using an eventemitter, I'd recommend you create one at the highest level possible and then pass it to the components that need it. In this case, House can pass an event emitter to the material or any other components (such as a room). Due to the craziness of Javascript, House can be the eventemitter and pass itself to the material. See the House#setEmitter function in the House class below. The observe how it's used in the polymorphic function Material#Break.
/** Unimportant */
class EventEmitter {
constructor(){ this.map = {} }
on(e, cb){
if(!this.map[e]) this.map[e] = []
this.map[e].push(cb)
}
emit(event,...data){
if(!this.map[event]) return
this.map[event].forEach(cb=>cb(...data))
}
}
/**/
class Material {
constructor(name = 'Something', weight = 5, color = 'black', hard = true){
this.weight = weight
this.color = color
this.hard = hard
this.name = name
}
setEmitter(emitter){
this.emitter = emitter
}
break(){
if(this.emitter){
this.emitter.emit(`break`, `The ${this.name} has broken` )
}
}
describe(){
return `${this.weight}lb ${this.hard?'hard':'soft'} ${this.color} ${this.name}`
}
}
class Stone extends Material {
constructor(weight = 8, color = 'gray'){
super("Stone", weight, color, true)
}
}
class Wood extends Material {
constructor(weight=4, color="brown"){
super("Wood", weight, color, false)
}
}
class House extends EventEmitter {
constructor(material, name){
super()
this.material = this.setMaterial(material)
this.name = name
this.on('break', (what)=>{
console.log(`${this.name} Event: `+what)
})
}
setMaterial(mat){
const matMap = {
stone : Stone,
wood : Wood
}
// Just gets a default material
if(typeof mat == 'string'){
mat = new matMap[mat]()
}
mat.setEmitter(this)
return mat
}
// Logs information about the material
describe(){
console.log(`A house named ${this.name} made out of ${this.material.describe()}`)
}
}
// Examples
// Method 1: Create a basic stone house and set material color later
const stoneHouse = new House("stone", "MyHouse")
stoneHouse.describe()
stoneHouse.material.color = "blue"
stoneHouse.describe()
stoneHouse.material.break()
// Method 2: Create a material and pass it to the house
const myMaterial = new Wood(6, "green")
const woodHouse = new House(myMaterial, "WoodHouse")
woodHouse.describe()
// Call a function that emits an event to the house
myMaterial.break()
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();
Assume that class Cheddar inherits from a class which expects an object as a parameter; essentially composition:
class Brand {
constructor(b) {
this.brand = b;
}
getBrand() {
return this.brand;
}
}
class Cheese {
constructor(brand_obj) {
// do stuff...
}
}
1
class Cheddar extends Cheese {
constructor(b) {
super(new Brand(b)); // <-- HERE, I'm a "one-liner" kinda guy :D
}
}
Now when I instantiate:
let snack = new Cheddar("Cabot Clothbound");
I can't access the Brand object, because it was created as an argument.
So, I tried to create the Brand and put it on the object before calling super, like this:
2
class Cheddar extends Cheese {
constructor(b) {
this.brand = new Brand(b);
super(this.brand);
}
}
...which results in the following error:
'this' is not allowed before super()
Grr.. so, I could do it this way:
3
class Cheddar extends Cheese {
constructor(b) {
let myBrand = new Brand(b);
super(myBrand);
this.brand = myBrand;
}
getBrand() {
return this.brand.getBrand();
}
}
And I can now happily access the methods on the cheese object like so:
let snack = new Cheese("Cabot Clothbound");
console.log(snack.getBrand()); //-> Cabot Clothbound
...but, it's getting messy. I want to be a "one-liner guy."
Anyway to access the object created as an argument to this constructor, or can I possibly structure things a bit differently to make this easier? I feel like I'm working too hard here. Thx, Keith :^D
You are not finishing the construction of Cheese. Your // do stuff should include saving the brand_obj if you want to access it from subclasses.
When you call super(new Brand(b)) the brand object will end up in the super class's constructor. But you aren't doing anything with it there. If save the brand as a property on the super class it will be available to the subclass:
class Brand {
constructor(b) {
this.brand = b;
}
getBrand() {
return this.brand;
}
}
class Cheese {
constructor(brand_obj) {
this.brand = brand_obj // <= save this!!
}
}
class Cheddar extends Cheese {
constructor(b) {
super(new Brand(b));
console.log("brand in cheddar constructor", this.brand) //<== now you can use it
}
}
let snack = new Cheddar("Cabot Clothbound");
console.log("Brand on instance: ", snack.brand)