Is it possible to define nested/inner classes in typescript? Yes, it is. Go to chapter 3 or 4 if you want to know only nested/inner classes.
Clone the repository if you want to try it on your own.
Problem to solve
I use Node-RED in my project and write lots of flow tests. There are multiple flow files and they have the same output node for different inputs. It means that I have to prepare the same expected data for the different flows. The expected data looks like the following.
const expected = {
payload: {
timestamp: "2021-06-20T10:15:18.123Z",
key: "status",
value: "running",
}
};
When the possible value is 1 – 5 I had to write all of them in the different tests. However, sometimes I forgot to write one of those tests. I didn’t want to repeat the mistake, so I decided to create common classes used for multiple flow tests. I less often forgot to write necessary tests in this way. The classes have functions that return possible values. Following is an example.
export class MachineStatus {
public static timestamp= "2021-06-20T10:15:18.123Z";
public static running() {
return {
payload: 1,
};
}
public static stop() {
return {
payload: 2,
};
}
}
export class OutputStatus {
private key = "status";
public static running(timestamp: string) {
return {
payload: {
timestamp,
key: this.key,
value: "running",
}
};
}
public static stop(timestamp: string) {
return {
payload: {
timestamp,
key: this.key,
value: "stop",
}
};
}
}
// Test file
const input = MachineStatus.running();
const expected = OutputStatus.running(MachineStatus.timestamp);
runTest(input, expected);
One test file uses MachineStatus
but another use AudioStatus
for example but both of them use OutputStatus
. I don’t have to change many files in this way even if I need to change the possible value from running
to Running
.
I created a test data class for each input but those classes look similar or the same. If its value is boolean the class has true
and false
. If its value is a number, the class has create(value: number)
or return100()
for example. There are many classes that are actually the same structure. This is the problem I want to solve. I want to remove the duplication.
Using extends keyword like type Alias
type alias is not available for class but we can use extends keyword instead.
Boolean base
Let’s see the actual code. Firstly, Boolean.
abstract class BooleanBase {
public static timestamp = "2021-06-20T10:15:18.123Z";
public static get true() {
return {
timestamp: BooleanBase.timestamp,
key: this.name,
payload: true,
};
}
public static get false() {
return {
timestamp: BooleanBase.timestamp,
key: this.name,
payload: false,
};
}
}
console.log("---BooleanBase---")
console.log(BooleanBase.true);
console.log(BooleanBase.false);
// ---BooleanBase---
// {
// timestamp: '2021-06-20T10:15:18.123Z',
// key: 'BooleanBase',
// payload: true
// }
// {
// timestamp: '2021-06-20T10:15:18.123Z',
// key: 'BooleanBase',
// payload: false
// }
This is the base class used by all boolean statuses. You can create a new item class by extends
.
class Light extends BooleanBase { }
console.log("---Light---")
console.log(Light.true);
console.log(Light.false);
// ---Light---
// { timestamp: '2021-06-20T10:15:18.123Z', key: 'Light', payload: true }
// { timestamp: '2021-06-20T10:15:18.123Z', key: 'Light', payload: false }
In this way, we can easily create new classes as many as we want. I’m wondering why the output format is different…
Number base
abstract class NumberBase {
public static timestamp = "2021-06-22T22:22:22.222Z";
public static create(data: number) {
return {
timestamp: NumberBase.timestamp,
key: this.name,
payload: data,
}
}
}
class Speed extends NumberBase { }
console.log("---NumberBase---")
console.log(NumberBase.create(12));
// ---NumberBase---
// {
// timestamp: '2021-06-22T22:22:22.222Z',
// key: 'NumberBase',
// payload: 12
// }
console.log("---Speed---")
console.log(Speed.create(120));
// ---Speed---
// { timestamp: '2021-06-22T22:22:22.222Z', key: 'Speed', payload: 120 }
Nested class or inner class
We need to put more than 2 items together for readability or maintainability. A nested class or inner class can use for it. However, there are other ways to do the same thing. It’s a function that returns an object that contains desired functions. Then, the class can have a property that has the same functions.
const globalTestTimestamp = "2021-06-20T11:11:11.111Z";
function createPayloadFuncs(key: string) {
return {
true: () => {
return {
timestamp: globalTestTimestamp,
key,
payload: true,
};
},
false: () => {
return {
timestamp: globalTestTimestamp,
key,
payload: false,
};
},
};
}
class Car {
public static FrontLight = class extends BooleanBase { };
public static BackLight = createPayloadFuncs("BackLight");
public AveSpeed = class extends NumberBase { };
public CurrentSpeed = class extends NumberBase { };
}
console.log("---Car---")
console.log(Car.BackLight.false());
console.log(Car.FrontLight.true);
// ---Car---
// {
// timestamp: '2021-06-20T11:11:11.111Z',
// key: 'BackLight',
// payload: false
// }
// {
// timestamp: '2021-06-20T10:15:18.123Z',
// key: 'BooleanBase',
// payload: true
// }
const car1 = new Car();
console.log(car1.AveSpeed.create(12));
console.log(car1.CurrentSpeed.create(80));
// {
// timestamp: '2021-06-22T22:22:22.222Z',
// key: 'NumberBase',
// payload: 12
// }
// {
// timestamp: '2021-06-22T22:22:22.222Z',
// key: 'NumberBase',
// payload: 80
// }
car1.extension.honk();
const car2 = Car.Factory.create();
car2.extension.honk();
Car.FrontLight.true
can be called like this while Car.BackLight.false()
needs ()
because createPayloadFuncs
returns object where we can’t set the property as a getter.
Look at the key output. this.name
returns the name of the base class even though it extends it.
console.log(Car.FrontLight.true);
// {
// timestamp: '2021-06-20T10:15:18.123Z',
// key: 'BooleanBase',
// payload: true
// }
I don’t know if it’s available to solve this with the current typescript version. The version that I used was 3.9.7.
Define Factory class as nested/inner class
Let’s see another example that might be useful to define nested/inner classes. It’s a factory class that wants to refer to private variables in the outer class.
class Car {
private static count = 0;
private constructor(private carNumber: number) { }
public extension = {
honk: () => console.log(`beeee: ${this.carNumber}`),
};
public static Factory = class {
public static create(): Car {
Car.count++;
return new Car(Car.count);
}
}
}
const car1 = Car.Factory.create();
car1.extension.honk();
// beeee: 1
const car2 = Car.Factory.create();
car2.extension.honk();
// beeee: 2
It works as expected and its name is clear to show that Car.Factory.create()
creates a new instance. However, this is not the best way in my opinion. I think it’s enough to define static factory function in Car
class. Please leave your comment if you know better use cases.
Comments