- Learning TypeScript 2.x
- Remo H. Jansen
- 543字
- 2025-04-04 17:02:05
Generic classes
In the previous chapter, we learned how to work with generic functions. Now, we will look at how to work with generic classes.
Just like with generic functions, generic classes can help us to avoid the duplication of code. Let's look at an example:
class User { public name!: string; public surname!: string; }
We have declared a class named User with two properties named name and password. We will now declare a class named UserQueue. A queue is a data structure that we can use to store a list of items. Items can be added at the end of the list and removed from the beginning of the list. For this reason, a queue is considered a first-in-first-out (FIFO) data structure. The UserQueue class doesn't use generics:
class UserQueue { private _items: User[] = []; public push(item: User) { this._items.push(item); } public pop() { return this._items.shift(); } public size() { return this._items.length; } }
Once we have finished declaring the UserQueue class, we can create an instance and invoke the push and pop methods to add and remove items, respectively:
const userQueue = new UserQueue(); userQueue.push({ name: "Remo", surname: "Jansen" }); userQueue.push({ name: "John", surname: "Smith" }); const remo = userQueue.pop(); const john = userQueue.pop();
If we also need to create a new queue with items of a different type, we could end up duplicating a lot of code that looks almost identical:
class Car { public manufacturer!: string; public model!: string; } class CarQueue { private _items: Car[] = []; public push(item: Car) { this._items.push(item); } public pop() { return this._items.shift(); } public size() { return this._items.length; } } const carQueue = new CarQueue(); carQueue.push({ manufacturer: "BMW", model: "M3" }); carQueue.push({ manufacturer: "Tesla", model: "S" }); const bmw = carQueue.pop(); const tesla = carQueue.pop();
If the number of entities grows, we will continue to repeatedly duplicate code. We could use the any type to avoid this problem, but then we would be losing the type checks at compile time:
class Queue { private _items: any[] = []; public push(item: any) { this._items.push(item); } public pop() { return this._items.shift(); } public size() { return this._items.length; } }
A much better solution is to create a generic queue:
class Queue<T> { private _items: T[] = []; public push(item: T) { this._items.push(item); } public pop() { return this._items.shift(); } public size() { return this._items.length; } }
The generic queue code is identical to UserQueue and CarQueue, except for the type of the items property. We have replaced the hardcoded reference to the User and Car entities and replaced them with the generic type T. We can now declare as many kinds of queues as we might need without duplicating a single line of code:
class User { public name!: string; public surname!: string; } const userQueue = new Queue<User>(); userQueue.push({ name: "Remo", surname: "Jansen" }); userQueue.push({ name: "John", surname: "Smith" }); const remo = userQueue.pop(); const john = userQueue.pop(); class Car { public manufacturer!: string; public model!: string; } const carQueue = new Queue<Car>(); carQueue.push({ manufacturer: "BMW", model: "M3" }); carQueue.push({ manufacturer: "Tesla", model: "S" }); const bmw = carQueue.pop(); const tesla = carQueue.pop();