-
Book Overview & Buying
-
Table Of Contents
TypeScript 4 Design Patterns and Best Practices
By :
You now know how to work with VSCode and have a firm understanding of its code base and some examples. We will complete this chapter by learning about UML and how we can utilize it to study design patterns. We will focus on a limited set of UML, specifically class diagrams, since this is the traditional way to depict design patterns; plus, they are straightforward to comprehend.
UML is a standardized way of modeling software architecture concepts, as well as interactions between systems or deployment configurations. Nowadays, UML covers more areas, and it's fairly comprehensive. It came as a result of a consolidation of similar tools and modeling techniques, such as use cases, the Object Modeling Technique (OMT), and the Booch Method.
You don't really need to know all the ins and outs of UML, but it is really helpful when you're learning about design patterns. When you first learn about design patterns, you want to have a holistic overview of the patterns, irrespective of the implementation part, which will differ from language to language. Using UML class diagrams is a perfect choice for modeling our patterns in a design language that everyone can understand with minimal training.
Let's delve into more practical examples using TypeScript.
Note
Although UML diagrams have a long history in software engineering, you should use them carefully. Generally, they should only be used to demonstrate a specific use case or sub-system, together with a short explanation of the architecture decisions. UML is not very suitable for capturing the dynamic requirements of very complex systems because, as a visual language, it is only suitable for representing high-level overviews.
UML class diagrams consist of static representations of the classes or objects of a system. TypeScript supports classes and interfaces, as well as visibility modifiers (public, protected, or private) so that we can leverage those types to describe them with class diagrams. Here are some of the most fundamental concepts when studying class diagrams:
Product class looks like this:class Product {}This corresponds to the following diagram:
Figure 1.7 – Class representation
interface Identifiable<T extends string | number>{
id: T
}
class Product implements Identifiable<string> {
id: string
constructor(id: string) {
this.id = id;
}
}This corresponds to the following diagram. Notice the placement of the interface clause on top of the class name within the left shift (<<) and right shift (>>) symbols:
Figure 1.8 – Interface representation
abstract class BaseApiClient {}This corresponds to the following diagram. The name of the class is in italics:
Figure 1.9 – Abstract class representation
Blog and Author:class Blog implements Identifiable<string> {
id: string;
authorId: string;
constructor(id: string, authorId: string) {
this.id = id;
this.authorId = authorId;
}
}
class Author {}This corresponds to the following diagram. Blog is connected to Author with a line:
Figure 1.10 – Association representation
Notice that because the Author class here is not being passed as a parameter, it is referenced from the authorId parameter instead. This is an example of indirect association.
SearchService that accepts a QueryBuilder parameter and performs API requests on a different system:class QueryBuilder {}
class EmptyQueryBuilder extends QueryBuilder {}
interface SearchParams {
qb?: QueryBuilder;
path: string;
}
class SearchService {
queryBuilder?: QueryBuilder;
path: string;
constructor({ qb = EmptyQueryBuilder, path }:
SearchParams) {
this.queryBuilder = qb;
this.path = path;
}
}This corresponds to the following diagram. SearchService is connected to QueryBuilder with a line and a white rhombus:
Figure 1.11 – Aggregation representation
In this case, when we don't have a QueryBuilder or the class itself has no queries to perform, then SearchService will still exist, although it will not actually perform any requests. QueryBuilder can also exist without SearchService.
Composition is a stricter version of aggregation, where we have a parent component or class that will control the lifetime of its children. If the parent is removed from the system, then all the children will be removed as well. Here is an example with Directory and File:
class Directory {
files: File[];
directories: Directory[];
constructor(files: File[], directories: Directory[]) {
this.files = files;
this.directories = directories;
}
addFile(file: File): void {
this.files.push(file);
}
addDir(directory: Directory): void {
this.directories.push(directory);
}
}
This corresponds to the following diagram. Directory is connected to File with a line and a black or filled rhombus:
Figure 1.12 – Composition representation
class BaseClient {}
class UsersApiClient extends BaseClient {} This corresponds to the following diagram. UsersApiClient is connected to BaseClient with a line and a white pointed arrow:
Figure 1.13 – Inheritance representation
SSHUser class that accepts a private key and a public key:class SSHUser {
private privateKey: string;
public publicKey: string;
constructor(prvKey: string, pubKey: string) {
this.privateKey = prvKey;
this.publicKey = pubKey;
}
public getBase64(): string {
return Buffer.from(this.publicKey).toString
("base64");
}
}This corresponds to the following diagram. SSHUser contains two properties and one method. We use a minus (-) for private visibility and a plus (+) for public visibility:
Figure 1.14 – Visibility
Here, we can see that the methods are separated by a horizontal bar for visibility.
We can also add notes or comments to class diagrams, although it's not very clear if they should be included in the code:
Figure 1.15 – Comments representation
The main difficulty when using class diagrams is not drawing them on a piece of paper, but rather how to properly model the domain classes and relationships in a sound manner. This process is often iterative and involves interacting with several domain experts or knowledgeable stakeholders. In Chapter 8, Developing Modern and Robust TypeScript Applications, we are going to learn how domain-driven design can help us with modeling business rules.
Change the font size
Change margin width
Change background colour