When writing a class inside a TypeScript file, we first need to have in mind what this class will do, what this class can be for, how it can be extended by another class through inheritance, and how it can be affected in the process.
Imagine that we have a basic Animal class. This class can have some basic properties such as its name, whether it produces a sound, its family, and the basic food chain this animal eats.
- Let's start with the basics of the process, the food chain. We need to make sure that it's an innumerable list, and that each file that is using it will have the same value at the end. We just need to call a constant variable:
export enum FoodChainType {
Carnivorous = 'carnivorous',
Herbivorous = 'herbivorous',
Omnivorous = 'omnivorous',
}
- Now, we want to make the basic interface for our animal. We know that our animal has a name, can produce a sound, can be part of a family, and be in a food chain category. Using an interface in a class, we make a contract between the class and what will be exposed, helping in the development process:
interface IAnimal {
name: string;
sound?: string;
family: string;
foodChainType: FoodChainType;
}
- With all that settled, we can make our Animal class. Each class can have its constructor. The class constructor can be simple, containing just some variables as arguments, or can be more complex and have an object as an argument. If your constructor will have any parameters, an interface or declaring the type of each parameter is needed. In this case, our constructor will be an object and will have only one parameter that is the same as the Animal, so it will extend the IAnimal interface:
interface IAnimalConstructor extends IAnimal {
}
- Now, to make our class, we have declared the interfaces and enums that will be used. We will start by declaring that the class will implement the IBasicAnimal interface. To do this, we need to add some public elements that our class will have and declare those too. We will need to implement the functions to show what animal it is and what sound it makes. Now, we have a basic class that includes all the attributes for our animal. It has separate interfaces for the class and the constructors. The enum for the food chain is declared in a human-readable way, so the JavaScript imports of this library can execute without any problems:
interface IBasicAnimal extends IAnimal {
whoAmI: () => void;
makeSound: () => void;
}
export class Animal implements IBasicAnimal {
public name: string;
public sound: string;
public family: string;
public foodChainType: FoodChainType;
constructor(params: IAnimalConstructor) {
this.name = params.name;
this.sound = params.sound || '';
this.family = params.family;
this.foodChainType = params.foodChainType;
}
public whoAmI(): void {
console.log(`I am a ${this.name}, my family is ${this.family}.
My diet is ${this.foodChainType}.`);
if (this.sound) {
console.log([...Array(2).fill(this.sound)].join(', '));
}
}
public makeSound(): void {
console.log(this.sound);
}
}
- Let's extend this class with a few lines of code and transform this Animal into a Dog:
import {Animal, FoodChainType} from './Animal';
class Dog extends Animal {
constructor() {
super({
name: 'Dog',
sound: 'Wof!',
family: 'Canidae',
foodChainType: FoodChainType.Carnivorous,
});
}n
}
This is a simple way of extending a parent class and using the parent's definition of the child to compose a new class with almost the same interface as the parent.