I'm having a very hard time writing modules that are comprised of typically 3-5 smaller modules or classes. The issue arises when these sub components need to be extended, but the main module is already creating and implementing their base versions.
Example
// main module design
class Car {
constructor() {
this.horn = new Horn();
this.horn.on('honk', function() {
console.log('honked');
})
}
}
class Horn {
constructor() {
this.sound = 'hornA.wav'
}
}
// extended module design
class RaceCar extends Car {
constructor() {
super();
this.horn = new RaceHorn();
// event handler is now missing
}
}
class RaceHorn extends Horn {
constructor() {
super();
this.horn = 'hornB.wav'
}
}
This is indeed a very simplified example, while my true issues involve modules with more components and more setup requirements. I do understand that I can put things into another init or setup or similar function, but to me it seems like I'm doing something wrong inherently.
I heavily suggest passing the values through parameters, for what concerns this issue. Default parameters can help a lot
// main module design
class Car {
constructor( parametrizedHorn=Horn ) {
this.horn = new parametrizedHorn();
this.horn.on('honk', function() {
console.log('honked');
})
}
}
class Horn {
constructor() {
this.sound = 'hornA.wav'
}
}
// extended module design
class RaceCar extends Car {
constructor() {
super( RaceHorn );
}
}
class RaceHorn extends Horn {
constructor() {
super();
this.sound = 'hornB.wav'
}
}
This can get very messy, so instead you can use initialization methods
// main module design
class Car {
constructor() { ... }
init( hornType=Horn ){
this.initHorn( hornType );
}
initHorn( hornType=Horn ){
this.horn = new Horn();
this.horn.on('honk', function() {
console.log('honked');
})
}
}
class Horn {
constructor() {
this.sound = 'hornA.wav'
}
}
// extended module design
class RaceCar extends Car {
constructor() {
super();
}
init( hornType=RaceHorn ){
this.initHorn( hornType );
}
}
class RaceHorn extends Horn {
constructor() {
super();
this.sound = 'hornB.wav'
}
}
const raceCar = new RaceCar();
raceCar.init();
These are two modular patterns that you can use, and depending on the kind of structure you're already using, you can judge which one is best. The trick in the end is to parametrize the contents of your class, or modularize it even further with methods
Related
For example, using mix-ins to extends multiple utility classes, like this:
import { autorun, makeObservable } from "mobx";
type GConstructor<T = {}> = new (...args: any[]) => T;
interface HasLife {
grow: () => void;
}
class Animal {
age: number;
constructor(age: number) {
this.age = age;
}
}
function addLife<TBase extends GConstructor<Animal>>(Base: TBase) {
return class Jumpable extends Base {
// this is wrong, mixins class cannot use constructor!
constructor() {
super();
makeObservable(this, {
age: observable,
grow: action,
});
}
grow() {
this.age++;
}
};
}
class Dog extends addLife(Animal) implements HasLife {
constructor(age: number) {
super(age);
}
}
const dog = new Dog(0);
autorun(() => {
console.log(dog.age);
});
dog.grow();
The example I provided cannot run correctly because mixin classes cannot have constructors, but I would also like to mark the member methods as observable or action in the mixin classes.
What should I do?
or, What is the common approach within the mobx community for dealing with this problem using other methods?
I want to implement very simple abstract factory pattern but I am facing this error.
Also am I doing something wrong with my code? How can I improve this to make it work properly?
Must call super constructor in derived class before accessing 'this' or returning from derived constructor"
You need to call super() inside constructor:
class MakeMeatPizza extends MakePizza {
constructor() {
super();
super.createPizza("meat");
}
}
So thanks to this beautiful answer:
The rules for ES2015 (ES6) classes basically come down to:
In a child class constructor, this cannot be used until super is called.
ES6 class constructors MUST call super if they are subclasses, or they must explicitly return some object to take the place of the one
that was not initialized.
And the code looks like this:
class Pizza {
constructor(name,size,type) {
this.name = name;
this.size = size;
this.type = type;
}
prepare() {
console.log("prepare");
}
bake() {
console.log("bake");
}
deliver() {
console.log("deliver");
}
}
class MeatPizza extends Pizza {
constructor() {
super("Chicago style pizza", "Large", "Meat")
}
}
class VegePizza extends Pizza {
constructor() {
super("Only natural", "Large", "Vegetarian")
}
}
class MakePizza {
createPizza(type) {
switch (type) {
case "meat":
return new MeatPizza()
case "vege":
return new VegePizza()
default:
throw new Error("Something went wrong...");
}
}
}
class MakeMeatPizza extends MakePizza {
constructor() {
super()
}
create() {
return this.createPizza("meat")
}
}
class MakeVegePizza extends MakePizza {
constructor() {
super()
}
create() {
return this.createPizza("vege")
}
}
class OrderPizza {
}
const test = new MakeVegePizza().create();
console.log(test)
I have a class A, and a class B inherited from it.
class A {
constructor(){
this.init();
}
init(){}
}
class B extends A {
private myMember = {value:1};
constructor(){
super();
}
init(){
console.log(this.myMember.value);
}
}
const x = new B();
When I run this code, I get the following error:
Uncaught TypeError: Cannot read property 'value' of undefined
How can I avoid this error?
It's clear for me that the JavaScript code will call the init method before it creates the myMember, but there should be some practice/pattern to make it work.
This is why in some languages (cough C#) code analysis tools flag usage of virtual members inside constructors.
In Typescript field initializations happen in the constructor, after the call to the base constructor. The fact that field initializations are written near the field is just syntactic sugar. If we look at the generated code the problem becomes clear:
function B() {
var _this = _super.call(this) || this; // base call here, field has not been set, init will be called
_this.myMember = { value: 1 }; // field init here
return _this;
}
You should consider a solution where init is either called from outside the instance, and not in the constructor:
class A {
constructor(){
}
init(){}
}
class B extends A {
private myMember = {value:1};
constructor(){
super();
}
init(){
console.log(this.myMember.value);
}
}
const x = new B();
x.init();
Or you can have an extra parameter to your constructor that specifies whether to call init and not call it in the derived class as well.
class A {
constructor()
constructor(doInit: boolean)
constructor(doInit?: boolean){
if(doInit || true)this.init();
}
init(){}
}
class B extends A {
private myMember = {value:1};
constructor()
constructor(doInit: boolean)
constructor(doInit?: boolean){
super(false);
if(doInit || true)this.init();
}
init(){
console.log(this.myMember.value);
}
}
const x = new B();
Or the very very very dirty solution of setTimeout, which will defer initialization until the current frame completes. This will let the parent constructor call to complete, but there will be an interim between constructor call and when the timeout expires when the object has not been inited
class A {
constructor(){
setTimeout(()=> this.init(), 1);
}
init(){}
}
class B extends A {
private myMember = {value:1};
constructor(){
super();
}
init(){
console.log(this.myMember.value);
}
}
const x = new B();
// x is not yet inited ! but will be soon
Because myMember property is accessed in parent constructor (init() is called during super() call), there is no way how it can be defined in child constructor without hitting a race condition.
There are several alternative approaches.
init hook
init is considered a hook that shouldn't be called in class constructor. Instead, it is called explicitly:
new B();
B.init();
Or it is called implicitly by the framework, as a part of application lifecycle.
Static property
If a property is supposed to be a constant, it can be static property.
This is the most efficient way because this is what static members are for, but the syntax may be not that attractive because it requires to use this.constructor instead of class name if static property should be properly referred in child classes:
class B extends A {
static readonly myMember = { value: 1 };
init() {
console.log((this.constructor as typeof B).myMember.value);
}
}
Property getter/setter
Property descriptor can be defined on class prototype with get/set syntax. If a property is supposed to be primitive constant, it can be just a getter:
class B extends A {
get myMember() {
return 1;
}
init() {
console.log(this.myMember);
}
}
It becomes more hacky if the property is not constant or primitive:
class B extends A {
private _myMember?: { value: number };
get myMember() {
if (!('_myMember' in this)) {
this._myMember = { value: 1 };
}
return this._myMember!;
}
set myMember(v) {
this._myMember = v;
}
init() {
console.log(this.myMember.value);
}
}
In-place initialization
A property may be initialized where it's accessed first. If this happens in init method where this can be accessed prior to B class constructor, this should happen there:
class B extends A {
private myMember?: { value: number };
init() {
this.myMember = { value: 1 };
console.log(this.myMember.value);
}
}
Asynchronous initialization
init method may become asynchronous. Initialization state should be trackable, so the class should implement some API for that, e.g. promise-based:
class A {
initialization = Promise.resolve();
constructor(){
this.init();
}
init(){}
}
class B extends A {
private myMember = {value:1};
init(){
this.initialization = this.initialization.then(() => {
console.log(this.myMember.value);
});
}
}
const x = new B();
x.initialization.then(() => {
// class is initialized
})
This approach may be considered antipattern for this particular case because initialization routine is intrinsically synchronous, but it may be suitable for asynchronous initialization routines.
Desugared class
Since ES6 classes have limitations on the use of this prior to super, child class can be desugared to a function to evade this limitation:
interface B extends A {}
interface BPrivate extends B {
myMember: { value: number };
}
interface BStatic extends A {
new(): B;
}
const B = <BStatic><Function>function B(this: BPrivate) {
this.myMember = { value: 1 };
return A.call(this);
}
B.prototype.init = function () {
console.log(this.myMember.value);
}
This is rarely a good option, because desugared class should be additionally typed in TypeScript. This also won't work with native parent classes (TypeScript es6 and esnext target).
One approach you could take is use a getter/setter for myMember and manage the default value in the getter. This would prevent the undefined problem and allow you to keep almost exactly the same structure you have. Like this:
class A {
constructor(){
this.init();
}
init(){}
}
class B extends A {
private _myMember;
constructor(){
super();
}
init(){
console.log(this.myMember.value);
}
get myMember() {
return this._myMember || { value: 1 };
}
set myMember(val) {
this._myMember = val;
}
}
const x = new B();
Try this:
class A {
constructor() {
this.init();
}
init() { }
}
class B extends A {
private myMember = { 'value': 1 };
constructor() {
super();
}
init() {
this.myMember = { 'value': 1 };
console.log(this.myMember.value);
}
}
const x = new B();
Super has to be first command. Remeber that typescript is more "javascript with documentation of types" rather than language on its own.
If you look to the transpiled code .js it is clearly visible:
class A {
constructor() {
this.init();
}
init() {
}
}
class B extends A {
constructor() {
super();
this.myMember = { value: 1 };
}
init() {
console.log(this.myMember.value);
}
}
const x = new B();
Do you have to call init in class A?
That works fine, but I don't know if you have different requirements:
class A {
constructor(){}
init(){}
}
class B extends A {
private myMember = {value:1};
constructor(){
super();
this.init();
}
init(){
console.log(this.myMember.value);
}
}
const x = new B();
More often than not, you can defer the call of init() to a time just before it is needed by hacking into one of your getters.
For example:
class FoodieParent {
public init() {
favoriteFood = "Salad";
}
public _favoriteFood: string;
public set favoriteFood(val) { this._favoriteFood = val; }
public get favoriteFood() {
if (!this._favoriteFood) {
this.init();
}
return this._favoriteFood;
}
public talkAboutFood() {
// init function automatically gets called just in time, because "favoriteFood" is a getter
console.log(`I love ${this.favoriteFood}`);
}
}
// overloading the init function works without having to call `init()` afterwards
class FoodieChild extends FoodieParent {
public init() {
this.favoriteFood = "Pizza"
}
}
Like this :
class A
{
myMember;
constructor() {
}
show() {
alert(this.myMember.value);
}
}
class B extends A {
public myMember = {value:1};
constructor() {
super();
}
}
const test = new B;
test.show();
I have a class A, and a class B inherited from it.
class A {
constructor(){
this.init();
}
init(){}
}
class B extends A {
private myMember = {value:1};
constructor(){
super();
}
init(){
console.log(this.myMember.value);
}
}
const x = new B();
When I run this code, I get the following error:
Uncaught TypeError: Cannot read property 'value' of undefined
How can I avoid this error?
It's clear for me that the JavaScript code will call the init method before it creates the myMember, but there should be some practice/pattern to make it work.
This is why in some languages (cough C#) code analysis tools flag usage of virtual members inside constructors.
In Typescript field initializations happen in the constructor, after the call to the base constructor. The fact that field initializations are written near the field is just syntactic sugar. If we look at the generated code the problem becomes clear:
function B() {
var _this = _super.call(this) || this; // base call here, field has not been set, init will be called
_this.myMember = { value: 1 }; // field init here
return _this;
}
You should consider a solution where init is either called from outside the instance, and not in the constructor:
class A {
constructor(){
}
init(){}
}
class B extends A {
private myMember = {value:1};
constructor(){
super();
}
init(){
console.log(this.myMember.value);
}
}
const x = new B();
x.init();
Or you can have an extra parameter to your constructor that specifies whether to call init and not call it in the derived class as well.
class A {
constructor()
constructor(doInit: boolean)
constructor(doInit?: boolean){
if(doInit || true)this.init();
}
init(){}
}
class B extends A {
private myMember = {value:1};
constructor()
constructor(doInit: boolean)
constructor(doInit?: boolean){
super(false);
if(doInit || true)this.init();
}
init(){
console.log(this.myMember.value);
}
}
const x = new B();
Or the very very very dirty solution of setTimeout, which will defer initialization until the current frame completes. This will let the parent constructor call to complete, but there will be an interim between constructor call and when the timeout expires when the object has not been inited
class A {
constructor(){
setTimeout(()=> this.init(), 1);
}
init(){}
}
class B extends A {
private myMember = {value:1};
constructor(){
super();
}
init(){
console.log(this.myMember.value);
}
}
const x = new B();
// x is not yet inited ! but will be soon
Because myMember property is accessed in parent constructor (init() is called during super() call), there is no way how it can be defined in child constructor without hitting a race condition.
There are several alternative approaches.
init hook
init is considered a hook that shouldn't be called in class constructor. Instead, it is called explicitly:
new B();
B.init();
Or it is called implicitly by the framework, as a part of application lifecycle.
Static property
If a property is supposed to be a constant, it can be static property.
This is the most efficient way because this is what static members are for, but the syntax may be not that attractive because it requires to use this.constructor instead of class name if static property should be properly referred in child classes:
class B extends A {
static readonly myMember = { value: 1 };
init() {
console.log((this.constructor as typeof B).myMember.value);
}
}
Property getter/setter
Property descriptor can be defined on class prototype with get/set syntax. If a property is supposed to be primitive constant, it can be just a getter:
class B extends A {
get myMember() {
return 1;
}
init() {
console.log(this.myMember);
}
}
It becomes more hacky if the property is not constant or primitive:
class B extends A {
private _myMember?: { value: number };
get myMember() {
if (!('_myMember' in this)) {
this._myMember = { value: 1 };
}
return this._myMember!;
}
set myMember(v) {
this._myMember = v;
}
init() {
console.log(this.myMember.value);
}
}
In-place initialization
A property may be initialized where it's accessed first. If this happens in init method where this can be accessed prior to B class constructor, this should happen there:
class B extends A {
private myMember?: { value: number };
init() {
this.myMember = { value: 1 };
console.log(this.myMember.value);
}
}
Asynchronous initialization
init method may become asynchronous. Initialization state should be trackable, so the class should implement some API for that, e.g. promise-based:
class A {
initialization = Promise.resolve();
constructor(){
this.init();
}
init(){}
}
class B extends A {
private myMember = {value:1};
init(){
this.initialization = this.initialization.then(() => {
console.log(this.myMember.value);
});
}
}
const x = new B();
x.initialization.then(() => {
// class is initialized
})
This approach may be considered antipattern for this particular case because initialization routine is intrinsically synchronous, but it may be suitable for asynchronous initialization routines.
Desugared class
Since ES6 classes have limitations on the use of this prior to super, child class can be desugared to a function to evade this limitation:
interface B extends A {}
interface BPrivate extends B {
myMember: { value: number };
}
interface BStatic extends A {
new(): B;
}
const B = <BStatic><Function>function B(this: BPrivate) {
this.myMember = { value: 1 };
return A.call(this);
}
B.prototype.init = function () {
console.log(this.myMember.value);
}
This is rarely a good option, because desugared class should be additionally typed in TypeScript. This also won't work with native parent classes (TypeScript es6 and esnext target).
One approach you could take is use a getter/setter for myMember and manage the default value in the getter. This would prevent the undefined problem and allow you to keep almost exactly the same structure you have. Like this:
class A {
constructor(){
this.init();
}
init(){}
}
class B extends A {
private _myMember;
constructor(){
super();
}
init(){
console.log(this.myMember.value);
}
get myMember() {
return this._myMember || { value: 1 };
}
set myMember(val) {
this._myMember = val;
}
}
const x = new B();
Try this:
class A {
constructor() {
this.init();
}
init() { }
}
class B extends A {
private myMember = { 'value': 1 };
constructor() {
super();
}
init() {
this.myMember = { 'value': 1 };
console.log(this.myMember.value);
}
}
const x = new B();
Super has to be first command. Remeber that typescript is more "javascript with documentation of types" rather than language on its own.
If you look to the transpiled code .js it is clearly visible:
class A {
constructor() {
this.init();
}
init() {
}
}
class B extends A {
constructor() {
super();
this.myMember = { value: 1 };
}
init() {
console.log(this.myMember.value);
}
}
const x = new B();
Do you have to call init in class A?
That works fine, but I don't know if you have different requirements:
class A {
constructor(){}
init(){}
}
class B extends A {
private myMember = {value:1};
constructor(){
super();
this.init();
}
init(){
console.log(this.myMember.value);
}
}
const x = new B();
More often than not, you can defer the call of init() to a time just before it is needed by hacking into one of your getters.
For example:
class FoodieParent {
public init() {
favoriteFood = "Salad";
}
public _favoriteFood: string;
public set favoriteFood(val) { this._favoriteFood = val; }
public get favoriteFood() {
if (!this._favoriteFood) {
this.init();
}
return this._favoriteFood;
}
public talkAboutFood() {
// init function automatically gets called just in time, because "favoriteFood" is a getter
console.log(`I love ${this.favoriteFood}`);
}
}
// overloading the init function works without having to call `init()` afterwards
class FoodieChild extends FoodieParent {
public init() {
this.favoriteFood = "Pizza"
}
}
Like this :
class A
{
myMember;
constructor() {
}
show() {
alert(this.myMember.value);
}
}
class B extends A {
public myMember = {value:1};
constructor() {
super();
}
}
const test = new B;
test.show();
Based on this awesome Composition over Inheritance video by MPJ, I've been trying to formulate composition in TypeScript. I want to compose classes, not objects or factory functions. Here is my effort so far (with a little help from lodash):
class Barker {
constructor(private state) {}
bark() {
console.log(`Woof, I am ${this.state.name}`);
}
}
class Driver {
constructor(private state) {}
drive() {
this.state.position = this.state.position + this.state.speed;
}
}
class Killer {
constructor(private state) {}
kill() {
console.log(`Burn the ${this.state.prey}`);
}
}
class MurderRobotDog {
constructor(private state) {
return _.assignIn(
{},
new Killer(state),
new Driver(state),
new Barker(state)
);
}
}
const metalhead = new MurderRobotDog({
name: 'Metalhead',
position: 0,
speed: 100,
prey: 'witch'
});
metalhead.bark(); // expected: "Woof, I am Metalhead"
metalhead.kill(); // expected: "Burn the witch"
This resulting in:
TS2339: Property 'bark' does not exist on type 'MurderRobotDog'
TS2339: Property 'kill' does not exist on type 'MurderRobotDog'
What's the right way of doing class composition in TypeScript?
Composition vs Inheritance
I think we should make a distinction between composition and inheritance and reconsider what we are trying to achieve. As a commenter pointed out, what MPJ does is actually an example of using mixins. This is basically a form of inheritance, adding implementation on the target object (mixing).
Multiple inheritance
I tried to come up with a neat way to do this and this is my best suggestion:
type Constructor<I extends Base> = new (...args: any[]) => I;
class Base {}
function Flies<T extends Constructor<Base>>(constructor: T = Base as any) {
return class extends constructor implements IFlies {
public fly() {
console.log("Hi, I fly!");
}
};
}
function Quacks<T extends Constructor<Base>>(constructor: T = Base as any) {
return class extends constructor implements ICanQuack {
public quack(this: IHasSound, loud: boolean) {
console.log(loud ? this.sound.toUpperCase() : this.sound);
}
};
}
interface IHasSound {
sound: string;
}
interface ICanQuack {
quack(loud: boolean): void;
}
interface IQuacks extends IHasSound, ICanQuack {}
interface IFlies {
fly(): void;
}
class MonsterDuck extends Quacks(Flies()) implements IQuacks, IFlies {
public sound = "quackly!!!";
}
class RubberDuck extends Quacks() implements IQuacks {
public sound = "quack";
}
const monsterDuck = new MonsterDuck();
monsterDuck.quack(true); // "QUACKLY!!!"
monsterDuck.fly(); // "Hi, I fly!"
const rubberDuck = new RubberDuck();
rubberDuck.quack(false); // "quack"
The benefit of using this approach is that you can allow access to certain properties of the owner object in the implementation of the inherited methods. Although a bit better naming could be use, I see this as a very potential solution.
Composition
Composition is instead of mixing the functions into the object, we set what behaviours should be contained in it instead, and then implement these as self-contained libraries inside the object.
interface IQuackBehaviour {
quack(): void;
}
interface IFlyBehaviour {
fly(): void;
}
class NormalQuack implements IQuackBehaviour {
public quack() {
console.log("quack");
}
}
class MonsterQuack implements IQuackBehaviour {
public quack() {
console.log("QUACK!!!");
}
}
class FlyWithWings implements IFlyBehaviour {
public fly() {
console.log("I am flying with wings");
}
}
class CannotFly implements IFlyBehaviour {
public fly() {
console.log("Sorry! Cannot fly");
}
}
interface IDuck {
flyBehaviour: IFlyBehaviour;
quackBehaviour: IQuackBehaviour;
}
class MonsterDuck implements IDuck {
constructor(
public flyBehaviour = new FlyWithWings(),
public quackBehaviour = new MonsterQuack()
) {}
}
class RubberDuck implements IDuck {
constructor(
public flyBehaviour = new CannotFly(),
public quackBehaviour = new NormalQuack()
) {}
}
const monsterDuck = new MonsterDuck();
monsterDuck.quackBehaviour.quack(); // "QUACK!!!"
monsterDuck.flyBehaviour.fly(); // "I am flying with wings"
const rubberDuck = new RubberDuck();
rubberDuck.quackBehaviour.quack(); // "quack"
As you can see, the practical difference is that the composites doesn't know of any properties existing on the object using it. This is probably a good thing, as it conforms to the principle of Composition over Inheritance.
Unfortunately, there is no easy way to do this. There is currently a proposal to allow for the extends keyword to allow you to do this, but it is still being talked about in this GitHub issue.
Your only other option is to use the Mixins functionality available in TypeScript, but the problem with that approach is that you have to re-define each function or method that you want to re-use from the "inherited" classes.