├── .gitignore
├── Documentations
├── 01 - Intro.md
├── 02 - OOP.md
├── 03 - Class.md
├── 04 - Objects.md
├── 05 - Abstraction.md
├── 06 - Encapsulation.md
├── 07 - Inheritance.md
├── 08 - Polymorphism.md
├── 09 - SOLID.md
├── 10 - Single Responsibility.md
├── 11 - Open Closed.md
├── 12 - Liskov Substitution.md
├── 13 - Interface Segregation.md
├── 14 - Dependency Inversion.md
├── 15 - Design Patterns.md
├── 16 - Abstract Factory.md
├── 17 - Builder.md
├── 18 - Factory Method.md
├── 19 - Prototype.md
├── 20 - Singleton.md
├── 21 - Adaptor.md
├── 22 - Bridge.md
├── 23 - Composite.md
├── 24 - Decorator.md
├── 25 - Facade.md
├── 26 - Flyweight.md
├── 27 - Proxy.md
├── 28 - Chain of Responsibility.md
├── 29 - Command.md
├── 30 - Interpreter.md
├── 31 - Iterator.md
├── 32 - Mediator.md
├── 33 - Memento.md
├── 34 - Observer.md
├── 35 - State.md
├── 36 - Strategy.md
├── 37 - Template Method.md
├── 38 - Visitor.md
├── 39 - References.md
└── 40 - Contributing.md
├── Examples
├── DP
│ ├── AbstractFactory.ts
│ ├── Adapter.ts
│ ├── Bridge.ts
│ ├── Builder.ts
│ ├── ChainOfResponsibility.ts
│ ├── Command.ts
│ ├── Composite.ts
│ ├── Decorator.ts
│ ├── Facade.ts
│ ├── FactoryMethod.ts
│ ├── Flyweight.ts
│ ├── Interpreter.ts
│ ├── Iterator.ts
│ ├── Mediator.ts
│ ├── Memento.ts
│ ├── Observer.ts
│ ├── Prototype.ts
│ ├── Proxy.ts
│ ├── Singleton.ts
│ ├── State.ts
│ ├── Strategy.ts
│ ├── TemplateMethod.ts
│ └── Visitor.ts
└── SOLID
│ ├── +DependencyInversion.ts
│ ├── +InterfaceSegregation.ts
│ ├── +LiskovSubstitution.ts
│ ├── +OpenClosed.ts
│ ├── +SingleResponsibility.ts
│ ├── -DependencyInversion.ts
│ ├── -InterfaceSegregation.ts
│ ├── -LiskovSubstitution.ts
│ ├── -OpenClosed.ts
│ └── -SingleResponsibility.ts
├── README.md
├── biome.json
├── bun.lockb
├── html-generator.ts
├── package.json
├── script.ts
├── tsconfig.json
└── website
├── DankMono-Regular.ttf
├── index.html
├── styles.css
├── styles.scss
└── template.html
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
--------------------------------------------------------------------------------
/Documentations/01 - Intro.md:
--------------------------------------------------------------------------------
1 | # Object Oriented Programming Expert with TypeScript
2 |
3 | This repository is a complete guide and tutorial for the principles and techniques of object-oriented programming. It can be a reference for all interested in programming and software developers. You will find simple and practical examples in all sections to make the concepts easier to understand.
4 |
5 | ## Table of Contents
6 |
7 | 1. [Fundamentals](#fundamentals)
8 | - [What's Object-Oriented-Programming?](#whats-object-oriented-programming)
9 | - [Class](#class)
10 | - [Objects](#objects)
11 | - [Abstraction](#abstraction)
12 | - [Encapsulation](#encapsulation)
13 | - [Inheritance](#inheritance)
14 | - [Polymorphism](#polymorphism)
15 | 2. [SOLID Principles](#solid-principles)
16 | - [What's SOLID Meaning?](#whats-solid-meaning)
17 | - [Single Responsibility (SRP)](#1-single-responsibility-srp)
18 | - [Open/Closed (OCP)](#2-openclosed-ocp)
19 | - [Liskov Substitution (LSP)](#3-liskov-substitution-lsp)
20 | - [Interface Segregation (ISP)](#4-interface-segregation-isp)
21 | - [Dependency Inversion (DIP)](#5-dependency-inversion-dip)
22 | 3. [Design Patterns](#design-patterns)
23 | - [What's a Design Pattern?](#whats-a-design-pattern)
24 | - [Creational - Abstract Factory](#abstract-factory)
25 | - [Creational - Builder](#builder)
26 | - [Creational - Factory Method or Virtual Constructor](#factory-method)
27 | - [Creational - Prototype or Clone](#prototype)
28 | - [Creational - Singleton](#singleton)
29 | - [Structural - Adapter or Wrapper](#adapter-wrapper)
30 | - [Structural - Bridge](#bridge)
31 | - [Structural - Composite or Object Tree](#composite-object-tree)
32 | - [Structural - Decorator or Wrapper](#decorator-wrapper)
33 | - [Structural - Facade](#facade)
34 | - [Structural - Flyweight or Cache](#flyweight-cache)
35 | - [Structural - Proxy](#proxy)
36 | - [Behavioral - Chain of Responsibility](#chain-of-responsibility)
37 | - [Behavioral - Command or Action or Transaction](#command)
38 | - [Behavioral - Interpreter](#interpreter)
39 | - [Behavioral - Iterator](#iterator)
40 | - [Behavioral - Mediator or Intermediary or Controller](#mediator)
41 | - [Behavioral - Memento or Snapshot](#memento)
42 | - [Behavioral - Observer or Event-Subscriber or Listener](#observer)
43 | - [Behavioral - State](#state)
44 | - [Behavioral - Strategy](#strategy)
45 | - [Behavioral - Template Method](#template-method)
46 | - [Behavioral - Visitor](#visitor)
47 | 4. [References](#references)
48 | 5. [Contributing and Supporting](#contributing-and-supporting)
49 |
--------------------------------------------------------------------------------
/Documentations/02 - OOP.md:
--------------------------------------------------------------------------------
1 | ## Fundamentals
2 |
3 | ### What's Object-oriented-programming?
4 |
5 | Object-oriented programming (OOP) is a programming paradigm based on the concept of "objects", which can contain data and code: data in the form of fields (often known as attributes or properties), and code, in the form of procedures (often known as methods). There are 6 pillars of OOP, includes:
6 |
7 | 1. *Class*
8 | 2. *Objects*
9 | 3. *Data Abstraction*
10 | 4. *Encapsulation*
11 | 5. *Inheritance*
12 | 6. *Polymorphism*
13 |
--------------------------------------------------------------------------------
/Documentations/03 - Class.md:
--------------------------------------------------------------------------------
1 | ### Class
2 |
3 | > A class is a blueprint for creating objects (instances) that share common properties and behaviors. It serves as a template or a prototype from which objects are created. A class encapsulates data for the object and methods to operate on that data. It provides a way to structure and organize code in a modular and reusable manner.
4 |
5 | Here's a simple explanation of the concept of a class in TypeScript along with a real-world example:
6 |
7 | ```typescript
8 | class Task {
9 | // Properties
10 | private id: number;
11 | private title: string;
12 | private description: string;
13 | private dueDate: Date;
14 | private completed: boolean;
15 |
16 | // Constructor
17 | constructor(taskInfo: {
18 | id: number;
19 | title: string;
20 | description: string;
21 | dueDate: Date;
22 | }) {
23 | this.id = taskInfo.id;
24 | this.title = taskInfo.title;
25 | this.description = taskInfo.description;
26 | this.dueDate = taskInfo.dueDate;
27 | this.completed = false; // By default, the task is not completed
28 | }
29 |
30 | // Method to mark the task as completed
31 | public complete() {
32 | this.completed = true;
33 | }
34 |
35 | // Method to mark the task as incomplete
36 | public incomplete() {
37 | this.completed = false;
38 | }
39 | }
40 |
41 | // Create instances of the Task class using an object as a parameter
42 | const task1 = new Task({
43 | id: 1,
44 | title: "Finish report",
45 | description: "Write the quarterly report for the team meeting",
46 | dueDate: new Date("2120-03-25"),
47 | });
48 |
49 | const task2 = new Task({
50 | id: 2,
51 | title: "Buy groceries",
52 | description: "Buy milk, eggs, and bread",
53 | dueDate: new Date("2077-11-17"),
54 | });
55 |
56 | // Mark task1 as completed
57 | task1.complete();
58 |
59 | // Output task details
60 | console.log(task1);
61 | console.log(task2);
62 | ```
63 |
64 | In this example:
65 |
66 | We define a Task class with properties `id`, `title`, `description`, `dueDate`, and `completed`, along with methods `complete()` and `incomplete()` to mark tasks as completed or incomplete.
67 | We create instances of the Task class (task1 and task2) representing different tasks.
68 | We demonstrate marking task1 as completed and then output the details of both tasks.
69 |
--------------------------------------------------------------------------------
/Documentations/04 - Objects.md:
--------------------------------------------------------------------------------
1 | ### Objects
2 |
3 | > It is a basic unit of Object-Oriented Programming and represents the real-life entities. An Object is an instance of a Class. When a class is defined, no memory is allocated but when it is instantiated (i.e. an object is created) memory is allocated. An object has an identity, state, and behavior. Each object contains data and code to manipulate the data. Objects can interact without having to know details of each other’s data or code, it is sufficient to know the type of message accepted and type of response returned by the objects.
4 |
5 | For example *Dog* is a real-life object, which has some characteristics like color, breed, bark, sleep, and eats.
6 |
--------------------------------------------------------------------------------
/Documentations/05 - Abstraction.md:
--------------------------------------------------------------------------------
1 | ### Abstraction
2 |
3 | > Abstraction is a concept that allows you to focus on the essential attributes and behaviors of an object while hiding the unnecessary details. It involves representing only the relevant characteristics of an object, and hiding the complex implementation details from the user.
4 |
5 | Let's break this down with a simple example:
6 |
7 | Consider a car. When you think about a car, you don't need to know every intricate detail of how the engine works or how the transmission shifts gears in order to drive it. Instead, you focus on the essential features like steering, accelerating, and braking.
8 |
9 | In OOP, abstraction allows us to create a `Car` class that encapsulates these essential features without revealing the internal complexities. Here's a basic implementation:
10 |
11 | ```typescript
12 | class Car {
13 | private brand: string;
14 | private model: string;
15 | private speed: number;
16 |
17 | constructor(brand: string, model: string) {
18 | this.brand = brand;
19 | this.model = model;
20 | this.speed = 0;
21 | }
22 |
23 | public accelerate(): void {
24 | this.speed += 10;
25 | }
26 |
27 | public brake(): void {
28 | this.speed -= 10;
29 | }
30 |
31 | public getSpeed(): number {
32 | return this.speed;
33 | }
34 | }
35 |
36 | // Create a car object
37 | const myCar: Car = new Car("Toyota", "Camry");
38 |
39 | // Accelerate the car
40 | myCar.accelerate();
41 |
42 | // Get the current speed
43 | console.log("Current speed:", myCar.getSpeed());
44 | ```
45 |
46 | In this example:
47 |
48 | We have a `Car` class with attributes like `make`, `model`, and `speed`.
49 | We define methods like `accelerate` and `brake` to manipulate the speed of the car.
50 | The user interacts with the car object through these methods without needing to know how they are implemented internally.
51 | So, in essence, abstraction allows us to think about objects at a higher level of understanding, focusing on what they do rather than how they do it.
52 |
--------------------------------------------------------------------------------
/Documentations/06 - Encapsulation.md:
--------------------------------------------------------------------------------
1 | ### Encapsulation
2 |
3 | > Encapsulation is defined as the wrapping up of data under a single unit. It is the mechanism that binds together code and the data it manipulates. In Encapsulation, the variables or data of a class are hidden from any other class and can be accessed only through any member function of their class in which they are declared. As in encapsulation, the data in a class is hidden from other classes, so it is also known as data-hiding.
4 |
5 | Let's consider a simple example where encapsulation is used to control access to sensitive data. Imagine we have a `User` class representing users in a system, and we want to ensure that the user's password is not directly accessible from outside the class.
6 |
7 | ```typescript
8 | class User {
9 | private username: string;
10 | private password: string;
11 |
12 | constructor(username: string, password: string) {
13 | this.username = username;
14 | this.password = password;
15 | }
16 |
17 | // Method to authenticate user
18 | authenticate(enteredPassword: string): boolean {
19 | return enteredPassword === this.password;
20 | }
21 |
22 | // Method to change password
23 | changePassword(newPassword: string): void {
24 | this.password = newPassword;
25 | }
26 | }
27 |
28 | // Create a user
29 | const user = new User("Ahmad Jafari", "abcd1234");
30 |
31 | console.log(user.authenticate("12345678")); // Output: false
32 | console.log(user.authenticate("abcd1234")); // Output: true
33 |
34 | user.changePassword("Ab1234!?");
35 |
36 | console.log(user.authenticate("abcd1234")); // Output: false
37 | console.log(user.authenticate("Ab1234!?")); // Output: true
38 | ```
39 |
40 | In this example:
41 |
42 | - We define a `User` class with a private property `password`.
43 | - `password` property is encapsulated and cannot be directly accessed.
44 | - We provide public methods `authenticate()` to verify the user's password and `changePassword()` to allow users to change their password.
45 | - Accessing or modifying the password property directly from outside the class is not allowed due to its private access modifier.
46 | - Encapsulation ensures that sensitive data (password) is hidden and can only be accessed or modified through controlled methods, enhancing security and preventing unauthorized access or manipulation.
47 |
--------------------------------------------------------------------------------
/Documentations/07 - Inheritance.md:
--------------------------------------------------------------------------------
1 | ### Inheritance
2 |
3 | > Inheritance in OOP is a concept where a new class (called a subclass or derived class) is created based on an existing class (called a superclass or base class). The subclass inherits attributes and behaviors (methods) from its superclass, allowing it to reuse and extend the functionality of the superclass.
4 |
5 | Using the `Shape` class example:
6 |
7 | ```typescript
8 | // Base class representing a generic shape
9 | class Shape {
10 | x: number;
11 | y: number;
12 |
13 | constructor(x: number, y: number) {
14 | this.x = x;
15 | this.y = y;
16 | }
17 |
18 | // Method to describe the shape
19 | describe(): void {
20 | console.log(`This is a shape at position (${this.x}, ${this.y}).`);
21 | }
22 | }
23 | ```
24 |
25 | Here, `Shape` is the base class. It has properties like x and y, along with a method describe() to provide information about the shape.
26 |
27 | ```typescript
28 | // Derived class representing a circle
29 | class Circle extends Shape {
30 | radius: number;
31 |
32 | constructor(x: number, y: number, radius: number) {
33 | super(x, y); // Call the constructor of the superclass (Shape)
34 | this.radius = radius;
35 | }
36 |
37 | // Method to calculate area of the circle
38 | area(): number {
39 | return Math.PI * this.radius ** 2;
40 | }
41 | }
42 | ```
43 |
44 | In this example, `Circle` is the subclass, and it extends the `Shape` class. By using the extends keyword, Circle inherits all properties and methods from Shape. Additionally, Circle has its own property `radius` and method `area()` specific to circles.
45 |
46 | By utilizing inheritance, you can create a hierarchy of classes where subclasses inherit and extend the functionality of their superclass, promoting code reusability and maintaining a logical structure in your programs.
47 |
--------------------------------------------------------------------------------
/Documentations/08 - Polymorphism.md:
--------------------------------------------------------------------------------
1 | ### Polymorphism
2 |
3 | > Polymorphism in OOP refers to the ability of different objects to be treated as instances of a common superclass. Simply put, it allows objects of different classes to be treated as objects of a shared superclass. This enables more flexible and dynamic code, as different objects can respond to the same method call in different ways.
4 |
5 | Let's consider a real-world example involving shapes. We'll create a program that calculates the area of different shapes such as rectangles, circles, and triangles using polymorphism.
6 |
7 | ```typescript
8 | // Parent class
9 | abstract class Shape {
10 | abstract calculateArea(): number;
11 | }
12 |
13 | // Rectangle class
14 | class Rectangle extends Shape {
15 | constructor(private width: number, private height: number) {
16 | super();
17 | }
18 |
19 | calculateArea(): number {
20 | return this.width * this.height;
21 | }
22 | }
23 |
24 | // Circle class
25 | class Circle extends Shape {
26 | constructor(private radius: number) {
27 | super();
28 | }
29 |
30 | calculateArea(): number {
31 | return Math.PI * this.radius ** 2;
32 | }
33 | }
34 |
35 | // Triangle class
36 | class Triangle extends Shape {
37 | constructor(private base: number, private height: number) {
38 | super();
39 | }
40 |
41 | calculateArea(): number {
42 | return 0.5 * this.base * this.height;
43 | }
44 | }
45 |
46 | // Function to calculate the area of any shape
47 | function calculateShapeArea(shape: Shape): number {
48 | return shape.calculateArea();
49 | }
50 |
51 | // Creating instances of different shapes
52 | const rectangle = new Rectangle(5, 10);
53 | const circle = new Circle(7);
54 | const triangle = new Triangle(4, 6);
55 |
56 | // Using the function with different shape objects
57 | console.log("Area of Rectangle:", calculateShapeArea(rectangle)); // Outputs: 50
58 | console.log("Area of Circle:", calculateShapeArea(circle).toFixed(2)); // Outputs: 153.94
59 | console.log("Area of Triangle:", calculateShapeArea(triangle)); // Outputs: 12
60 | ```
61 |
62 | In this example, `Shape` is the superclass, and `Rectangle`, `Circle`, and `Triangle` are its subclasses. They all implement the `calculateArea()` method differently according to their specific shapes. When we call `calculateShapeArea()` with different shape objects, polymorphism allows the correct version of `calculateArea()` to be called based on the type of shape passed. This demonstrates how polymorphism enables code to handle different types of objects in a unified manner.
63 |
--------------------------------------------------------------------------------
/Documentations/09 - SOLID.md:
--------------------------------------------------------------------------------
1 | ## SOLID Principles
2 |
3 | ### What's SOLID meaning?
4 |
5 | In software engineering, SOLID is a mnemonic acronym for five design principles intended to make software designs more understandable, flexible, and maintainable. The principles are a subset of many principles promoted by American software engineer and instructor Robert C. Martin, first introduced in his 2000 paper Design Principles and Design Patterns.
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Documentations/10 - Single Responsibility.md:
--------------------------------------------------------------------------------
1 | ### 1. Single Responsibility (SRP)
2 |
3 | #### Original Definition
4 |
5 | > There should never be more than one reason for a class to change. Every class should have only one responsibility.
6 |
7 | #### Simple Definition
8 |
9 | > SRP means that each class should only be responsible for one thing. It keeps classes focused and makes code easier to understand and maintain.
10 |
11 |
12 |
13 | #### Example
14 |
15 | Before following the Single Responsibility Principle (SRP), the `Profile` class was handling both user profile data (like email, bio, etc.) and user settings (theme and preferredLanguage). This violated SRP because a class should have only one reason to change, but here the Profile class had multiple reasons to change - if either the profile data structure or the settings structure changed.
16 |
17 | After following SRP, the code was refactored to separate concerns. The Profile class now only deals with profile-related information such as email and bio. The settings-related functionality has been moved to a new Settings class. This change improves maintainability and makes the codebase more flexible. Now, if there's a need to update how settings are handled, it only affects the Settings class, keeping the Profile class untouched. Additionally, it enhances code readability and makes it easier to understand the purpose of each class.
18 |
19 | ##### ❌ Before following SRP:
20 |
21 | [EXAMPLE-FILE-ADDRESS](/Examples/SOLID/-SingleResponsibility.ts)
22 |
23 | ##### ✔️ After following SRP:
24 |
25 | [EXAMPLE-FILE-ADDRESS](/Examples/SOLID/+SingleResponsibility.ts)
26 |
--------------------------------------------------------------------------------
/Documentations/11 - Open Closed.md:
--------------------------------------------------------------------------------
1 | ### 2. Open/Closed (OCP)
2 |
3 | #### Original Definition
4 |
5 | > Software entities should be open for extension, but closed for modification.
6 |
7 | #### Simple Definition
8 |
9 | > The Open/Closed Principle means that once you write a piece of code, you should be able to add new functionality to it without changing the existing code. It promotes extending the behavior of software rather than altering it, ensuring that changes don't break existing functionality.
10 |
11 |
12 |
13 | #### Example
14 |
15 | Before OCP implementation, the `QueryGenerator` class directly handles different types of databases, such as _MySQL_, _Redis_, and _Neo4j_, within its methods. This violates the Open/Closed Principle because if you want to add support for a new database, you would need to modify the `QueryGenerator` class by adding a new case to each switch statement. This could lead to the class becoming bloated and tightly coupled to specific database implementations, making it harder to maintain and extend.
16 |
17 | After implementing OCP, the code is refactored to use interfaces and separate classes for each database type. Now, the QueryGenerator interface defines common methods `getReadingQuery` and `getWritingQuery`, while individual database classes (`MySql`, `Redis`, and `Neo4j`) implement these methods according to their specific behavior.
18 |
19 | This approach adheres to the Open/Closed Principle because the `QueryGenerator` interface is open for extension, allowing you to add support for new databases by creating new classes that implement the interface, without modifying existing code. Additionally, it's closed for modification because changes to existing database classes won't affect the `QueryGenerator` interface or other database implementations. This results in a more flexible, maintainable, and scalable design.
20 |
21 | ##### ❌ Before following OCP:
22 |
23 | [EXAMPLE-FILE-ADDRESS](/Examples/SOLID/-OpenClosed.ts)
24 |
25 | ##### ✔️ After following OCP:
26 |
27 | [EXAMPLE-FILE-ADDRESS](/Examples/SOLID/+OpenClosed.ts)
28 |
--------------------------------------------------------------------------------
/Documentations/12 - Liskov Substitution.md:
--------------------------------------------------------------------------------
1 | ### 3. Liskov Substitution (LSP)
2 |
3 | #### Original Definition
4 |
5 | > If `S` is a subtype of `T`, then objects of type `T` in a program may be replaced with objects of type `S` without altering any of the desirable properties of that program.
6 |
7 | #### Simple Definition
8 |
9 | > The LSP says that if you have a class, you should be able to use any of its subclasses interchangeably without breaking the program.
10 |
11 |
12 |
13 | #### Example
14 |
15 | In the initial example, we have an `ImageProcessor` class responsible for various image processing operations such as **compression**, **enhancing size**, **removing background**, and **enhancing quality with AI**. There's also a `LimitedImageProcessor` class that extends `ImageProcessor`, but it overrides the `removeBackground` and `enhanceQualityWithAI` methods to throw errors indicating that these features are only available in the premium version.
16 |
17 | This violates the Liskov Substitution Principle because substituting an instance of `LimitedImageProcessor` for an instance of `ImageProcessor` could lead to unexpected errors if code relies on those overridden methods.
18 |
19 | To adhere to the LSP, we refactor the classes. We create a `PremiumImageProcessor` class that extends `ImageProcessor` and implements the `removeBackground` and `enhanceQualityWithAI` methods. This way, both classes share a common interface and substituting an instance of `PremiumImageProcessor` for an instance of `ImageProcessor` won't break the program's correctness.
20 |
21 | In the refactored version, `ImageProcessor` is now focused on basic image processing operations like compression and enhancing size, while `PremiumImageProcessor` extends it to include premium features like removing background and enhancing quality with AI. This separation allows for better code organization and adherence to the Liskov Substitution Principle.
22 |
23 | ##### ❌ Before following LSP:
24 |
25 | [EXAMPLE-FILE-ADDRESS](/Examples/SOLID/-LiskovSubstitution.ts)
26 |
27 | ##### ✔️ After following LSP:
28 |
29 | [EXAMPLE-FILE-ADDRESS](/Examples/SOLID/+LiskovSubstitution.ts)
30 |
--------------------------------------------------------------------------------
/Documentations/13 - Interface Segregation.md:
--------------------------------------------------------------------------------
1 | ### 4. Interface Segregation (ISP)
2 |
3 | #### Original Definition
4 |
5 | > No code should be forced to depend on methods it does not use.
6 |
7 | #### Simple Definition
8 |
9 | > The ISP means that clients should not be forced to implement methods they don't use. It's like saying, "Don't make people take things they don't need."
10 |
11 |
12 |
13 | #### Example
14 |
15 | In the initial implementation before applying the ISP, the `VPNConnection` interface encompasses methods for various VPN protocols, including `useL2TP`, `useOpenVPN`, `useV2Ray`, and `useShadowsocks`. However, not all classes implementing this interface require all these methods. For instance, the `InternalNetwork` class only utilizes `useL2TP` and `useOpenVPN`, yet it is forced to implement all methods defined in the `VPNConnection` interface, leading to unnecessary dependencies and potential errors if methods are called inappropriately.
16 |
17 | To address this issue, the Interface Segregation Principle suggests breaking down the monolithic interface into smaller, more focused interfaces. In the improved implementation, two interfaces are introduced: `BaseVPNConnection` and `ExtraVPNConnection`. The `BaseVPNConnection` interface contains methods common to both external and internal networks (`useL2TP` and `useOpenVPN`), while the `ExtraVPNConnection` interface includes methods specific to external networks (`useV2Ray` and `useShadowsocks`).
18 |
19 | With this segregation, the `InternalNetwork` class now only needs to implement the methods relevant to its functionality, adhering to the principle of "clients should not be forced to depend on interfaces they do not use." This restructuring enhances code clarity, reduces unnecessary dependencies, and makes the system more maintainable and flexible. Additionally, it mitigates the risk of errors by ensuring that classes only expose the methods they actually support, promoting better encapsulation and separation of concerns.
20 |
21 | ##### ❌ Before following ISP:
22 |
23 | [EXAMPLE-FILE-ADDRESS](/Examples/SOLID/-InterfaceSegregation.ts)
24 |
25 | ##### ✔️ After following ISP:
26 |
27 | [EXAMPLE-FILE-ADDRESS](/Examples/SOLID/+InterfaceSegregation.ts)
28 |
--------------------------------------------------------------------------------
/Documentations/14 - Dependency Inversion.md:
--------------------------------------------------------------------------------
1 | ### 5. Dependency Inversion (DIP)
2 |
3 | #### Original Definition
4 |
5 | > High-level modules should not import anything from low-level modules. Both should depend on abstractions. Abstractions should not depend on details. Details (concrete implementations) should depend on abstractions.
6 |
7 | #### Simple Definition
8 |
9 | > DIP means that instead of high-level modules depending directly on low-level modules, both should depend on abstractions. This way, changes in low-level modules don't directly affect high-level ones, promoting flexible and maintainable code.
10 |
11 |
12 |
13 | #### Example
14 |
15 | In the original code, the `Messenger` class directly depends on specific implementations of messaging APIs like `TelegramApi`, `WhatsappApi`, and `SignalApi`. This tightly couples Messenger with these concrete implementations, making it difficult to change or extend the system without modifying the Messenger class itself. This violates the Dependency Inversion Principle (DIP), which suggests that high-level modules should not depend on low-level modules but rather on abstractions.
16 |
17 | To adhere to DIP, we introduce an interface called `MessengerApi`, which defines the methods that the Messenger class requires from a messaging API. Then, each messaging API class (`TelegramApi`, `WhatsappApi` and `SignalApi`) implements this interface, providing their own implementation of the connect and send methods.
18 |
19 | By doing this, we decouple the Messenger class from specific messaging API implementations. Now, Messenger depends on the MessengerApi interface rather than concrete implementations. This allows us to easily switch between different messaging APIs or add new ones without modifying the Messenger class. Additionally, it promotes code reusability and simplifies testing, as we can now easily mock the MessengerApi interface for testing purposes. Overall, following DIP enhances the flexibility, maintainability, and testability of the codebase.
20 |
21 | ##### ❌ Before following DIP:
22 |
23 | [EXAMPLE-FILE-ADDRESS](/Examples/SOLID/-DependencyInversion.ts)
24 |
25 | ##### ✔️ After following DIP:
26 |
27 | [EXAMPLE-FILE-ADDRESS](/Examples/SOLID/+DependencyInversion.ts)
28 |
--------------------------------------------------------------------------------
/Documentations/15 - Design Patterns.md:
--------------------------------------------------------------------------------
1 | ## Design Patterns
2 |
3 | ### What's a design pattern?
4 |
5 | In software engineering, a software design pattern is a general, reusable solution to a commonly occurring problem within a given context in software design. It is not a finished design that can be transformed directly into source or machine code. Rather, it is a description or template for how to solve a problem that can be used in many different situations. Design patterns are formalized best practices that the programmer can use to solve common problems when designing an application or system.
6 |
7 | There are 23 design patterns that are grouped into 3 categories:
8 |
9 | 1. **Creational**: Creational patterns provide various object creation mechanisms, which increase flexibility and reuse of existing code. Includes:
10 |
11 | - Abstract Factory
12 | - Builder
13 | - Factory Method
14 | - Prototype
15 | - Singleton
16 |
17 | 2. **Structural**: Structural patterns explain how to assemble objects and classes into larger structures while keeping these structures flexible and efficient. Includes:
18 |
19 | - Adapter
20 | - Bridge
21 | - Composite
22 | - Decorator
23 | - Facade
24 | - Flyweight
25 | - Proxy
26 |
27 | 3. **Behavioral**: Behavioral design patterns are concerned with algorithms and the assignment of responsibilities between objects. Includes:
28 |
29 | - Chain of Responsibility
30 | - Command
31 | - Interpreter
32 | - Iterator
33 | - Mediator
34 | - Memento
35 | - Observer
36 | - State
37 | - Strategy
38 | - Template Method
39 | - Visitor
40 |
41 | **Tip**: The order of design patterns isn't important. So, you can choose which one to learn, regardless of the category.
42 |
--------------------------------------------------------------------------------
/Documentations/16 - Abstract Factory.md:
--------------------------------------------------------------------------------
1 | ### Abstract Factory
2 |
3 | > Abstract Factory is a creational design pattern that lets you produce families of related objects without specifying their concrete classes. This pattern is particularly useful when a system needs to be independent of the way its products are created, composed, and represented. It also helps in enforcing that a set of products follow a consistent theme across different platforms.
4 |
5 |
6 |
7 | #### Example Context
8 |
9 | In this example, we demonstrate the Abstract Factory pattern through a media API system where different types of API providers (`Normal` and `Premium`) can be generated for movies and audio tracks. Each provider offers multiple ways to search within its respective domain.
10 |
11 | - **Interfaces Defined:**
12 | - `Movie`: Represents the structure of a movie object.
13 | - `AudioTrack`: Represents the structure of an audio track object.
14 | - `MovieApi` and `AudioApi`: Define the set of operations available for interacting with movies and audio tracks, respectively.
15 |
16 | - **Concrete Implementations:**
17 | - `NormalMovieApiProvider` and `NormalAudioApiProvider`: Implement the `MovieApi` and `AudioApi` interfaces with standard search operations.
18 | - `PremiumMovieApiProvider` and `PremiumAudioApiProvider`: Similar to the normal providers but designed to represent premium service capabilities.
19 |
20 | - **Factory Interfaces and Implementations:**
21 | - `ApiProviderFactory`: Defines the methods for creating movie and audio API providers.
22 | - `NormalApiProviderFactory` and `PremiumApiProviderFactory`: Implementations of the factory interface that create instances of normal and premium API providers.
23 |
24 | ##### Purpose
25 |
26 | This pattern allows for the dynamic creation of `MovieApi` and `AudioApi` services that adhere to whether the user has access to normal or premium features. The flexibility provided by the Abstract Factory pattern makes it easier to extend and maintain the system, as new provider types can be added with minimal changes to existing code.
27 |
28 | ##### How It Works
29 |
30 | - A client first decides on the type of factory to use (`NormalApiProviderFactory` or `PremiumApiProviderFactory`).
31 | - The factory then creates instances of `MovieApi` and `AudioApi` providers based on the chosen type.
32 | - From there, the client can utilize these providers to perform various search operations tailored to their specific needs.
33 |
34 | By abstracting the creation process, the code is cleaner and adheres to SOLID principles, making it a flexible solution for varying user tiers in media applications.
35 |
36 | [EXAMPLE-FILE-ADDRESS](/Examples/DP/AbstractFactory.ts)
37 |
--------------------------------------------------------------------------------
/Documentations/17 - Builder.md:
--------------------------------------------------------------------------------
1 | ### Builder
2 |
3 | > Builder is a creational design pattern that lets you construct complex objects step by step. The pattern allows you to produce different types and representations of an object using the same construction code.
4 |
5 |
6 |
7 | #### Example Context
8 |
9 | In this example, we illustrate the Builder pattern through the creation of a `Page` object, which can represent different types of web pages with varying headers, bodies, and footers. We utilize builders to construct pages for specific purposes, like a personal blog or an online shop.
10 |
11 | - **Main Class:**
12 | - `Page`: Represents a web page composed of header, body, and footer parts. It provides methods to set these parts.
13 |
14 | - **Builder Interface:**
15 | - `PageBuilder`: Declares the generic methods for constructing different parts of a `Page` object, including the header, body, and footer.
16 |
17 | - **Concrete Builders:**
18 | - `PersonalBlogPageBuilder`: Implements the `PageBuilder` interface to construct a page suitable for a personal blog. The header, body, and footer have blog-specific parts.
19 | - `OnlineShopPageBuilder`: Another implementation of the `PageBuilder` interface for creating a page suited for an online shop, with distinct sections in the header, body, and footer.
20 |
21 | ##### Purpose
22 |
23 | The Builder pattern is employed here to manage the construction of a `Page` object that may consist of various optional parts, which allows for more flexible and maintainable code. By using different builders, we can easily create different representations of a page without altering the underlying logic and construction process.
24 |
25 | ##### How It Works
26 |
27 | - **Initialization**: Each specific `PageBuilder` implementation initializes a new `Page` object.
28 | - **Building Process**: The client calls the builder's methods to set up each part of the page:
29 | - `buildHeader()`: Constructs the header based on the page type.
30 | - `buildBody()`: Adds the body components specific to the page context.
31 | - `buildFooter()`: Defines the footer layout.
32 |
33 | Upon completion, the `getPage()` method is used to retrieve the fully constructed `Page` object.
34 |
35 | This organized step-by-step construction process makes it easier to create complex page objects that can be adapted and extended to accommodate new requirements without modifying existing code directly, following SOLID principles effectively.
36 |
37 | [EXAMPLE-FILE-ADDRESS](/Examples/DP/Builder.ts)
38 |
--------------------------------------------------------------------------------
/Documentations/18 - Factory Method.md:
--------------------------------------------------------------------------------
1 | ### Factory Method
2 |
3 | > Factory Method is a creational design pattern that provides an interface for creating objects in a superclass, but allows subclasses to alter the type of objects that will be created.
4 |
5 |
6 |
7 | [EXAMPLE-FILE-ADDRESS](/Examples/DP/FactoryMethod.ts)
8 |
--------------------------------------------------------------------------------
/Documentations/19 - Prototype.md:
--------------------------------------------------------------------------------
1 | ### Prototype
2 |
3 | > Prototype is a creational design pattern that lets you copy existing objects without making your code dependent on their classes.
4 |
5 |
6 |
7 | [EXAMPLE-FILE-ADDRESS](/Examples/DP/Prototype.ts)
8 |
--------------------------------------------------------------------------------
/Documentations/20 - Singleton.md:
--------------------------------------------------------------------------------
1 | ### Singleton
2 |
3 | > Singleton is a creational design pattern that lets you ensure that a class has only one instance, while providing a global access point to this instance.
4 |
5 |
6 |
7 | [EXAMPLE-FILE-ADDRESS](/Examples/DP/Singleton.ts)
8 |
--------------------------------------------------------------------------------
/Documentations/21 - Adaptor.md:
--------------------------------------------------------------------------------
1 | ### Adapter (Wrapper)
2 |
3 | > Adapter is a structural design pattern that allows objects with incompatible interfaces to collaborate.
4 |
5 |
6 |
7 | [EXAMPLE-FILE-ADDRESS](/Examples/DP/Adapter.ts)
8 |
--------------------------------------------------------------------------------
/Documentations/22 - Bridge.md:
--------------------------------------------------------------------------------
1 | ### Bridge
2 |
3 | > Bridge is a structural design pattern that lets you split a large class or a set of closely related classes into two separate hierarchies—abstraction and implementation—which can be developed independently of each other.
4 |
5 |
6 |
7 | [EXAMPLE-FILE-ADDRESS](/Examples/DP/Bridge.ts)
8 |
--------------------------------------------------------------------------------
/Documentations/23 - Composite.md:
--------------------------------------------------------------------------------
1 | ### Composite (Object Tree)
2 |
3 | > Composite is a structural design pattern that lets you compose objects into tree structures and then work with these structures as if they were individual objects.
4 |
5 |
6 |
7 | [EXAMPLE-FILE-ADDRESS](/Examples/DP/Composite.ts)
8 |
--------------------------------------------------------------------------------
/Documentations/24 - Decorator.md:
--------------------------------------------------------------------------------
1 | ### Decorator (Wrapper)
2 |
3 | > Adapter is a structural design pattern that allows objects with incompatible interfaces to collaborate.
4 |
5 |
6 |
7 | [EXAMPLE-FILE-ADDRESS](/Examples/DP/Decorator.ts)
8 |
--------------------------------------------------------------------------------
/Documentations/25 - Facade.md:
--------------------------------------------------------------------------------
1 | ### Facade
2 |
3 | > Facade is a structural design pattern that provides a simplified interface to a library, a framework, or any other complex set of classes.
4 |
5 |
6 |
7 | [EXAMPLE-FILE-ADDRESS](/Examples/DP/Facade.ts)
8 |
--------------------------------------------------------------------------------
/Documentations/26 - Flyweight.md:
--------------------------------------------------------------------------------
1 | ### Flyweight (Cache)
2 |
3 | > Flyweight is a structural design pattern that lets you fit more objects into the available amount of RAM by sharing common parts of state between multiple objects instead of keeping all of the data in each object.
4 |
5 |
6 |
7 | [EXAMPLE-FILE-ADDRESS](/Examples/DP/Flyweight.ts)
8 |
--------------------------------------------------------------------------------
/Documentations/27 - Proxy.md:
--------------------------------------------------------------------------------
1 | ### Proxy
2 |
3 | > Proxy is a structural design pattern that lets you provide a substitute or placeholder for another object. A proxy controls access to the original object, allowing you to perform something either before or after the request gets through to the original object.
4 |
5 |
6 |
7 | [EXAMPLE-FILE-ADDRESS](/Examples/DP/Proxy.ts)
8 |
--------------------------------------------------------------------------------
/Documentations/28 - Chain of Responsibility.md:
--------------------------------------------------------------------------------
1 | ### Chain of Responsibility
2 |
3 | > Chain of Responsibility is a behavioral design pattern that lets you pass requests along a chain of handlers. Upon receiving a request, each handler decides either to process the request or to pass it to the next handler in the chain.
4 |
5 |
6 |
7 | [EXAMPLE-FILE-ADDRESS](/Examples/DP/ChainOfResponsibility.ts)
8 |
--------------------------------------------------------------------------------
/Documentations/29 - Command.md:
--------------------------------------------------------------------------------
1 | ### Command
2 |
3 | > Command is a behavioral design pattern that turns a request into a stand-alone object that contains all information about the request. This transformation lets you pass requests as a method arguments, delay or queue a request’s execution, and support undoable operations.
4 |
5 |
6 |
7 | [EXAMPLE-FILE-ADDRESS](/Examples/DP/Command.ts)
8 |
--------------------------------------------------------------------------------
/Documentations/30 - Interpreter.md:
--------------------------------------------------------------------------------
1 | ### Interpreter
2 |
3 | > Interpreter is a behavioral design pattern that provides a way to interpret and evaluate sentences or expressions in a language. This pattern defines a language grammar, along with an interpreter that can parse and execute the expressions.
4 |
5 |
6 |
7 | [EXAMPLE-FILE-ADDRESS](/Examples/DP/Interpreter.ts)
8 |
--------------------------------------------------------------------------------
/Documentations/31 - Iterator.md:
--------------------------------------------------------------------------------
1 | ### Iterator
2 |
3 | > Iterator is a behavioral design pattern that lets you traverse elements of a collection without exposing its underlying representation (list, stack, tree, etc.).
4 |
5 |
6 |
7 | [EXAMPLE-FILE-ADDRESS](/Examples/DP/Iterator.ts)
8 |
--------------------------------------------------------------------------------
/Documentations/32 - Mediator.md:
--------------------------------------------------------------------------------
1 | ### Mediator
2 |
3 | > Mediator is a behavioral design pattern that lets you reduce chaotic dependencies between objects. The pattern restricts direct communications between the objects and forces them to collaborate only via a mediator object.
4 |
5 |
6 |
7 | [EXAMPLE-FILE-ADDRESS](/Examples/DP/Mediator.ts)
8 |
--------------------------------------------------------------------------------
/Documentations/33 - Memento.md:
--------------------------------------------------------------------------------
1 | ### Memento
2 |
3 | > Memento is a behavioral design pattern that lets you save and restore the previous state of an object without revealing the details of its implementation.
4 |
5 |
6 |
7 | [EXAMPLE-FILE-ADDRESS](/Examples/DP/Memento.ts)
8 |
--------------------------------------------------------------------------------
/Documentations/34 - Observer.md:
--------------------------------------------------------------------------------
1 | ### Observer
2 |
3 | > Observer is a behavioral design pattern that lets you define a subscription mechanism to notify multiple objects about any events that happen to the object they’re observing.
4 |
5 |
6 |
7 | [EXAMPLE-FILE-ADDRESS](/Examples/DP/Observer.ts)
8 |
--------------------------------------------------------------------------------
/Documentations/35 - State.md:
--------------------------------------------------------------------------------
1 | ### State
2 |
3 | > State is a behavioral design pattern that lets an object alter its behavior when its internal state changes. It appears as if the object changed its class.
4 |
5 |
6 |
7 | [EXAMPLE-FILE-ADDRESS](/Examples/DP/State.ts)
8 |
--------------------------------------------------------------------------------
/Documentations/36 - Strategy.md:
--------------------------------------------------------------------------------
1 | ### Strategy
2 |
3 | > Strategy is a behavioral design pattern that lets you define a family of algorithms, put each of them into a separate class, and make their objects interchangeable.
4 |
5 |
6 |
7 | [EXAMPLE-FILE-ADDRESS](/Examples/DP/Strategy.ts)
8 |
--------------------------------------------------------------------------------
/Documentations/37 - Template Method.md:
--------------------------------------------------------------------------------
1 | ### Template Method
2 |
3 | > Template Method is a behavioral design pattern that defines the skeleton of an algorithm in the superclass but lets subclasses override specific steps of the algorithm without changing its structure.
4 |
5 |
6 |
7 | [EXAMPLE-FILE-ADDRESS](/Examples/DP/TemplateMethod.ts)
8 |
--------------------------------------------------------------------------------
/Documentations/38 - Visitor.md:
--------------------------------------------------------------------------------
1 | ### Visitor
2 |
3 | > Visitor is a behavioral design pattern that lets you separate algorithms from the objects on which they operate.
4 |
5 |
6 |
7 | [EXAMPLE-FILE-ADDRESS](/Examples/DP/Visitor.ts)
8 |
--------------------------------------------------------------------------------
/Documentations/39 - References.md:
--------------------------------------------------------------------------------
1 | ## References
2 |
3 | In creating this repository, I aimed to provide original examples to facilitate learning about object-oriented programming (OOP) pillars, SOLID principles, and design patterns using TypeScript. While there may be similarities between examples found in this repository and those in other resources, it's essential to emphasize that all code and documentation within this repository are original creations.
4 |
5 | The following list includes resources that have inspired and informed my understanding of OOP, SOLID principles, and design patterns:
6 |
7 | 1. Head First Design Patterns (by Eric Freeman and Elisabeth Robson)
8 | 2. Dive Into Design Patterns (by Alexander Shvets)
9 | 3. [GeeksForGeeks Website](https://www.geeksforgeeks.org/)
10 | 4. [WikiPedia Website](https://www.wikipedia.org/)
11 | 5. +5 years of experience in the software development industry
12 |
13 | The resources for the images in the repository are:
14 |
15 | 1. [The S.O.L.I.D Principles in Pictures](https://medium.com/backticks-tildes/the-s-o-l-i-d-principles-in-pictures-b34ce2f1e898)
16 | 2. [Refactoring.guru Website](https://refactoring.guru/)
17 | 3. Google!
18 |
19 | > Please note that while the concepts discussed in these resources may overlap with the content of this repository, all examples and code within this repository have been independently developed by myself with the goal of providing real-world scenarios and applications.
20 |
--------------------------------------------------------------------------------
/Documentations/40 - Contributing.md:
--------------------------------------------------------------------------------
1 | ## Contributing and Supporting
2 |
3 | Thank you for exploring **OOP Expert with TypeScript**. This repository serves as a comprehensive resource for mastering object-oriented programming principles, SOLID design, and design patterns through the lens of TypeScript.
4 |
5 | Your contributions can enhance the learning experience for countless individuals. Whether it's correcting a typo, suggesting improvements to code examples, or adding new content, your input is invaluable in ensuring the repository remains a top-notch educational tool.
6 |
7 | By collaborating with me, you not only enrich the learning journey for others but also sharpen your own skills. Every line of code, every explanation, and every suggestion can make a significant difference.
8 |
9 | If you've found this repository helpful, kindly consider giving it a ⭐. Your support encourages me to continue refining and expanding its content, benefitting the entire community of developers striving to master object-oriented programming with TypeScript.
10 |
11 | Let's work together to cultivate a vibrant learning environment where knowledge is shared, refined, and celebrated. Your contributions are deeply appreciated. Thank you for being a part of this journey.
12 |
--------------------------------------------------------------------------------
/Examples/DP/AbstractFactory.ts:
--------------------------------------------------------------------------------
1 | interface Movie {
2 | title: string;
3 | artists: Array;
4 | director: string;
5 | releaseYear: number;
6 | awards: Array;
7 | duration: number;
8 | }
9 |
10 | interface AudioTrack {
11 | title: string;
12 | artist: string;
13 | genre: string;
14 | mood: string;
15 | lyric: string;
16 | duration: number;
17 | }
18 |
19 | interface MovieApi {
20 | searchByTitle: (name: string) => Array;
21 | searchByActors: (actors: Array) => Array;
22 | searchByAwards: (awards: Array) => Array;
23 | searchByDirector: (director: string) => Array;
24 | releaseYear: (releaseYear: Date) => Array;
25 | }
26 |
27 | interface AudioApi {
28 | searchByTitle: (name: string) => Array;
29 | searchByArtist: (artist: string) => Array;
30 | searchByMood: (mood: string) => Array;
31 | searchByGenre: (genre: string) => Array;
32 | searchByLyric: (text: string) => Array;
33 | }
34 |
35 | class NormalMovieApiProvider implements MovieApi {
36 | searchByTitle(name: string) {
37 | return [];
38 | }
39 | searchByActors(actors: Array) {
40 | return [];
41 | }
42 | searchByAwards(awards: Array) {
43 | return [];
44 | }
45 | searchByDirector(director: string) {
46 | return [];
47 | }
48 | releaseYear(releaseYear: Date) {
49 | return [];
50 | }
51 | }
52 |
53 | class NormalAudioApiProvider implements AudioApi {
54 | searchByTitle(name: string) {
55 | return [];
56 | }
57 | searchByArtist(artist: string) {
58 | return [];
59 | }
60 | searchByMood(mood: string) {
61 | return [];
62 | }
63 | searchByGenre(genre: string) {
64 | return [];
65 | }
66 | searchByLyric(text: string) {
67 | return [];
68 | }
69 | }
70 |
71 | class PremiumMovieApiProvider implements MovieApi {
72 | searchByTitle(name: string) {
73 | return [];
74 | }
75 | searchByActors(actors: Array) {
76 | return [];
77 | }
78 | searchByAwards(awards: Array) {
79 | return [];
80 | }
81 | searchByDirector(director: string) {
82 | return [];
83 | }
84 | releaseYear(releaseYear: Date) {
85 | return [];
86 | }
87 | }
88 |
89 | class PremiumAudioApiProvider implements AudioApi {
90 | searchByTitle(name: string) {
91 | return [];
92 | }
93 | searchByArtist(artist: string) {
94 | return [];
95 | }
96 | searchByMood(mood: string) {
97 | return [];
98 | }
99 | searchByGenre(genre: string) {
100 | return [];
101 | }
102 | searchByLyric(text: string) {
103 | return [];
104 | }
105 | }
106 |
107 | interface ApiProviderFactory {
108 | createMovieApiProvider: () => MovieApi;
109 | createAudioApiProvider: () => AudioApi;
110 | }
111 |
112 | class NormalApiProviderFactory implements ApiProviderFactory {
113 | createMovieApiProvider() {
114 | return new NormalMovieApiProvider();
115 | }
116 |
117 | createAudioApiProvider() {
118 | return new NormalAudioApiProvider();
119 | }
120 | }
121 |
122 | class PremiumApiProviderFactory implements ApiProviderFactory {
123 | createMovieApiProvider() {
124 | return new PremiumMovieApiProvider();
125 | }
126 |
127 | createAudioApiProvider() {
128 | return new PremiumAudioApiProvider();
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/Examples/DP/Adapter.ts:
--------------------------------------------------------------------------------
1 | interface StandardUser {
2 | fullName: string;
3 | skills: Array;
4 | age: number;
5 | contact: {
6 | email: string;
7 | phone: string;
8 | };
9 | }
10 |
11 | abstract class ResumeServiceApi {
12 | static generateResume(data: StandardUser) {
13 | /* Implementation */
14 | }
15 | }
16 |
17 | class User {
18 | readonly firstName: string;
19 | readonly lastName: string;
20 | readonly birthday: Date;
21 | readonly skills: Record;
22 | readonly email?: string;
23 | readonly phone?: string;
24 |
25 | constructor({
26 | firstName,
27 | lastName,
28 | birthday,
29 | skills,
30 | email,
31 | phone,
32 | }: {
33 | firstName: string;
34 | lastName: string;
35 | birthday: Date;
36 | skills: Record;
37 | email?: string;
38 | phone?: string;
39 | }) {
40 | this.firstName = firstName;
41 | this.lastName = lastName;
42 | this.birthday = birthday;
43 | this.skills = skills;
44 | this.email = email;
45 | this.phone = phone;
46 | }
47 | }
48 |
49 | class UserAdapter implements StandardUser {
50 | private user: User;
51 |
52 | constructor(user: User) {
53 | this.user = user;
54 | }
55 |
56 | get fullName() {
57 | return `${this.user.firstName} ${this.user.lastName}`;
58 | }
59 |
60 | get skills() {
61 | return Object.keys(this.user.skills);
62 | }
63 |
64 | get age() {
65 | return new Date().getFullYear() - this.user.birthday.getFullYear();
66 | }
67 |
68 | get contact() {
69 | return { email: this.user.email ?? "", phone: this.user.phone ?? "" };
70 | }
71 | }
72 |
73 | // Usage
74 |
75 | const user = new User({
76 | firstName: "Ahmad",
77 | lastName: "Jafari",
78 | birthday: new Date(1999, 1, 1, 0, 0, 0, 0),
79 | skills: { TypeScript: 4, JavaScript: 3, OOP: 4, CSharp: 2, Java: 1 },
80 | email: "a99jafari@gmail.com",
81 | phone: "+98 930 848 XXXX",
82 | });
83 |
84 | // const resume = ResumeServiceApi.generateResume(user); |-> Type Error!
85 |
86 | const standardUser = new UserAdapter(user);
87 | const resume = ResumeServiceApi.generateResume(standardUser); // OK!
88 |
--------------------------------------------------------------------------------
/Examples/DP/Bridge.ts:
--------------------------------------------------------------------------------
1 | interface Player {
2 | play(): string;
3 | stop(): string;
4 | }
5 |
6 | class AudioPlayer implements Player {
7 | play(): string {
8 | return "Audio is playing";
9 | }
10 |
11 | stop(): string {
12 | return "Audio is stopped";
13 | }
14 | }
15 |
16 | class VideoPlayer implements Player {
17 | play(): string {
18 | return "Video is playing";
19 | }
20 |
21 | stop(): string {
22 | return "Video is stopped";
23 | }
24 | }
25 |
26 | interface Platform {
27 | play(): string;
28 | stop(): string;
29 | }
30 |
31 | class Desktop implements Platform {
32 | private player: Player;
33 |
34 | constructor(player: Player) {
35 | this.player = player;
36 | }
37 |
38 | play(): string {
39 | return `${this.player.play()} on desktop`;
40 | }
41 |
42 | stop(): string {
43 | return `${this.player.stop()} on desktop`;
44 | }
45 | }
46 |
47 | class Mobile implements Platform {
48 | private player: Player;
49 |
50 | constructor(player: Player) {
51 | this.player = player;
52 | }
53 |
54 | play(): string {
55 | return `${this.player.play()} on mobile`;
56 | }
57 |
58 | stop(): string {
59 | return `${this.player.stop()} on mobile`;
60 | }
61 | }
62 |
63 | // Usage
64 | const audioPlayer = new AudioPlayer();
65 | const videoPlayer = new VideoPlayer();
66 |
67 | const desktopVideoPlayer = new Desktop(videoPlayer);
68 | const desktopAudioPlayer = new Desktop(audioPlayer);
69 | const mobileVideoPlayer = new Mobile(videoPlayer);
70 | const mobileAudioPlayer = new Mobile(audioPlayer);
71 |
--------------------------------------------------------------------------------
/Examples/DP/Builder.ts:
--------------------------------------------------------------------------------
1 | class Page {
2 | private headerParts: Array;
3 | private bodyParts: Array;
4 | private footerParts: Array;
5 |
6 | constructor() {
7 | this.headerParts = [];
8 | this.bodyParts = [];
9 | this.footerParts = [];
10 | }
11 |
12 | public setHeaderParts(...parts: Array) {
13 | this.headerParts = parts;
14 | }
15 |
16 | public setBodyParts(...parts: Array) {
17 | this.bodyParts = parts;
18 | }
19 |
20 | public setFooterParts(...parts: Array) {
21 | this.footerParts = parts;
22 | }
23 |
24 | public getPage() {
25 | return {
26 | headerParts: this.headerParts,
27 | bodyParts: this.bodyParts,
28 | footerParts: this.footerParts,
29 | };
30 | }
31 | }
32 |
33 | interface PageBuilder {
34 | getPage: () => Page;
35 | buildHeader: () => void;
36 | buildBody: () => void;
37 | buildFooter: () => void;
38 | }
39 |
40 | class PersonalBlogPageBuilder implements PageBuilder {
41 | private page: Page;
42 |
43 | constructor() {
44 | this.page = new Page();
45 | }
46 |
47 | public getPage() {
48 | return this.page;
49 | }
50 |
51 | public buildHeader() {
52 | this.page.setHeaderParts("Title", "Author Information");
53 | }
54 |
55 | public buildBody() {
56 | this.page.setBodyParts("Recent Posts", "Favorite Posts", "Last Comments");
57 | }
58 |
59 | public buildFooter() {
60 | this.page.setFooterParts("CopyRights", "Author Email Address");
61 | }
62 | }
63 |
64 | class OnlineShopPageBuilder implements PageBuilder {
65 | private page: Page;
66 |
67 | constructor() {
68 | this.page = new Page();
69 | }
70 |
71 | public getPage() {
72 | return this.page;
73 | }
74 |
75 | public buildHeader() {
76 | this.page.setHeaderParts("Logo", "Description", "Products Category Menu");
77 | }
78 |
79 | public buildBody() {
80 | this.page.setBodyParts("New Products", "Daily Off", "Suggested Products");
81 | }
82 |
83 | public buildFooter() {
84 | this.page.setFooterParts("About Us", "Address", "Legal Certificate Link");
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/Examples/DP/ChainOfResponsibility.ts:
--------------------------------------------------------------------------------
1 | interface IResponse {
2 | statusCode: number;
3 | body: Record;
4 | authentication: Record;
5 | message?: string;
6 | }
7 |
8 | class ResponseHandler {
9 | private nextHandler?: ResponseHandler;
10 |
11 | protected process(response: IResponse): IResponse {
12 | return response;
13 | }
14 |
15 | public setNext(ResponseHandler: ResponseHandler): ResponseHandler {
16 | this.nextHandler = ResponseHandler;
17 |
18 | return ResponseHandler;
19 | }
20 |
21 | public handle(response: IResponse): IResponse {
22 | const processedResponse = this.process(response);
23 |
24 | if (this.nextHandler == null) {
25 | return processedResponse;
26 | } else {
27 | return this.nextHandler.handle(processedResponse);
28 | }
29 | }
30 | }
31 |
32 | class Encryptor extends ResponseHandler {
33 | private encryptTokens(response: IResponse) {
34 | const { authentication } = response;
35 | const encryptedAuthTokens: Record = {};
36 |
37 | for (const key in authentication) {
38 | encryptedAuthTokens[key] = `encrypted-${authentication[key]}`;
39 | }
40 |
41 | return { ...response, authentication: encryptedAuthTokens };
42 | }
43 |
44 | protected process(response: IResponse) {
45 | const encryptedResponse = this.encryptTokens(response);
46 |
47 | return encryptedResponse;
48 | }
49 | }
50 |
51 | class BodyFormatter extends ResponseHandler {
52 | private transformKeysToCamelCase(body: Record) {
53 | const newBody: Record = {};
54 |
55 | for (const key in body) {
56 | const camelCaseKey = key.replace(/_([a-z])/g, (subString) => subString[1].toUpperCase());
57 |
58 | newBody[camelCaseKey] = body[key];
59 | }
60 |
61 | return newBody;
62 | }
63 |
64 | protected process(response: IResponse) {
65 | const clonedResponseBody = JSON.parse(JSON.stringify(response.body));
66 | const formattedBody = this.transformKeysToCamelCase(clonedResponseBody);
67 | const formattedResponse = { ...response, body: formattedBody };
68 |
69 | return formattedResponse;
70 | }
71 | }
72 |
73 | class MetadataAdder extends ResponseHandler {
74 | private getResponseMetadata(statusCode: number) {
75 | if (statusCode < 200) {
76 | return "Informational";
77 | } else if (statusCode < 300) {
78 | return "Success";
79 | } else if (statusCode < 400) {
80 | return "Redirection";
81 | } else if (statusCode < 500) {
82 | return "Client Error";
83 | } else {
84 | return "Server Error";
85 | }
86 | }
87 |
88 | protected process(response: IResponse) {
89 | const updatedResponse = {
90 | ...response,
91 | message: this.getResponseMetadata(response.statusCode),
92 | };
93 |
94 | return updatedResponse;
95 | }
96 | }
97 |
98 | // Usage
99 | const response: IResponse = {
100 | statusCode: 200,
101 | body: {
102 | design_pattern_name: "Chain of Responsibility",
103 | pattern_category: "Behavioral",
104 | complexity_percentage: 80,
105 | },
106 | authentication: {
107 | api_token: "12345678",
108 | refresh_token: "ABCDEFGH",
109 | },
110 | };
111 |
112 | const responseHandler = new ResponseHandler();
113 | const encryptor = new Encryptor();
114 | const bodyFormatter = new BodyFormatter();
115 | const metadataAdder = new MetadataAdder();
116 |
117 | responseHandler.setNext(encryptor).setNext(bodyFormatter).setNext(metadataAdder);
118 |
119 | const resultResponse = responseHandler.handle(response);
120 |
121 | console.log(resultResponse);
122 | /*
123 | {
124 | "statusCode": 200,
125 | "body": {
126 | "designPatternName": "Chain of Responsibility",
127 | "patternCategory": "Behavioral",
128 | "complexityPercentage": 80
129 | },
130 | "authentication": {
131 | "api_token": "encrypted-12345678",
132 | "refresh_token": "encrypted-ABCDEFGH"
133 | },
134 | "message": "Success"
135 | }
136 | */
137 |
--------------------------------------------------------------------------------
/Examples/DP/Command.ts:
--------------------------------------------------------------------------------
1 | interface Command {
2 | execute(): void;
3 | undo(): void;
4 | }
5 |
6 | class AddTextCommand implements Command {
7 | private prevText: string = "";
8 |
9 | constructor(
10 | private editor: TextEditor,
11 | private text: string,
12 | ) {}
13 |
14 | execute() {
15 | this.prevText = this.editor.content;
16 | this.editor.content += this.text;
17 | }
18 |
19 | undo() {
20 | this.editor.content = this.prevText;
21 | }
22 | }
23 |
24 | class DeleteTextCommand implements Command {
25 | private prevText: string = "";
26 |
27 | constructor(private editor: TextEditor) {}
28 |
29 | execute() {
30 | this.prevText = this.editor.content;
31 | this.editor.content = "";
32 | }
33 |
34 | undo() {
35 | this.editor.content = this.prevText;
36 | }
37 | }
38 |
39 | class TextEditor {
40 | content: string = "";
41 | }
42 |
43 | class CommandInvoker {
44 | private commandHistory: Array = [];
45 | private currentCommandIndex: number = -1;
46 |
47 | executeCommand(command: Command) {
48 | if (this.currentCommandIndex < this.commandHistory.length - 1) {
49 | this.commandHistory = this.commandHistory.slice(0, this.currentCommandIndex + 1);
50 | }
51 |
52 | command.execute();
53 | this.commandHistory.push(command);
54 | this.currentCommandIndex++;
55 | }
56 |
57 | undo() {
58 | if (this.currentCommandIndex >= 0) {
59 | const command = this.commandHistory[this.currentCommandIndex];
60 |
61 | command.undo();
62 | this.currentCommandIndex--;
63 | } else {
64 | console.log("Nothing to undo.");
65 | }
66 | }
67 |
68 | redo() {
69 | if (this.currentCommandIndex < this.commandHistory.length - 1) {
70 | const command = this.commandHistory[this.currentCommandIndex + 1];
71 |
72 | command.execute();
73 | this.currentCommandIndex++;
74 | } else {
75 | console.log("Nothing to redo.");
76 | }
77 | }
78 | }
79 |
80 | // Client Code
81 | const editor = new TextEditor();
82 | const invoker = new CommandInvoker();
83 |
84 | const addTextCmd = new AddTextCommand(editor, "Hello, World!");
85 |
86 | invoker.executeCommand(addTextCmd);
87 | console.log(editor.content); // "Hello, World!"
88 |
89 | const deleteTextCmd = new DeleteTextCommand(editor);
90 |
91 | invoker.executeCommand(deleteTextCmd);
92 | console.log(editor.content); // ""
93 |
94 | invoker.undo();
95 | console.log(editor.content); // "Hello, World!"
96 |
97 | invoker.redo();
98 | console.log(editor.content); // ""
99 |
--------------------------------------------------------------------------------
/Examples/DP/Composite.ts:
--------------------------------------------------------------------------------
1 | abstract class BaseUnit {
2 | constructor(
3 | private readonly id: string,
4 | private readonly units: Array> = [],
5 | ) {}
6 |
7 | getUnit(unitId: string): BaseUnit | null {
8 | return this.units.find((unit) => unit.id === unitId) ?? null;
9 | }
10 |
11 | getSalary(): number {
12 | return this.units.reduce((acc, unit) => acc + unit.getSalary(), 0);
13 | }
14 |
15 | increaseSalary(percentage: number): void {
16 | this.units.forEach((unit) => unit.increaseSalary(percentage));
17 | }
18 | }
19 |
20 | class Employee extends BaseUnit {
21 | private salary: number;
22 |
23 | constructor(id: string, salary: number) {
24 | super(id);
25 | this.salary = salary;
26 | }
27 |
28 | getUnit(): never {
29 | throw new Error("Employee cannot have sub-units");
30 | }
31 |
32 | getSalary() {
33 | return this.salary;
34 | }
35 |
36 | increaseSalary(percentage: number) {
37 | this.salary = this.salary + (this.salary * percentage) / 100;
38 | }
39 | }
40 |
41 | class Department extends BaseUnit {}
42 |
43 | class Faculty extends BaseUnit {}
44 |
45 | class University extends BaseUnit {}
46 |
47 | // Usage
48 |
49 | const harvardUniversity = new University("Harvard", [
50 | new Faculty("Engineering", [
51 | new Department("Computer", [new Employee("C1", 6200), new Employee("C2", 5400), new Employee("C3", 5600)]),
52 | new Department("Electrical", [new Employee("E1", 4800), new Employee("E2", 5800)]),
53 | ]),
54 | new Faculty("Science", [
55 | new Department("Physics", [new Employee("P1", 3800), new Employee("P2", 4600)]),
56 | new Department("Mathematics", [new Employee("M1", 5200), new Employee("M2", 5600), new Employee("M3", 4600)]),
57 | ]),
58 | ]);
59 |
60 | console.log(harvardUniversity.getSalary());
61 | harvardUniversity.increaseSalary(10);
62 | console.log(harvardUniversity.getSalary());
63 |
64 | const engineeringFaculty = harvardUniversity.getUnit("Engineering") as Faculty;
65 |
66 | console.log(engineeringFaculty.getSalary());
67 | engineeringFaculty.increaseSalary(10);
68 | console.log(engineeringFaculty.getSalary());
69 |
70 | const computerDepartment = engineeringFaculty.getUnit("Computer") as Department;
71 |
72 | console.log(computerDepartment.getSalary());
73 | computerDepartment.increaseSalary(10);
74 | console.log(computerDepartment.getSalary());
75 |
76 | const employee = computerDepartment.getUnit("C1") as Employee;
77 |
78 | console.log(employee.getSalary());
79 | employee.increaseSalary(10);
80 | console.log(employee.getSalary());
81 |
--------------------------------------------------------------------------------
/Examples/DP/Decorator.ts:
--------------------------------------------------------------------------------
1 | interface ImageProcessor {
2 | processImage: () => File;
3 | }
4 |
5 | class ImageFile implements ImageProcessor {
6 | private image: File;
7 |
8 | constructor(imageBlobs: Array, imageName: string) {
9 | this.image = new File(imageBlobs, imageName);
10 | }
11 |
12 | processImage() {
13 | // Converts the blobs to a visible image
14 | return this.image;
15 | }
16 | }
17 |
18 | abstract class ImageDecorator implements ImageProcessor {
19 | protected image: File;
20 |
21 | constructor(image: File) {
22 | this.image = image;
23 | }
24 |
25 | abstract processImage(): File;
26 | }
27 |
28 | class ImageCompressor extends ImageDecorator {
29 | processImage(): File {
30 | // Compresses image size
31 | return this.image;
32 | }
33 | }
34 |
35 | class ImageEnhancer extends ImageDecorator {
36 | processImage(): File {
37 | // Enhances image quality
38 | return this.image;
39 | }
40 | }
41 |
42 | class ImageResizer extends ImageDecorator {
43 | processImage() {
44 | // Changes image width and height
45 | return this.image;
46 | }
47 | }
48 |
49 | // Usage
50 |
51 | const image = new ImageFile([], "Picture.jpg").processImage();
52 | const compressedImage = new ImageCompressor(image).processImage();
53 | const enhancedImage = new ImageCompressor(compressedImage).processImage();
54 | const resizedImage = new ImageResizer(enhancedImage).processImage();
55 |
--------------------------------------------------------------------------------
/Examples/DP/Facade.ts:
--------------------------------------------------------------------------------
1 | class GitChecker {
2 | private repositoryPath: string;
3 |
4 | constructor(repositoryPath: string) {
5 | this.repositoryPath = repositoryPath;
6 | }
7 |
8 | analyzeCommits() {
9 | // Checks the quality of commit messages
10 | }
11 |
12 | analyzeUnmergedBranches() {
13 | // Checks the
14 | }
15 | }
16 |
17 | class Linter {
18 | private rules: Array;
19 |
20 | constructor(rules: Array) {
21 | this.rules = rules;
22 | }
23 |
24 | findIssues() {
25 | // Checks codebase and finds all issues
26 | }
27 |
28 | resolveFixableIssues() {
29 | // Checks codebase and fix all fixable issues
30 | }
31 | }
32 |
33 | class PackageManager {
34 | private dependencies: Array<{ name: string; version: number }>;
35 |
36 | constructor(dependencies: Array<{ name: string; version: number }>) {
37 | this.dependencies = dependencies;
38 | }
39 |
40 | findUnsecureLibraries() {
41 | // Analyzes all dependencies and finds all of unsecure libraries
42 | }
43 |
44 | findDeprecatedLibraries() {
45 | // Analyzes all dependencies and finds all of deprecated libraries
46 | }
47 | }
48 |
49 | // Facade Class
50 | class CodebaseAnalyzer {
51 | private gitChecker: GitChecker;
52 | private linter: Linter;
53 | private packageManager: PackageManager;
54 |
55 | constructor({
56 | repositoryPath,
57 | linterRules,
58 | dependencies,
59 | }: {
60 | repositoryPath: string;
61 | linterRules: Array;
62 | dependencies: Array<{ name: string; version: number }>;
63 | }) {
64 | this.gitChecker = new GitChecker(repositoryPath);
65 | this.linter = new Linter(linterRules);
66 | this.packageManager = new PackageManager(dependencies);
67 | }
68 |
69 | // This method is the facade method and does all of the work
70 | analyze() {
71 | this.gitChecker.analyzeCommits();
72 | this.gitChecker.analyzeUnmergedBranches();
73 | this.linter.findIssues();
74 | this.linter.resolveFixableIssues();
75 | this.packageManager.findUnsecureLibraries();
76 | this.packageManager.findDeprecatedLibraries();
77 | }
78 | }
79 |
80 | // Usage
81 | const codebaseAnalyzer = new CodebaseAnalyzer({
82 | repositoryPath: "root/design-patterns/structural/facade/",
83 | linterRules: ["rule1", "rule2", "rule3", "rule4"],
84 | dependencies: [
85 | { name: "ABC", version: 19 },
86 | { name: "MNP", version: 14 },
87 | { name: "XYZ", version: 23 },
88 | ],
89 | });
90 |
91 | codebaseAnalyzer.analyze();
92 |
--------------------------------------------------------------------------------
/Examples/DP/FactoryMethod.ts:
--------------------------------------------------------------------------------
1 | enum PaymentType {
2 | Paypal = "PAYPAL",
3 | Bitcoin = "BITCOIN",
4 | VisaCard = "VISA_CARD",
5 | }
6 |
7 | abstract class PaymentService {
8 | public abstract payMoney(amount: number): void;
9 | }
10 |
11 | class Paypal extends PaymentService {
12 | public override payMoney(amount: number) {
13 | console.log(`You paid ${amount} dollars by Paypal.`);
14 | }
15 | }
16 |
17 | class Bitcoin extends PaymentService {
18 | public override payMoney(amount: number) {
19 | console.log(`You paid ${amount} dollars by Bitcoin.`);
20 | }
21 | }
22 |
23 | class VisaCard extends PaymentService {
24 | public override payMoney(amount: number) {
25 | console.log(`You paid ${amount} dollars by VisaCard.`);
26 | }
27 | }
28 |
29 | abstract class PaymentFactory {
30 | public abstract createService(): PaymentService;
31 | }
32 |
33 | class PaypalFactory extends PaymentFactory {
34 | public override createService(): PaymentService {
35 | return new Paypal();
36 | }
37 | }
38 |
39 | class BitcoinFactory extends PaymentFactory {
40 | public override createService(): PaymentService {
41 | return new Bitcoin();
42 | }
43 | }
44 |
45 | class VisaCardFactory extends PaymentFactory {
46 | public override createService(): PaymentService {
47 | return new VisaCard();
48 | }
49 | }
50 |
51 | // Usage
52 |
53 | function getPaymentFactory(paymentType: PaymentType): PaymentFactory {
54 | switch (paymentType) {
55 | case PaymentType.Paypal:
56 | return new PaypalFactory();
57 | case PaymentType.Bitcoin:
58 | return new BitcoinFactory();
59 | case PaymentType.VisaCard:
60 | return new VisaCardFactory();
61 | default:
62 | throw new Error("Invalid payment type.");
63 | }
64 | }
65 |
66 | const paypalService = getPaymentFactory(PaymentType.Paypal).createService();
67 |
68 | paypalService.payMoney(100); // You paid 100 dollars by Paypal.
69 |
70 | const bitcoinService = getPaymentFactory(PaymentType.Bitcoin).createService();
71 |
72 | bitcoinService.payMoney(200); // You paid 200 dollars by Bitcoin.
73 |
74 | const visaCardService = getPaymentFactory(PaymentType.VisaCard).createService();
75 |
76 | visaCardService.payMoney(300); // You paid 300 dollars by VisaCard.
77 |
--------------------------------------------------------------------------------
/Examples/DP/Flyweight.ts:
--------------------------------------------------------------------------------
1 | interface IRequest {
2 | readonly method: "GET" | "POST" | "PUT" | "DELETE";
3 | readonly url: string;
4 | readonly body: Record;
5 | send(): Promise;
6 | }
7 |
8 | class MinimalRequest implements IRequest {
9 | constructor(
10 | public readonly method: "GET" | "POST" | "PUT" | "DELETE",
11 | public readonly url: string,
12 | public readonly body: Record = {},
13 | ) {}
14 |
15 | public async send(): Promise {
16 | const options = { method: this.method, body: JSON.stringify(this.body) };
17 |
18 | const response = await fetch(this.url, options);
19 |
20 | return response.json();
21 | }
22 | }
23 |
24 | class RequestFactory {
25 | private requests: Map = new Map();
26 |
27 | public createRequest(
28 | method: "GET" | "POST" | "PUT" | "DELETE",
29 | url: string,
30 | body: Record = {},
31 | ): IRequest {
32 | const key = `${method}-${url}`;
33 |
34 | if (!this.requests.has(key)) {
35 | const request = new MinimalRequest(method, url, body);
36 |
37 | this.requests.set(key, request);
38 | }
39 |
40 | return this.requests.get(key)!; // Type assertion for clarity
41 | }
42 | }
43 |
44 | class ParallelRequestsHandler {
45 | private factory: RequestFactory;
46 |
47 | constructor(factory: RequestFactory) {
48 | this.factory = factory;
49 | }
50 |
51 | public async sendAll(
52 | requestsInfo: Array<{
53 | method: "GET" | "POST" | "PUT" | "DELETE";
54 | url: string;
55 | body?: Record;
56 | }>,
57 | ): Promise> {
58 | const requests = requestsInfo.map((requestInfo) =>
59 | this.factory.createRequest(requestInfo.method, requestInfo.url, requestInfo.body),
60 | );
61 | const responses = await Promise.all(requests.map((request) => request.send()));
62 |
63 | return responses;
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/Examples/DP/Interpreter.ts:
--------------------------------------------------------------------------------
1 | interface Expression {
2 | interpret(): number;
3 | }
4 |
5 | class NumberExpression implements Expression {
6 | constructor(private value: number) {}
7 |
8 | interpret(): number {
9 | return this.value;
10 | }
11 | }
12 |
13 | class PlusExpression implements Expression {
14 | constructor(
15 | private left: Expression,
16 | private right: Expression,
17 | ) {}
18 |
19 | interpret(): number {
20 | return this.left.interpret() + this.right.interpret();
21 | }
22 | }
23 |
24 | class MinusExpression implements Expression {
25 | constructor(
26 | private left: Expression,
27 | private right: Expression,
28 | ) {}
29 |
30 | interpret(): number {
31 | return this.left.interpret() - this.right.interpret();
32 | }
33 | }
34 |
35 | class MultiplyExpression implements Expression {
36 | constructor(
37 | private left: Expression,
38 | private right: Expression,
39 | ) {}
40 |
41 | interpret(): number {
42 | return this.left.interpret() * this.right.interpret();
43 | }
44 | }
45 |
46 | class DivideExpression implements Expression {
47 | constructor(
48 | private left: Expression,
49 | private right: Expression,
50 | ) {}
51 |
52 | interpret(): number {
53 | return this.left.interpret() / this.right.interpret();
54 | }
55 | }
56 |
57 | class Interpreter {
58 | interpret(expression: string): number {
59 | const stack: Array = [];
60 |
61 | const tokens = expression.split(" ");
62 |
63 | for (const token of tokens) {
64 | if (this.isOperator(token)) {
65 | const right = stack.pop()!;
66 | const left = stack.pop()!;
67 | const operator = this.createExpression(token, left, right);
68 |
69 | stack.push(operator);
70 | } else {
71 | stack.push(new NumberExpression(parseFloat(token)));
72 | }
73 | }
74 |
75 | return stack.pop()!.interpret();
76 | }
77 |
78 | private isOperator(token: string): boolean {
79 | return token === "+" || token === "-" || token === "*" || token === "/";
80 | }
81 |
82 | private createExpression(operator: string, left: Expression, right: Expression): Expression {
83 | switch (operator) {
84 | case "+":
85 | return new PlusExpression(left, right);
86 | case "-":
87 | return new MinusExpression(left, right);
88 | case "*":
89 | return new MultiplyExpression(left, right);
90 | case "/":
91 | return new DivideExpression(left, right);
92 | default:
93 | throw new Error(`Invalid operator: ${operator}`);
94 | }
95 | }
96 | }
97 |
98 | // Usage
99 | const interpreter = new Interpreter();
100 |
101 | console.log(interpreter.interpret("3 4 +")); // Output: 7
102 | console.log(interpreter.interpret("5 2 * 3 +")); // Output: 13
103 | console.log(interpreter.interpret("10 2 /")); // Output: 5
104 |
--------------------------------------------------------------------------------
/Examples/DP/Iterator.ts:
--------------------------------------------------------------------------------
1 | interface MyIterator {
2 | hasPrevious: () => boolean;
3 | hasNext: () => boolean;
4 | previous: () => T;
5 | next: () => T;
6 | }
7 |
8 | class Book {
9 | readonly title: string;
10 | readonly author: string;
11 | readonly isbn: string;
12 |
13 | constructor(title: string, author: string, isbn: string = "") {
14 | this.title = title;
15 | this.author = author;
16 | this.isbn = isbn;
17 | }
18 | }
19 |
20 | class BookShelf {
21 | private books: Array = [];
22 |
23 | getLength(): number {
24 | return this.books.length;
25 | }
26 |
27 | addBook(book: Book): void {
28 | this.books.push(book);
29 | }
30 |
31 | getBookAt(index: number): Book {
32 | return this.books[index];
33 | }
34 |
35 | createIterator() {
36 | return new BookShelfIterator(this);
37 | }
38 | }
39 |
40 | class BookShelfIterator implements MyIterator {
41 | private bookShelf: BookShelf;
42 | private currentIndex: number;
43 |
44 | constructor(bookShelf: BookShelf) {
45 | this.bookShelf = bookShelf;
46 | this.currentIndex = 0;
47 | }
48 |
49 | hasNext() {
50 | return this.currentIndex < this.bookShelf.getLength();
51 | }
52 |
53 | hasPrevious() {
54 | return this.currentIndex > 0;
55 | }
56 |
57 | next() {
58 | this.currentIndex += 1;
59 |
60 | return this.bookShelf.getBookAt(this.currentIndex);
61 | }
62 |
63 | previous() {
64 | this.currentIndex -= 1;
65 |
66 | return this.bookShelf.getBookAt(this.currentIndex);
67 | }
68 | }
69 |
70 | // Usage
71 | const shelf = new BookShelf();
72 |
73 | shelf.addBook(new Book("Design Patterns", "Gang of Four"));
74 | shelf.addBook(new Book("Clean Code", "Robert C. Martin"));
75 | shelf.addBook(new Book("You Don't Know JS", "Kyle Simpson"));
76 |
77 | const MyIterator = shelf.createIterator();
78 |
79 | while (MyIterator.hasNext()) {
80 | const book = MyIterator.next();
81 |
82 | console.log(`${book.title} by ${book.author}`);
83 | }
84 |
--------------------------------------------------------------------------------
/Examples/DP/Mediator.ts:
--------------------------------------------------------------------------------
1 | interface ChatMediator {
2 | sendMessage(receiver: User, message: string): void;
3 | }
4 |
5 | class ConcreteChatMediator implements ChatMediator {
6 | private users: Array = [];
7 |
8 | addUser(user: User): void {
9 | this.users.push(user);
10 | }
11 |
12 | sendMessage(receiver: User, message: string): void {
13 | for (const user of this.users) {
14 | // Don't send the message to the user who sent it
15 | if (user !== receiver) {
16 | user.receiveMessage(message);
17 | }
18 | }
19 | }
20 | }
21 |
22 | class User {
23 | private mediator: ChatMediator;
24 | private name: string;
25 |
26 | constructor(mediator: ChatMediator, name: string) {
27 | this.mediator = mediator;
28 | this.name = name;
29 | }
30 |
31 | sendMessage(message: string): void {
32 | console.log(`${this.name} sends: ${message}`);
33 | this.mediator.sendMessage(this, message);
34 | }
35 |
36 | receiveMessage(message: string): void {
37 | console.log(`${this.name} receives: ${message}`);
38 | }
39 | }
40 |
41 | // Usage
42 | const mediator = new ConcreteChatMediator();
43 |
44 | const user1 = new User(mediator, "Alice");
45 | const user2 = new User(mediator, "Bob");
46 | const user3 = new User(mediator, "Charlie");
47 |
48 | mediator.addUser(user1);
49 | mediator.addUser(user2);
50 | mediator.addUser(user3);
51 |
52 | user1.sendMessage("Hello, everyone!");
53 |
54 | user2.sendMessage("Hi, Alice!");
55 |
56 | user3.sendMessage("Hey, Bob!");
57 |
--------------------------------------------------------------------------------
/Examples/DP/Memento.ts:
--------------------------------------------------------------------------------
1 | class EditorMemento {
2 | constructor(private readonly content: string) {}
3 |
4 | getContent(): string {
5 | return this.content;
6 | }
7 | }
8 |
9 | class Editor {
10 | constructor(private content: string = "") {}
11 |
12 | getContent(): string {
13 | return this.content;
14 | }
15 |
16 | setContent(content: string): void {
17 | this.content = content;
18 | }
19 |
20 | createSnapshot(): EditorMemento {
21 | return new EditorMemento(this.content);
22 | }
23 |
24 | restoreSnapshot(snapshot: EditorMemento): void {
25 | this.content = snapshot.getContent();
26 | }
27 | }
28 |
29 | class MinimalHistory {
30 | private snapshots: Array = [];
31 |
32 | push(snapshot: EditorMemento): void {
33 | this.snapshots.push(snapshot);
34 | }
35 |
36 | pop(): EditorMemento | undefined {
37 | return this.snapshots.pop();
38 | }
39 | }
40 |
41 | // Usage
42 | const editor = new Editor();
43 | const minimalHistory = new MinimalHistory();
44 |
45 | editor.setContent("Hello, World!");
46 | editor.setContent("Hello, TypeScript!");
47 | minimalHistory.push(editor.createSnapshot());
48 | editor.setContent("Hello, Memento Pattern!");
49 |
50 | const lastSnapshot = minimalHistory.pop();
51 |
52 | if (lastSnapshot) {
53 | editor.restoreSnapshot(lastSnapshot);
54 | }
55 |
56 | console.log(editor.getContent()); // Output: Hello, TypeScript!
57 |
--------------------------------------------------------------------------------
/Examples/DP/Observer.ts:
--------------------------------------------------------------------------------
1 | interface Subject {
2 | registerObserver(observer: Observer): void;
3 | removeObserver(observer: Observer): void;
4 | notifyObservers(): void;
5 | }
6 |
7 | interface Observer {
8 | update(notification: string): void;
9 | }
10 |
11 | class Celebrity implements Subject {
12 | private followers: Array;
13 | private posts: Array;
14 |
15 | constructor() {
16 | this.followers = [];
17 | this.posts = [];
18 | }
19 |
20 | // Method to make a new post
21 | sendPost(newPost: string) {
22 | this.posts = [...this.posts, newPost];
23 | this.notifyFollowers();
24 | }
25 |
26 | // Method to notify followers
27 | private notifyFollowers() {
28 | this.followers.forEach((follower) => {
29 | const latestPost = this.posts[this.posts.length - 1];
30 |
31 | follower.update(latestPost);
32 | });
33 | }
34 |
35 | // Register a new follower
36 | registerObserver(observer: Observer) {
37 | this.followers.push(observer);
38 | }
39 |
40 | // Remove a follower
41 | removeObserver(observer: Observer) {
42 | const index = this.followers.indexOf(observer);
43 |
44 | if (index !== -1) {
45 | this.followers.splice(index, 1);
46 | }
47 | }
48 |
49 | // Notify all followers
50 | notifyObservers() {
51 | this.notifyFollowers();
52 | }
53 | }
54 |
55 | class Follower implements Observer {
56 | private followerName: string;
57 |
58 | constructor(name: string) {
59 | this.followerName = name;
60 | }
61 |
62 | // Update method to receive notifications
63 | update(notification: string) {
64 | console.log(`${this.followerName} received a notification: ${notification}`);
65 | }
66 | }
67 |
68 | // Usage
69 | const celebrity1 = new Celebrity();
70 | const celebrity2 = new Celebrity();
71 |
72 | const follower1 = new Follower("John");
73 | const follower2 = new Follower("Alice");
74 | const follower3 = new Follower("Bob");
75 |
76 | celebrity1.registerObserver(follower1);
77 | celebrity1.registerObserver(follower2);
78 | celebrity2.registerObserver(follower3);
79 |
80 | celebrity1.sendPost("Hello World!");
81 | celebrity2.sendPost("I love coding!");
82 |
83 | celebrity1.removeObserver(follower1);
84 | celebrity1.removeObserver(follower2);
85 |
86 | celebrity1.sendPost("Observer pattern is awesome!");
87 |
88 | // Output:
89 | // John received a notification: Hello World!
90 | // Alice received a notification: Hello World!
91 | // Bob received a notification: I love coding!
92 |
--------------------------------------------------------------------------------
/Examples/DP/Prototype.ts:
--------------------------------------------------------------------------------
1 | interface IPrototype {
2 | clone: () => IPrototype;
3 | }
4 |
5 | class Product implements IPrototype {
6 | private name: string;
7 | private price: number;
8 | private warranty: Date | null;
9 |
10 | constructor(name: string, price: number, warranty: Date | null) {
11 | this.name = name;
12 | this.price = price;
13 | this.warranty = warranty;
14 | }
15 |
16 | // Assume we implement methods, getters and setters all here
17 |
18 | public clone() {
19 | return new Product(this.name, this.price, this.warranty);
20 | }
21 | }
22 |
23 | const productOne = new Product("Laptop", 2500000, new Date(2050));
24 |
25 | const productTwo = productOne.clone();
26 |
27 | // productOne !== productTwo but their properties are the same
28 |
--------------------------------------------------------------------------------
/Examples/DP/Proxy.ts:
--------------------------------------------------------------------------------
1 | interface IRequestHandler {
2 | sendRequest(method: string, url: string, body?: string): void;
3 | }
4 |
5 | class RequestHandler implements IRequestHandler {
6 | sendRequest(method: string, url: string, body?: string): void {
7 | console.log(`Request sent: ${method} ${url} ${body}`);
8 | }
9 | }
10 |
11 | class RequestHandlerProxy implements IRequestHandler {
12 | private realApi: RequestHandler;
13 |
14 | constructor(realApi: RequestHandler) {
15 | this.realApi = realApi;
16 | }
17 |
18 | private logRequest(method: string, url: string, body?: string): void {
19 | console.log(`Request logged: ${method} ${url} ${body}`);
20 | }
21 |
22 | private validateRequestUrl(url: string): boolean {
23 | return url.startsWith("/api");
24 | }
25 |
26 | sendRequest(method: string, url: string, body?: string): void {
27 | if (this.validateRequestUrl(url)) {
28 | this.realApi.sendRequest(method, url, body);
29 | this.logRequest(method, url, body);
30 | }
31 | }
32 | }
33 |
34 | // Usage
35 |
36 | const realRequestHandler = new RequestHandler();
37 | const proxyRequestHandler = new RequestHandlerProxy(realRequestHandler);
38 |
39 | proxyRequestHandler.sendRequest("GET", "/api/users");
40 |
--------------------------------------------------------------------------------
/Examples/DP/Singleton.ts:
--------------------------------------------------------------------------------
1 | class Weather {
2 | private static instance: Weather | null = null;
3 |
4 | private statusOfCities: Array<{
5 | city: string;
6 | status: "SUNNY" | "CLOUDY" | "RAINY" | "SNOWY";
7 | }>;
8 |
9 | private constructor() {
10 | const data = []; // Get data from API
11 |
12 | this.statusOfCities = data;
13 | }
14 |
15 | public getTemperatureByCity(city: string) {
16 | return this.statusOfCities.find((data) => data.city === city);
17 | }
18 |
19 | public static getInstance() {
20 | if (this.instance == null) {
21 | this.instance = new Weather();
22 | }
23 |
24 | return this.instance;
25 | }
26 | }
27 |
28 | const instanceOne = Weather.getInstance();
29 | const instanceTwo = Weather.getInstance();
30 | // instanceOne is equal to instanceTwo (instanceOne === instanceTwo)
31 |
--------------------------------------------------------------------------------
/Examples/DP/State.ts:
--------------------------------------------------------------------------------
1 | interface PipelineState {
2 | start(pipeline: Pipeline): void;
3 | fail(pipeline: Pipeline): void;
4 | complete(pipeline: Pipeline): void;
5 | }
6 |
7 | class IdleState implements PipelineState {
8 | start(pipeline: Pipeline) {
9 | console.log("Pipeline started. Building...");
10 | pipeline.setState(new BuildingState());
11 | }
12 |
13 | fail(_pipeline: Pipeline) {
14 | console.log("Pipeline is idle. Nothing to fail.");
15 | }
16 |
17 | complete(_pipeline: Pipeline) {
18 | console.log("Pipeline is idle. Nothing to complete.");
19 | }
20 | }
21 |
22 | class BuildingState implements PipelineState {
23 | start(_pipeline: Pipeline) {
24 | console.log("Pipeline is already building.");
25 | }
26 |
27 | fail(pipeline: Pipeline) {
28 | console.log("Build failed.");
29 | pipeline.setState(new FailedState());
30 | }
31 |
32 | complete(pipeline: Pipeline) {
33 | console.log("Build complete. Testing...");
34 | pipeline.setState(new TestingState());
35 | }
36 | }
37 |
38 | class TestingState implements PipelineState {
39 | start(_pipeline: Pipeline) {
40 | console.log("Pipeline is already in progress.");
41 | }
42 |
43 | fail(pipeline: Pipeline) {
44 | console.log("Testing failed.");
45 | pipeline.setState(new FailedState());
46 | }
47 |
48 | complete(pipeline: Pipeline) {
49 | console.log("Testing complete. Deploying...");
50 | pipeline.setState(new DeployingState());
51 | }
52 | }
53 |
54 | class DeployingState implements PipelineState {
55 | start(_pipeline: Pipeline) {
56 | console.log("Pipeline is already deploying.");
57 | }
58 |
59 | fail(pipeline: Pipeline) {
60 | console.log("Deployment failed.");
61 | pipeline.setState(new FailedState());
62 | }
63 |
64 | complete(pipeline: Pipeline) {
65 | console.log("Deployment successful!");
66 | pipeline.setState(new IdleState());
67 | }
68 | }
69 |
70 | class FailedState implements PipelineState {
71 | start(_pipeline: Pipeline) {
72 | console.log("Fix the issues and start the pipeline again.");
73 | }
74 |
75 | fail(_pipeline: Pipeline) {
76 | console.log("Pipeline already in failed state.");
77 | }
78 |
79 | complete(_pipeline: Pipeline) {
80 | console.log("Cannot complete. The pipeline has failed.");
81 | }
82 | }
83 |
84 | // 3. Context
85 | class Pipeline {
86 | private state: PipelineState;
87 |
88 | constructor() {
89 | // Initial state
90 | this.state = new IdleState();
91 | }
92 |
93 | setState(state: PipelineState) {
94 | this.state = state;
95 | }
96 |
97 | start() {
98 | this.state.start(this);
99 | }
100 |
101 | fail() {
102 | this.state.fail(this);
103 | }
104 |
105 | complete() {
106 | this.state.complete(this);
107 | }
108 | }
109 |
110 | // Client Code
111 | const pipeline = new Pipeline();
112 |
113 | pipeline.start(); // Output: Pipeline started. Building...
114 | pipeline.complete(); // Output: Build complete. Testing...
115 | pipeline.fail(); // Output: Testing failed.
116 |
117 | pipeline.setState(new BuildingState());
118 | pipeline.start(); // Output: Pipeline is already building.
119 | pipeline.complete(); // Output: Testing complete. Deploying...
120 | pipeline.complete(); // Output: Deployment successful!
121 |
122 | pipeline.setState(new TestingState());
123 | pipeline.start(); // Output: Pipeline is already in progress.
124 | pipeline.fail(); // Output: Testing failed.
125 | pipeline.complete(); // Output: Deployment successful!
126 |
127 | pipeline.setState(new DeployingState());
128 | pipeline.start(); // Output: Pipeline is already deploying.
129 | pipeline.fail(); // Output: Deployment failed.
130 | pipeline.complete(); // Output: Deployment successful!
131 |
132 | pipeline.setState(new FailedState());
133 | pipeline.start(); // Output: Fix the issues and start the pipeline again.
134 | pipeline.fail(); // Output: Pipeline already in failed state.
135 | pipeline.complete(); // Output: Cannot complete. The pipeline has failed.
136 |
--------------------------------------------------------------------------------
/Examples/DP/Strategy.ts:
--------------------------------------------------------------------------------
1 | interface RenderStrategy {
2 | renderShape(shape: Shape): void;
3 | }
4 |
5 | class RasterRender implements RenderStrategy {
6 | renderShape(shape: Shape) {
7 | console.log(`Raster rendering the ${shape.getName()}`);
8 | }
9 | }
10 |
11 | class VectorRender implements RenderStrategy {
12 | renderShape(shape: Shape) {
13 | console.log(`Vector rendering the ${shape.getName()}`);
14 | }
15 | }
16 |
17 | class Shape {
18 | private name: string;
19 | private renderStrategy: RenderStrategy;
20 |
21 | constructor(name: string, strategy: RenderStrategy) {
22 | this.name = name;
23 | this.renderStrategy = strategy;
24 | }
25 |
26 | setRenderStrategy(strategy: RenderStrategy) {
27 | this.renderStrategy = strategy;
28 | }
29 |
30 | render() {
31 | this.renderStrategy.renderShape(this);
32 | }
33 |
34 | getName(): string {
35 | return this.name;
36 | }
37 | }
38 |
39 | // Usage
40 | const rasterRender = new RasterRender();
41 | const vectorRender = new VectorRender();
42 |
43 | const circle = new Shape("Circle", rasterRender);
44 |
45 | circle.render();
46 |
47 | circle.setRenderStrategy(vectorRender);
48 | circle.render();
49 |
--------------------------------------------------------------------------------
/Examples/DP/TemplateMethod.ts:
--------------------------------------------------------------------------------
1 | abstract class SocialMediaPostAnalyzer {
2 | private readonly HARMFUL_WORDS = [
3 | "dumb",
4 | "stupid",
5 | "idiot",
6 | "loser",
7 | "ugly",
8 | "fat",
9 | "skinny",
10 | "weird",
11 | "hate",
12 | "rude",
13 | "nasty",
14 | ];
15 |
16 | preprocessData(data: string): Array {
17 | return data.split(" ").map((word) => word.replace(/[^a-zA-Z ]/g, "").toLowerCase());
18 | }
19 |
20 | analyze(data: Array): Array {
21 | return data.filter((word) => this.HARMFUL_WORDS.includes(word));
22 | }
23 |
24 | displayResults(data: Array): void {
25 | console.log(`The number of harmful words in this post is ${data.length}, including ${data.join(", ")}.`);
26 | }
27 |
28 | async analyzePosts(): Promise {
29 | const data = await this.fetchData();
30 | const preprocessedData = this.preprocessData(data);
31 | const analyticsResult = this.analyze(preprocessedData);
32 |
33 | this.displayResults(analyticsResult);
34 | }
35 |
36 | abstract fetchData(): Promise;
37 | }
38 |
39 | class TwitterPostAnalyzer extends SocialMediaPostAnalyzer {
40 | // Fetches data from Twitter API and returns its data
41 | async fetchData() {
42 | return ""; // Dummy data
43 | }
44 | }
45 |
46 | class InstagramPostAnalyzer extends SocialMediaPostAnalyzer {
47 | // Fetches data from Instagram API and returns its data
48 | async fetchData() {
49 | return ""; // Dummy data
50 | }
51 | }
52 |
53 | // Usage
54 | const twitterAnalysis = new TwitterPostAnalyzer();
55 |
56 | twitterAnalysis.analyzePosts();
57 |
58 | const instagramAnalysis = new InstagramPostAnalyzer();
59 |
60 | instagramAnalysis.analyzePosts();
61 |
--------------------------------------------------------------------------------
/Examples/DP/Visitor.ts:
--------------------------------------------------------------------------------
1 | interface Visitor {
2 | visitDesigner(manager: Designer): void;
3 | visitDeveloper(developer: Developer): void;
4 | }
5 |
6 | interface Employee {
7 | accept(visitor: Visitor): void;
8 | }
9 |
10 | class Designer implements Employee {
11 | name: string;
12 | numberOfDesignedPages: number;
13 |
14 | constructor(name: string, numberOfDesignedPages: number) {
15 | this.name = name;
16 | this.numberOfDesignedPages = numberOfDesignedPages;
17 | }
18 |
19 | accept(visitor: Visitor): void {
20 | visitor.visitDesigner(this);
21 | }
22 | }
23 |
24 | class Developer implements Employee {
25 | name: string;
26 | baseSalary: number;
27 | storyPoints: number;
28 |
29 | constructor(name: string, baseSalary: number, storyPoints: number) {
30 | this.name = name;
31 | this.baseSalary = baseSalary;
32 | this.storyPoints = storyPoints;
33 | }
34 |
35 | accept(visitor: Visitor): void {
36 | visitor.visitDeveloper(this);
37 | }
38 | }
39 |
40 | class SalaryCalculator implements Visitor {
41 | totalSalary: number = 0;
42 |
43 | visitDesigner(manager: Designer): void {
44 | this.totalSalary += manager.numberOfDesignedPages * 200;
45 | }
46 |
47 | visitDeveloper(developer: Developer): void {
48 | this.totalSalary += developer.baseSalary + developer.storyPoints * 30;
49 | }
50 | }
51 |
52 | // Usage
53 | const employees: Array = [
54 | new Designer("Alice", 15),
55 | new Designer("James", 20),
56 | new Developer("Ahmad", 3000, 40),
57 | new Developer("Kate", 2000, 60),
58 | ];
59 |
60 | const salaryCalculator = new SalaryCalculator();
61 |
62 | for (const employee of employees) {
63 | employee.accept(salaryCalculator);
64 | }
65 |
66 | console.log("Total salary:", salaryCalculator.totalSalary);
67 |
--------------------------------------------------------------------------------
/Examples/SOLID/+DependencyInversion.ts:
--------------------------------------------------------------------------------
1 | interface MessengerApi {
2 | connect: () => void;
3 | send: (targetId: string, message: string) => void;
4 | }
5 |
6 | class TelegramApi implements MessengerApi {
7 | connect() {
8 | console.log("You are connected to Telegram API!");
9 | }
10 |
11 | send(targetId: string, message: string) {
12 | console.log(`${message} sent to ${targetId} by Telegram!`);
13 | }
14 | }
15 |
16 | class WhatsappApi implements MessengerApi {
17 | connect() {
18 | console.log("You are connected to Whatsapp API!");
19 | }
20 |
21 | send(targetId: string, message: string) {
22 | console.log(`${message} sent to ${targetId} by Whatsapp!`);
23 | }
24 | }
25 |
26 | class SignalApi implements MessengerApi {
27 | connect() {
28 | console.log("You are connected to Signal API!");
29 | }
30 |
31 | send(targetId: string, message: string) {
32 | console.log(`${message} sent to ${targetId} by Signal!`);
33 | }
34 | }
35 |
36 | class Messenger {
37 | constructor(private api: MessengerApi) {}
38 |
39 | sendMessage(targetId: string, message: string) {
40 | this.api.connect();
41 | this.api.send(targetId, message);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/Examples/SOLID/+InterfaceSegregation.ts:
--------------------------------------------------------------------------------
1 | interface BaseVPNConnection {
2 | useL2TP: () => void;
3 | useOpenVPN: () => void;
4 | }
5 |
6 | interface ExtraVPNConnection {
7 | useV2Ray: () => void;
8 | useShadowsocks: () => void;
9 | }
10 |
11 | class ExternalNetwork implements BaseVPNConnection, ExtraVPNConnection {
12 | useL2TP() {
13 | console.log("L2TP VPN is ready for your external network!");
14 | }
15 |
16 | useOpenVPN() {
17 | console.log("OpenVPN is ready for your external network!");
18 | }
19 |
20 | useV2Ray() {
21 | console.log("V2Ray is ready for your external network!");
22 | }
23 |
24 | useShadowsocks() {
25 | console.log("Shadowsocks is ready for your external network!");
26 | }
27 | }
28 |
29 | class InternalNetwork implements BaseVPNConnection {
30 | useL2TP() {
31 | console.log("L2TP VPN is ready for your internal network!");
32 | }
33 |
34 | useOpenVPN() {
35 | console.log("OpenVPN is ready for your internal network!");
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Examples/SOLID/+LiskovSubstitution.ts:
--------------------------------------------------------------------------------
1 | class AudioProcessor {
2 | constructor(protected audioFile: File) {}
3 |
4 | compress() {
5 | // Compress the size of the audio
6 | }
7 |
8 | changeTempo() {
9 | // Increase the size of the audio
10 | }
11 | }
12 |
13 | class PremiumAudioProcessor extends AudioProcessor {
14 | constructor(audioFile: File) {
15 | super(audioFile);
16 | }
17 |
18 | separateMusicAndVocal() {
19 | // Remove the background of the audio
20 | }
21 |
22 | enhanceQualityWithAI() {
23 | // Enhance the quality of the audio with AI
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Examples/SOLID/+OpenClosed.ts:
--------------------------------------------------------------------------------
1 | interface QueryGenerator {
2 | getReadingQuery: () => string;
3 | getWritingQuery: (data: string) => string;
4 | }
5 |
6 | class MySql implements QueryGenerator {
7 | getReadingQuery() {
8 | return "SELECT * FROM MySQL";
9 | }
10 |
11 | getWritingQuery(data: string) {
12 | return `INSERT INTO MySQL VALUES (${data})`;
13 | }
14 | }
15 |
16 | class Redis implements QueryGenerator {
17 | getReadingQuery() {
18 | return "SCAN 0";
19 | }
20 |
21 | getWritingQuery(data: string) {
22 | return `SET ${data}`;
23 | }
24 | }
25 |
26 | class Neo4j implements QueryGenerator {
27 | getReadingQuery() {
28 | return "MATCH (n) RETURN n";
29 | }
30 |
31 | getWritingQuery(data: string) {
32 | return `CREATE (${data})`;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Examples/SOLID/+SingleResponsibility.ts:
--------------------------------------------------------------------------------
1 | class Settings {
2 | constructor(
3 | protected theme: "LIGHT" | "DARK",
4 | protected preferredLanguage: string,
5 | ) {}
6 |
7 | public toggleTheme(): void {
8 | if (this.theme === "LIGHT") {
9 | this.theme = "DARK";
10 | } else {
11 | this.theme = "LIGHT";
12 | }
13 | }
14 |
15 | public updatePreferredLanguage(language: string): void {
16 | this.preferredLanguage = language;
17 | }
18 |
19 | public getSettings() {
20 | return { theme: this.theme, preferredLanguage: this.preferredLanguage };
21 | }
22 | }
23 |
24 | class Profile {
25 | constructor(
26 | protected email: string,
27 | protected bio: string,
28 | protected settings: Settings,
29 | ) {}
30 |
31 | public updateEmail(email: string): void {
32 | this.email = email;
33 | }
34 |
35 | public updateBio(bio: string): void {
36 | this.bio = bio;
37 | }
38 |
39 | public getProfile() {
40 | return { email: this.email, bio: this.bio, settings: this.settings.getSettings() };
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Examples/SOLID/-DependencyInversion.ts:
--------------------------------------------------------------------------------
1 | class TelegramApi {
2 | start() {
3 | console.log("You are connected to Telegram API!");
4 | }
5 |
6 | messageTo(targetId: number, message: string) {
7 | console.log(`${message} sent to ${targetId} by Telegram!`);
8 | }
9 | }
10 |
11 | class WhatsappApi {
12 | setup() {
13 | console.log("You are connected to Whatsapp API!");
14 | }
15 |
16 | pushMessage(message: string, targetId: number) {
17 | console.log(`${message} sent to ${targetId} by Whatsapp!`);
18 | }
19 | }
20 |
21 | class SignalApi {
22 | open() {
23 | console.log("You are connected to Signal API!");
24 | }
25 |
26 | postMessage(params: { id: number; text: string }) {
27 | console.log(`${params.text} sent to ${params.id} by Signal!`);
28 | }
29 | }
30 |
31 | class Messenger {
32 | constructor(private api: TelegramApi | WhatsappApi | SignalApi) {}
33 |
34 | sendMessage(targetId: number, message: string) {
35 | if (this.api instanceof TelegramApi) {
36 | this.api.start();
37 | this.api.messageTo(targetId, message);
38 | } else if (this.api instanceof WhatsappApi) {
39 | this.api.setup();
40 | this.api.pushMessage(message, targetId);
41 | } else {
42 | this.api.open();
43 | this.api.postMessage({ id: targetId, text: message });
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/Examples/SOLID/-InterfaceSegregation.ts:
--------------------------------------------------------------------------------
1 | interface VPNConnection {
2 | useL2TP: () => void;
3 | useOpenVPN: () => void;
4 | useV2Ray: () => void;
5 | useShadowsocks: () => void;
6 | }
7 |
8 | class ExternalNetwork implements VPNConnection {
9 | useL2TP() {
10 | console.log("L2TP VPN is ready for your external network!");
11 | }
12 |
13 | useOpenVPN() {
14 | console.log("OpenVPN is ready for your external network!");
15 | }
16 |
17 | useV2Ray() {
18 | console.log("V2Ray is ready for your external network!");
19 | }
20 |
21 | useShadowsocks() {
22 | console.log("Shadowsocks is ready for your external network!");
23 | }
24 | }
25 |
26 | class InternalNetwork implements VPNConnection {
27 | useL2TP() {
28 | console.log("L2TP VPN is ready for your internal network!");
29 | }
30 |
31 | useOpenVPN() {
32 | console.log("OpenVPN is ready for your internal network!");
33 | }
34 |
35 | useV2Ray() {
36 | throw Error("V2Ray is not available for your internal network!");
37 | }
38 |
39 | useShadowsocks() {
40 | throw Error("Shadowsocks is not available for your internal network!");
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Examples/SOLID/-LiskovSubstitution.ts:
--------------------------------------------------------------------------------
1 | class AudioProcessor {
2 | constructor(protected audioFile: File) {}
3 |
4 | compress() {
5 | // Compress the size of the audio
6 | }
7 |
8 | changeTempo() {
9 | // Increase the size of the audio
10 | }
11 |
12 | separateMusicAndVocal() {
13 | // Remove the background of the audio
14 | }
15 |
16 | enhanceQualityWithAI() {
17 | // Enhance the quality of the audio with AI
18 | }
19 | }
20 |
21 | class LimitedAudioProcessor extends AudioProcessor {
22 | constructor(audioFile: File) {
23 | super(audioFile);
24 | }
25 |
26 | override separateMusicAndVocal(): Error {
27 | throw Error("You have to buy the premium version to access this feature!");
28 | }
29 |
30 | override enhanceQualityWithAI(): Error {
31 | throw Error("You have to buy the premium version to access this feature!");
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Examples/SOLID/-OpenClosed.ts:
--------------------------------------------------------------------------------
1 | type DB = "MySQL" | "Redis" | "Neo4j";
2 |
3 | class QueryGenerator {
4 | getReadingQuery(database: DB): string {
5 | switch (database) {
6 | case "MySQL":
7 | return "SELECT * FROM MySQL";
8 | case "Redis":
9 | return "SCAN 0";
10 | case "Neo4j":
11 | return "MATCH (n) RETURN n";
12 | default:
13 | return "Unknown";
14 | }
15 | }
16 |
17 | getWritingQuery(database: DB, data: string): string {
18 | switch (database) {
19 | case "MySQL":
20 | return `INSERT INTO MySQL VALUES (${data})`;
21 | case "Redis":
22 | return `SET ${data}`;
23 | case "Neo4j":
24 | return `CREATE (${data})`;
25 | default:
26 | return "Unknown";
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Examples/SOLID/-SingleResponsibility.ts:
--------------------------------------------------------------------------------
1 | class Profile {
2 | private email: string;
3 | private bio: string;
4 | private theme: "LIGHT" | "DARK";
5 | private preferredLanguage: string;
6 |
7 | constructor(params: { email: string; bio: string; theme: "LIGHT" | "DARK"; preferredLanguage: string }) {
8 | const { email, bio, theme, preferredLanguage } = params;
9 |
10 | this.email = email;
11 | this.bio = bio;
12 | this.theme = theme;
13 | this.preferredLanguage = preferredLanguage;
14 | }
15 |
16 | public updateEmail(email: string): void {
17 | this.email = email;
18 | }
19 |
20 | public updateBio(bio: string): void {
21 | this.bio = bio;
22 | }
23 |
24 | public toggleTheme(): void {
25 | if (this.theme === "LIGHT") {
26 | this.theme = "DARK";
27 | } else {
28 | this.theme = "LIGHT";
29 | }
30 | }
31 |
32 | public updatePreferredLanguage(language: string): void {
33 | this.preferredLanguage = language;
34 | }
35 |
36 | public getProfile() {
37 | return {
38 | email: this.email,
39 | bio: this.bio,
40 | theme: this.theme,
41 | preferredLanguage: this.preferredLanguage,
42 | };
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Object Oriented Programming Expert with TypeScript
2 |
3 | This repository is a complete guide and tutorial for the principles and techniques of object-oriented programming. It can be a reference for all interested in programming and software developers. You will find simple and practical examples in all sections to make the concepts easier to understand.
4 |
5 | ## Table of Contents
6 |
7 | 1. [Fundamentals](#fundamentals)
8 | - [What's Object-Oriented-Programming?](#whats-object-oriented-programming)
9 | - [Class](#class)
10 | - [Objects](#objects)
11 | - [Abstraction](#abstraction)
12 | - [Encapsulation](#encapsulation)
13 | - [Inheritance](#inheritance)
14 | - [Polymorphism](#polymorphism)
15 | 2. [SOLID Principles](#solid-principles)
16 | - [What's SOLID Meaning?](#whats-solid-meaning)
17 | - [Single Responsibility (SRP)](#1-single-responsibility-srp)
18 | - [Open/Closed (OCP)](#2-openclosed-ocp)
19 | - [Liskov Substitution (LSP)](#3-liskov-substitution-lsp)
20 | - [Interface Segregation (ISP)](#4-interface-segregation-isp)
21 | - [Dependency Inversion (DIP)](#5-dependency-inversion-dip)
22 | 3. [Design Patterns](#design-patterns)
23 | - [What's a Design Pattern?](#whats-a-design-pattern)
24 | - [Creational - Abstract Factory](#abstract-factory)
25 | - [Creational - Builder](#builder)
26 | - [Creational - Factory Method or Virtual Constructor](#factory-method)
27 | - [Creational - Prototype or Clone](#prototype)
28 | - [Creational - Singleton](#singleton)
29 | - [Structural - Adapter or Wrapper](#adapter-wrapper)
30 | - [Structural - Bridge](#bridge)
31 | - [Structural - Composite or Object Tree](#composite-object-tree)
32 | - [Structural - Decorator or Wrapper](#decorator-wrapper)
33 | - [Structural - Facade](#facade)
34 | - [Structural - Flyweight or Cache](#flyweight-cache)
35 | - [Structural - Proxy](#proxy)
36 | - [Behavioral - Chain of Responsibility](#chain-of-responsibility)
37 | - [Behavioral - Command or Action or Transaction](#command)
38 | - [Behavioral - Interpreter](#interpreter)
39 | - [Behavioral - Iterator](#iterator)
40 | - [Behavioral - Mediator or Intermediary or Controller](#mediator)
41 | - [Behavioral - Memento or Snapshot](#memento)
42 | - [Behavioral - Observer or Event-Subscriber or Listener](#observer)
43 | - [Behavioral - State](#state)
44 | - [Behavioral - Strategy](#strategy)
45 | - [Behavioral - Template Method](#template-method)
46 | - [Behavioral - Visitor](#visitor)
47 | 4. [References](#references)
48 | 5. [Contributing and Supporting](#contributing-and-supporting)
49 |
50 | [`⬆ BACK TO TOP ⬆`](#table-of-contents)
51 |
52 | ## Fundamentals
53 |
54 | ### What's Object-oriented-programming?
55 |
56 | Object-oriented programming (OOP) is a programming paradigm based on the concept of "objects", which can contain data and code: data in the form of fields (often known as attributes or properties), and code, in the form of procedures (often known as methods). There are 6 pillars of OOP, includes:
57 |
58 | 1. *Class*
59 | 2. *Objects*
60 | 3. *Data Abstraction*
61 | 4. *Encapsulation*
62 | 5. *Inheritance*
63 | 6. *Polymorphism*
64 |
65 | [`⬆ BACK TO TOP ⬆`](#table-of-contents)
66 |
67 | ### Class
68 |
69 | > A class is a blueprint for creating objects (instances) that share common properties and behaviors. It serves as a template or a prototype from which objects are created. A class encapsulates data for the object and methods to operate on that data. It provides a way to structure and organize code in a modular and reusable manner.
70 |
71 | Here's a simple explanation of the concept of a class in TypeScript along with a real-world example:
72 |
73 | ```typescript
74 | class Task {
75 | // Properties
76 | private id: number;
77 | private title: string;
78 | private description: string;
79 | private dueDate: Date;
80 | private completed: boolean;
81 |
82 | // Constructor
83 | constructor(taskInfo: {
84 | id: number;
85 | title: string;
86 | description: string;
87 | dueDate: Date;
88 | }) {
89 | this.id = taskInfo.id;
90 | this.title = taskInfo.title;
91 | this.description = taskInfo.description;
92 | this.dueDate = taskInfo.dueDate;
93 | this.completed = false; // By default, the task is not completed
94 | }
95 |
96 | // Method to mark the task as completed
97 | public complete() {
98 | this.completed = true;
99 | }
100 |
101 | // Method to mark the task as incomplete
102 | public incomplete() {
103 | this.completed = false;
104 | }
105 | }
106 |
107 | // Create instances of the Task class using an object as a parameter
108 | const task1 = new Task({
109 | id: 1,
110 | title: "Finish report",
111 | description: "Write the quarterly report for the team meeting",
112 | dueDate: new Date("2120-03-25"),
113 | });
114 |
115 | const task2 = new Task({
116 | id: 2,
117 | title: "Buy groceries",
118 | description: "Buy milk, eggs, and bread",
119 | dueDate: new Date("2077-11-17"),
120 | });
121 |
122 | // Mark task1 as completed
123 | task1.complete();
124 |
125 | // Output task details
126 | console.log(task1);
127 | console.log(task2);
128 | ```
129 |
130 | In this example:
131 |
132 | We define a Task class with properties `id`, `title`, `description`, `dueDate`, and `completed`, along with methods `complete()` and `incomplete()` to mark tasks as completed or incomplete.
133 | We create instances of the Task class (task1 and task2) representing different tasks.
134 | We demonstrate marking task1 as completed and then output the details of both tasks.
135 |
136 | [`⬆ BACK TO TOP ⬆`](#table-of-contents)
137 |
138 | ### Objects
139 |
140 | > It is a basic unit of Object-Oriented Programming and represents the real-life entities. An Object is an instance of a Class. When a class is defined, no memory is allocated but when it is instantiated (i.e. an object is created) memory is allocated. An object has an identity, state, and behavior. Each object contains data and code to manipulate the data. Objects can interact without having to know details of each other’s data or code, it is sufficient to know the type of message accepted and type of response returned by the objects.
141 |
142 | For example *Dog* is a real-life object, which has some characteristics like color, breed, bark, sleep, and eats.
143 |
144 | [`⬆ BACK TO TOP ⬆`](#table-of-contents)
145 |
146 | ### Abstraction
147 |
148 | > Abstraction is a concept that allows you to focus on the essential attributes and behaviors of an object while hiding the unnecessary details. It involves representing only the relevant characteristics of an object, and hiding the complex implementation details from the user.
149 |
150 | Let's break this down with a simple example:
151 |
152 | Consider a car. When you think about a car, you don't need to know every intricate detail of how the engine works or how the transmission shifts gears in order to drive it. Instead, you focus on the essential features like steering, accelerating, and braking.
153 |
154 | In OOP, abstraction allows us to create a `Car` class that encapsulates these essential features without revealing the internal complexities. Here's a basic implementation:
155 |
156 | ```typescript
157 | class Car {
158 | private brand: string;
159 | private model: string;
160 | private speed: number;
161 |
162 | constructor(brand: string, model: string) {
163 | this.brand = brand;
164 | this.model = model;
165 | this.speed = 0;
166 | }
167 |
168 | public accelerate(): void {
169 | this.speed += 10;
170 | }
171 |
172 | public brake(): void {
173 | this.speed -= 10;
174 | }
175 |
176 | public getSpeed(): number {
177 | return this.speed;
178 | }
179 | }
180 |
181 | // Create a car object
182 | const myCar: Car = new Car("Toyota", "Camry");
183 |
184 | // Accelerate the car
185 | myCar.accelerate();
186 |
187 | // Get the current speed
188 | console.log("Current speed:", myCar.getSpeed());
189 | ```
190 |
191 | In this example:
192 |
193 | We have a `Car` class with attributes like `make`, `model`, and `speed`.
194 | We define methods like `accelerate` and `brake` to manipulate the speed of the car.
195 | The user interacts with the car object through these methods without needing to know how they are implemented internally.
196 | So, in essence, abstraction allows us to think about objects at a higher level of understanding, focusing on what they do rather than how they do it.
197 |
198 | [`⬆ BACK TO TOP ⬆`](#table-of-contents)
199 |
200 | ### Encapsulation
201 |
202 | > Encapsulation is defined as the wrapping up of data under a single unit. It is the mechanism that binds together code and the data it manipulates. In Encapsulation, the variables or data of a class are hidden from any other class and can be accessed only through any member function of their class in which they are declared. As in encapsulation, the data in a class is hidden from other classes, so it is also known as data-hiding.
203 |
204 | Let's consider a simple example where encapsulation is used to control access to sensitive data. Imagine we have a `User` class representing users in a system, and we want to ensure that the user's password is not directly accessible from outside the class.
205 |
206 | ```typescript
207 | class User {
208 | private username: string;
209 | private password: string;
210 |
211 | constructor(username: string, password: string) {
212 | this.username = username;
213 | this.password = password;
214 | }
215 |
216 | // Method to authenticate user
217 | authenticate(enteredPassword: string): boolean {
218 | return enteredPassword === this.password;
219 | }
220 |
221 | // Method to change password
222 | changePassword(newPassword: string): void {
223 | this.password = newPassword;
224 | }
225 | }
226 |
227 | // Create a user
228 | const user = new User("Ahmad Jafari", "abcd1234");
229 |
230 | console.log(user.authenticate("12345678")); // Output: false
231 | console.log(user.authenticate("abcd1234")); // Output: true
232 |
233 | user.changePassword("Ab1234!?");
234 |
235 | console.log(user.authenticate("abcd1234")); // Output: false
236 | console.log(user.authenticate("Ab1234!?")); // Output: true
237 | ```
238 |
239 | In this example:
240 |
241 | - We define a `User` class with a private property `password`.
242 | - `password` property is encapsulated and cannot be directly accessed.
243 | - We provide public methods `authenticate()` to verify the user's password and `changePassword()` to allow users to change their password.
244 | - Accessing or modifying the password property directly from outside the class is not allowed due to its private access modifier.
245 | - Encapsulation ensures that sensitive data (password) is hidden and can only be accessed or modified through controlled methods, enhancing security and preventing unauthorized access or manipulation.
246 |
247 | [`⬆ BACK TO TOP ⬆`](#table-of-contents)
248 |
249 | ### Inheritance
250 |
251 | > Inheritance in OOP is a concept where a new class (called a subclass or derived class) is created based on an existing class (called a superclass or base class). The subclass inherits attributes and behaviors (methods) from its superclass, allowing it to reuse and extend the functionality of the superclass.
252 |
253 | Using the `Shape` class example:
254 |
255 | ```typescript
256 | // Base class representing a generic shape
257 | class Shape {
258 | x: number;
259 | y: number;
260 |
261 | constructor(x: number, y: number) {
262 | this.x = x;
263 | this.y = y;
264 | }
265 |
266 | // Method to describe the shape
267 | describe(): void {
268 | console.log(`This is a shape at position (${this.x}, ${this.y}).`);
269 | }
270 | }
271 | ```
272 |
273 | Here, `Shape` is the base class. It has properties like x and y, along with a method describe() to provide information about the shape.
274 |
275 | ```typescript
276 | // Derived class representing a circle
277 | class Circle extends Shape {
278 | radius: number;
279 |
280 | constructor(x: number, y: number, radius: number) {
281 | super(x, y); // Call the constructor of the superclass (Shape)
282 | this.radius = radius;
283 | }
284 |
285 | // Method to calculate area of the circle
286 | area(): number {
287 | return Math.PI * this.radius ** 2;
288 | }
289 | }
290 | ```
291 |
292 | In this example, `Circle` is the subclass, and it extends the `Shape` class. By using the extends keyword, Circle inherits all properties and methods from Shape. Additionally, Circle has its own property `radius` and method `area()` specific to circles.
293 |
294 | By utilizing inheritance, you can create a hierarchy of classes where subclasses inherit and extend the functionality of their superclass, promoting code reusability and maintaining a logical structure in your programs.
295 |
296 | [`⬆ BACK TO TOP ⬆`](#table-of-contents)
297 |
298 | ### Polymorphism
299 |
300 | > Polymorphism in OOP refers to the ability of different objects to be treated as instances of a common superclass. Simply put, it allows objects of different classes to be treated as objects of a shared superclass. This enables more flexible and dynamic code, as different objects can respond to the same method call in different ways.
301 |
302 | Let's consider a real-world example involving shapes. We'll create a program that calculates the area of different shapes such as rectangles, circles, and triangles using polymorphism.
303 |
304 | ```typescript
305 | // Parent class
306 | abstract class Shape {
307 | abstract calculateArea(): number;
308 | }
309 |
310 | // Rectangle class
311 | class Rectangle extends Shape {
312 | constructor(private width: number, private height: number) {
313 | super();
314 | }
315 |
316 | calculateArea(): number {
317 | return this.width * this.height;
318 | }
319 | }
320 |
321 | // Circle class
322 | class Circle extends Shape {
323 | constructor(private radius: number) {
324 | super();
325 | }
326 |
327 | calculateArea(): number {
328 | return Math.PI * this.radius ** 2;
329 | }
330 | }
331 |
332 | // Triangle class
333 | class Triangle extends Shape {
334 | constructor(private base: number, private height: number) {
335 | super();
336 | }
337 |
338 | calculateArea(): number {
339 | return 0.5 * this.base * this.height;
340 | }
341 | }
342 |
343 | // Function to calculate the area of any shape
344 | function calculateShapeArea(shape: Shape): number {
345 | return shape.calculateArea();
346 | }
347 |
348 | // Creating instances of different shapes
349 | const rectangle = new Rectangle(5, 10);
350 | const circle = new Circle(7);
351 | const triangle = new Triangle(4, 6);
352 |
353 | // Using the function with different shape objects
354 | console.log("Area of Rectangle:", calculateShapeArea(rectangle)); // Outputs: 50
355 | console.log("Area of Circle:", calculateShapeArea(circle).toFixed(2)); // Outputs: 153.94
356 | console.log("Area of Triangle:", calculateShapeArea(triangle)); // Outputs: 12
357 | ```
358 |
359 | In this example, `Shape` is the superclass, and `Rectangle`, `Circle`, and `Triangle` are its subclasses. They all implement the `calculateArea()` method differently according to their specific shapes. When we call `calculateShapeArea()` with different shape objects, polymorphism allows the correct version of `calculateArea()` to be called based on the type of shape passed. This demonstrates how polymorphism enables code to handle different types of objects in a unified manner.
360 |
361 | [`⬆ BACK TO TOP ⬆`](#table-of-contents)
362 |
363 | ## SOLID Principles
364 |
365 | ### What's SOLID meaning?
366 |
367 | In software engineering, SOLID is a mnemonic acronym for five design principles intended to make software designs more understandable, flexible, and maintainable. The principles are a subset of many principles promoted by American software engineer and instructor Robert C. Martin, first introduced in his 2000 paper Design Principles and Design Patterns.
368 |
369 |
370 |
371 | [`⬆ BACK TO TOP ⬆`](#table-of-contents)
372 |
373 | ### 1. Single Responsibility (SRP)
374 |
375 | #### Original Definition
376 |
377 | > There should never be more than one reason for a class to change. Every class should have only one responsibility.
378 |
379 | #### Simple Definition
380 |
381 | > SRP means that each class should only be responsible for one thing. It keeps classes focused and makes code easier to understand and maintain.
382 |
383 |
384 |
385 | #### Example
386 |
387 | Before following the Single Responsibility Principle (SRP), the `Profile` class was handling both user profile data (like email, bio, etc.) and user settings (theme and preferredLanguage). This violated SRP because a class should have only one reason to change, but here the Profile class had multiple reasons to change - if either the profile data structure or the settings structure changed.
388 |
389 | After following SRP, the code was refactored to separate concerns. The Profile class now only deals with profile-related information such as email and bio. The settings-related functionality has been moved to a new Settings class. This change improves maintainability and makes the codebase more flexible. Now, if there's a need to update how settings are handled, it only affects the Settings class, keeping the Profile class untouched. Additionally, it enhances code readability and makes it easier to understand the purpose of each class.
390 |
391 | ##### ❌ Before following SRP:
392 |
393 | ```typescript
394 | class Profile {
395 | private email: string;
396 | private bio: string;
397 | private theme: "LIGHT" | "DARK";
398 | private preferredLanguage: string;
399 |
400 | constructor(params: { email: string; bio: string; theme: "LIGHT" | "DARK"; preferredLanguage: string }) {
401 | const { email, bio, theme, preferredLanguage } = params;
402 |
403 | this.email = email;
404 | this.bio = bio;
405 | this.theme = theme;
406 | this.preferredLanguage = preferredLanguage;
407 | }
408 |
409 | public updateEmail(email: string): void {
410 | this.email = email;
411 | }
412 |
413 | public updateBio(bio: string): void {
414 | this.bio = bio;
415 | }
416 |
417 | public toggleTheme(): void {
418 | if (this.theme === "LIGHT") {
419 | this.theme = "DARK";
420 | } else {
421 | this.theme = "LIGHT";
422 | }
423 | }
424 |
425 | public updatePreferredLanguage(language: string): void {
426 | this.preferredLanguage = language;
427 | }
428 |
429 | public getProfile() {
430 | return {
431 | email: this.email,
432 | bio: this.bio,
433 | theme: this.theme,
434 | preferredLanguage: this.preferredLanguage,
435 | };
436 | }
437 | }
438 |
439 | ```
440 |
441 | ##### ✔️ After following SRP:
442 |
443 | ```typescript
444 | class Settings {
445 | constructor(
446 | protected theme: "LIGHT" | "DARK",
447 | protected preferredLanguage: string,
448 | ) {}
449 |
450 | public toggleTheme(): void {
451 | if (this.theme === "LIGHT") {
452 | this.theme = "DARK";
453 | } else {
454 | this.theme = "LIGHT";
455 | }
456 | }
457 |
458 | public updatePreferredLanguage(language: string): void {
459 | this.preferredLanguage = language;
460 | }
461 |
462 | public getSettings() {
463 | return { theme: this.theme, preferredLanguage: this.preferredLanguage };
464 | }
465 | }
466 |
467 | class Profile {
468 | constructor(
469 | protected email: string,
470 | protected bio: string,
471 | protected settings: Settings,
472 | ) {}
473 |
474 | public updateEmail(email: string): void {
475 | this.email = email;
476 | }
477 |
478 | public updateBio(bio: string): void {
479 | this.bio = bio;
480 | }
481 |
482 | public getProfile() {
483 | return { email: this.email, bio: this.bio, settings: this.settings.getSettings() };
484 | }
485 | }
486 |
487 | ```
488 |
489 | [`⬆ BACK TO TOP ⬆`](#table-of-contents)
490 |
491 | ### 2. Open/Closed (OCP)
492 |
493 | #### Original Definition
494 |
495 | > Software entities should be open for extension, but closed for modification.
496 |
497 | #### Simple Definition
498 |
499 | > The Open/Closed Principle means that once you write a piece of code, you should be able to add new functionality to it without changing the existing code. It promotes extending the behavior of software rather than altering it, ensuring that changes don't break existing functionality.
500 |
501 |
502 |
503 | #### Example
504 |
505 | Before OCP implementation, the `QueryGenerator` class directly handles different types of databases, such as _MySQL_, _Redis_, and _Neo4j_, within its methods. This violates the Open/Closed Principle because if you want to add support for a new database, you would need to modify the `QueryGenerator` class by adding a new case to each switch statement. This could lead to the class becoming bloated and tightly coupled to specific database implementations, making it harder to maintain and extend.
506 |
507 | After implementing OCP, the code is refactored to use interfaces and separate classes for each database type. Now, the QueryGenerator interface defines common methods `getReadingQuery` and `getWritingQuery`, while individual database classes (`MySql`, `Redis`, and `Neo4j`) implement these methods according to their specific behavior.
508 |
509 | This approach adheres to the Open/Closed Principle because the `QueryGenerator` interface is open for extension, allowing you to add support for new databases by creating new classes that implement the interface, without modifying existing code. Additionally, it's closed for modification because changes to existing database classes won't affect the `QueryGenerator` interface or other database implementations. This results in a more flexible, maintainable, and scalable design.
510 |
511 | ##### ❌ Before following OCP:
512 |
513 | ```typescript
514 | type DB = "MySQL" | "Redis" | "Neo4j";
515 |
516 | class QueryGenerator {
517 | getReadingQuery(database: DB): string {
518 | switch (database) {
519 | case "MySQL":
520 | return "SELECT * FROM MySQL";
521 | case "Redis":
522 | return "SCAN 0";
523 | case "Neo4j":
524 | return "MATCH (n) RETURN n";
525 | default:
526 | return "Unknown";
527 | }
528 | }
529 |
530 | getWritingQuery(database: DB, data: string): string {
531 | switch (database) {
532 | case "MySQL":
533 | return `INSERT INTO MySQL VALUES (${data})`;
534 | case "Redis":
535 | return `SET ${data}`;
536 | case "Neo4j":
537 | return `CREATE (${data})`;
538 | default:
539 | return "Unknown";
540 | }
541 | }
542 | }
543 |
544 | ```
545 |
546 | ##### ✔️ After following OCP:
547 |
548 | ```typescript
549 | interface QueryGenerator {
550 | getReadingQuery: () => string;
551 | getWritingQuery: (data: string) => string;
552 | }
553 |
554 | class MySql implements QueryGenerator {
555 | getReadingQuery() {
556 | return "SELECT * FROM MySQL";
557 | }
558 |
559 | getWritingQuery(data: string) {
560 | return `INSERT INTO MySQL VALUES (${data})`;
561 | }
562 | }
563 |
564 | class Redis implements QueryGenerator {
565 | getReadingQuery() {
566 | return "SCAN 0";
567 | }
568 |
569 | getWritingQuery(data: string) {
570 | return `SET ${data}`;
571 | }
572 | }
573 |
574 | class Neo4j implements QueryGenerator {
575 | getReadingQuery() {
576 | return "MATCH (n) RETURN n";
577 | }
578 |
579 | getWritingQuery(data: string) {
580 | return `CREATE (${data})`;
581 | }
582 | }
583 |
584 | ```
585 |
586 | [`⬆ BACK TO TOP ⬆`](#table-of-contents)
587 |
588 | ### 3. Liskov Substitution (LSP)
589 |
590 | #### Original Definition
591 |
592 | > If `S` is a subtype of `T`, then objects of type `T` in a program may be replaced with objects of type `S` without altering any of the desirable properties of that program.
593 |
594 | #### Simple Definition
595 |
596 | > The LSP says that if you have a class, you should be able to use any of its subclasses interchangeably without breaking the program.
597 |
598 |
599 |
600 | #### Example
601 |
602 | In the initial example, we have an `ImageProcessor` class responsible for various image processing operations such as **compression**, **enhancing size**, **removing background**, and **enhancing quality with AI**. There's also a `LimitedImageProcessor` class that extends `ImageProcessor`, but it overrides the `removeBackground` and `enhanceQualityWithAI` methods to throw errors indicating that these features are only available in the premium version.
603 |
604 | This violates the Liskov Substitution Principle because substituting an instance of `LimitedImageProcessor` for an instance of `ImageProcessor` could lead to unexpected errors if code relies on those overridden methods.
605 |
606 | To adhere to the LSP, we refactor the classes. We create a `PremiumImageProcessor` class that extends `ImageProcessor` and implements the `removeBackground` and `enhanceQualityWithAI` methods. This way, both classes share a common interface and substituting an instance of `PremiumImageProcessor` for an instance of `ImageProcessor` won't break the program's correctness.
607 |
608 | In the refactored version, `ImageProcessor` is now focused on basic image processing operations like compression and enhancing size, while `PremiumImageProcessor` extends it to include premium features like removing background and enhancing quality with AI. This separation allows for better code organization and adherence to the Liskov Substitution Principle.
609 |
610 | ##### ❌ Before following LSP:
611 |
612 | ```typescript
613 | class AudioProcessor {
614 | constructor(protected audioFile: File) {}
615 |
616 | compress() {
617 | // Compress the size of the audio
618 | }
619 |
620 | changeTempo() {
621 | // Increase the size of the audio
622 | }
623 |
624 | separateMusicAndVocal() {
625 | // Remove the background of the audio
626 | }
627 |
628 | enhanceQualityWithAI() {
629 | // Enhance the quality of the audio with AI
630 | }
631 | }
632 |
633 | class LimitedAudioProcessor extends AudioProcessor {
634 | constructor(audioFile: File) {
635 | super(audioFile);
636 | }
637 |
638 | override separateMusicAndVocal(): Error {
639 | throw Error("You have to buy the premium version to access this feature!");
640 | }
641 |
642 | override enhanceQualityWithAI(): Error {
643 | throw Error("You have to buy the premium version to access this feature!");
644 | }
645 | }
646 |
647 | ```
648 |
649 | ##### ✔️ After following LSP:
650 |
651 | ```typescript
652 | class AudioProcessor {
653 | constructor(protected audioFile: File) {}
654 |
655 | compress() {
656 | // Compress the size of the audio
657 | }
658 |
659 | changeTempo() {
660 | // Increase the size of the audio
661 | }
662 | }
663 |
664 | class PremiumAudioProcessor extends AudioProcessor {
665 | constructor(audioFile: File) {
666 | super(audioFile);
667 | }
668 |
669 | separateMusicAndVocal() {
670 | // Remove the background of the audio
671 | }
672 |
673 | enhanceQualityWithAI() {
674 | // Enhance the quality of the audio with AI
675 | }
676 | }
677 |
678 | ```
679 |
680 | [`⬆ BACK TO TOP ⬆`](#table-of-contents)
681 |
682 | ### 4. Interface Segregation (ISP)
683 |
684 | #### Original Definition
685 |
686 | > No code should be forced to depend on methods it does not use.
687 |
688 | #### Simple Definition
689 |
690 | > The ISP means that clients should not be forced to implement methods they don't use. It's like saying, "Don't make people take things they don't need."
691 |
692 |
693 |
694 | #### Example
695 |
696 | In the initial implementation before applying the ISP, the `VPNConnection` interface encompasses methods for various VPN protocols, including `useL2TP`, `useOpenVPN`, `useV2Ray`, and `useShadowsocks`. However, not all classes implementing this interface require all these methods. For instance, the `InternalNetwork` class only utilizes `useL2TP` and `useOpenVPN`, yet it is forced to implement all methods defined in the `VPNConnection` interface, leading to unnecessary dependencies and potential errors if methods are called inappropriately.
697 |
698 | To address this issue, the Interface Segregation Principle suggests breaking down the monolithic interface into smaller, more focused interfaces. In the improved implementation, two interfaces are introduced: `BaseVPNConnection` and `ExtraVPNConnection`. The `BaseVPNConnection` interface contains methods common to both external and internal networks (`useL2TP` and `useOpenVPN`), while the `ExtraVPNConnection` interface includes methods specific to external networks (`useV2Ray` and `useShadowsocks`).
699 |
700 | With this segregation, the `InternalNetwork` class now only needs to implement the methods relevant to its functionality, adhering to the principle of "clients should not be forced to depend on interfaces they do not use." This restructuring enhances code clarity, reduces unnecessary dependencies, and makes the system more maintainable and flexible. Additionally, it mitigates the risk of errors by ensuring that classes only expose the methods they actually support, promoting better encapsulation and separation of concerns.
701 |
702 | ##### ❌ Before following ISP:
703 |
704 | ```typescript
705 | interface VPNConnection {
706 | useL2TP: () => void;
707 | useOpenVPN: () => void;
708 | useV2Ray: () => void;
709 | useShadowsocks: () => void;
710 | }
711 |
712 | class ExternalNetwork implements VPNConnection {
713 | useL2TP() {
714 | console.log("L2TP VPN is ready for your external network!");
715 | }
716 |
717 | useOpenVPN() {
718 | console.log("OpenVPN is ready for your external network!");
719 | }
720 |
721 | useV2Ray() {
722 | console.log("V2Ray is ready for your external network!");
723 | }
724 |
725 | useShadowsocks() {
726 | console.log("Shadowsocks is ready for your external network!");
727 | }
728 | }
729 |
730 | class InternalNetwork implements VPNConnection {
731 | useL2TP() {
732 | console.log("L2TP VPN is ready for your internal network!");
733 | }
734 |
735 | useOpenVPN() {
736 | console.log("OpenVPN is ready for your internal network!");
737 | }
738 |
739 | useV2Ray() {
740 | throw Error("V2Ray is not available for your internal network!");
741 | }
742 |
743 | useShadowsocks() {
744 | throw Error("Shadowsocks is not available for your internal network!");
745 | }
746 | }
747 |
748 | ```
749 |
750 | ##### ✔️ After following ISP:
751 |
752 | ```typescript
753 | interface BaseVPNConnection {
754 | useL2TP: () => void;
755 | useOpenVPN: () => void;
756 | }
757 |
758 | interface ExtraVPNConnection {
759 | useV2Ray: () => void;
760 | useShadowsocks: () => void;
761 | }
762 |
763 | class ExternalNetwork implements BaseVPNConnection, ExtraVPNConnection {
764 | useL2TP() {
765 | console.log("L2TP VPN is ready for your external network!");
766 | }
767 |
768 | useOpenVPN() {
769 | console.log("OpenVPN is ready for your external network!");
770 | }
771 |
772 | useV2Ray() {
773 | console.log("V2Ray is ready for your external network!");
774 | }
775 |
776 | useShadowsocks() {
777 | console.log("Shadowsocks is ready for your external network!");
778 | }
779 | }
780 |
781 | class InternalNetwork implements BaseVPNConnection {
782 | useL2TP() {
783 | console.log("L2TP VPN is ready for your internal network!");
784 | }
785 |
786 | useOpenVPN() {
787 | console.log("OpenVPN is ready for your internal network!");
788 | }
789 | }
790 |
791 | ```
792 |
793 | [`⬆ BACK TO TOP ⬆`](#table-of-contents)
794 |
795 | ### 5. Dependency Inversion (DIP)
796 |
797 | #### Original Definition
798 |
799 | > High-level modules should not import anything from low-level modules. Both should depend on abstractions. Abstractions should not depend on details. Details (concrete implementations) should depend on abstractions.
800 |
801 | #### Simple Definition
802 |
803 | > DIP means that instead of high-level modules depending directly on low-level modules, both should depend on abstractions. This way, changes in low-level modules don't directly affect high-level ones, promoting flexible and maintainable code.
804 |
805 |
806 |
807 | #### Example
808 |
809 | In the original code, the `Messenger` class directly depends on specific implementations of messaging APIs like `TelegramApi`, `WhatsappApi`, and `SignalApi`. This tightly couples Messenger with these concrete implementations, making it difficult to change or extend the system without modifying the Messenger class itself. This violates the Dependency Inversion Principle (DIP), which suggests that high-level modules should not depend on low-level modules but rather on abstractions.
810 |
811 | To adhere to DIP, we introduce an interface called `MessengerApi`, which defines the methods that the Messenger class requires from a messaging API. Then, each messaging API class (`TelegramApi`, `WhatsappApi` and `SignalApi`) implements this interface, providing their own implementation of the connect and send methods.
812 |
813 | By doing this, we decouple the Messenger class from specific messaging API implementations. Now, Messenger depends on the MessengerApi interface rather than concrete implementations. This allows us to easily switch between different messaging APIs or add new ones without modifying the Messenger class. Additionally, it promotes code reusability and simplifies testing, as we can now easily mock the MessengerApi interface for testing purposes. Overall, following DIP enhances the flexibility, maintainability, and testability of the codebase.
814 |
815 | ##### ❌ Before following DIP:
816 |
817 | ```typescript
818 | class TelegramApi {
819 | start() {
820 | console.log("You are connected to Telegram API!");
821 | }
822 |
823 | messageTo(targetId: number, message: string) {
824 | console.log(`${message} sent to ${targetId} by Telegram!`);
825 | }
826 | }
827 |
828 | class WhatsappApi {
829 | setup() {
830 | console.log("You are connected to Whatsapp API!");
831 | }
832 |
833 | pushMessage(message: string, targetId: number) {
834 | console.log(`${message} sent to ${targetId} by Whatsapp!`);
835 | }
836 | }
837 |
838 | class SignalApi {
839 | open() {
840 | console.log("You are connected to Signal API!");
841 | }
842 |
843 | postMessage(params: { id: number; text: string }) {
844 | console.log(`${params.text} sent to ${params.id} by Signal!`);
845 | }
846 | }
847 |
848 | class Messenger {
849 | constructor(private api: TelegramApi | WhatsappApi | SignalApi) {}
850 |
851 | sendMessage(targetId: number, message: string) {
852 | if (this.api instanceof TelegramApi) {
853 | this.api.start();
854 | this.api.messageTo(targetId, message);
855 | } else if (this.api instanceof WhatsappApi) {
856 | this.api.setup();
857 | this.api.pushMessage(message, targetId);
858 | } else {
859 | this.api.open();
860 | this.api.postMessage({ id: targetId, text: message });
861 | }
862 | }
863 | }
864 |
865 | ```
866 |
867 | ##### ✔️ After following DIP:
868 |
869 | ```typescript
870 | interface MessengerApi {
871 | connect: () => void;
872 | send: (targetId: string, message: string) => void;
873 | }
874 |
875 | class TelegramApi implements MessengerApi {
876 | connect() {
877 | console.log("You are connected to Telegram API!");
878 | }
879 |
880 | send(targetId: string, message: string) {
881 | console.log(`${message} sent to ${targetId} by Telegram!`);
882 | }
883 | }
884 |
885 | class WhatsappApi implements MessengerApi {
886 | connect() {
887 | console.log("You are connected to Whatsapp API!");
888 | }
889 |
890 | send(targetId: string, message: string) {
891 | console.log(`${message} sent to ${targetId} by Whatsapp!`);
892 | }
893 | }
894 |
895 | class SignalApi implements MessengerApi {
896 | connect() {
897 | console.log("You are connected to Signal API!");
898 | }
899 |
900 | send(targetId: string, message: string) {
901 | console.log(`${message} sent to ${targetId} by Signal!`);
902 | }
903 | }
904 |
905 | class Messenger {
906 | constructor(private api: MessengerApi) {}
907 |
908 | sendMessage(targetId: string, message: string) {
909 | this.api.connect();
910 | this.api.send(targetId, message);
911 | }
912 | }
913 |
914 | ```
915 |
916 | [`⬆ BACK TO TOP ⬆`](#table-of-contents)
917 |
918 | ## Design Patterns
919 |
920 | ### What's a design pattern?
921 |
922 | In software engineering, a software design pattern is a general, reusable solution to a commonly occurring problem within a given context in software design. It is not a finished design that can be transformed directly into source or machine code. Rather, it is a description or template for how to solve a problem that can be used in many different situations. Design patterns are formalized best practices that the programmer can use to solve common problems when designing an application or system.
923 |
924 | There are 23 design patterns that are grouped into 3 categories:
925 |
926 | 1. **Creational**: Creational patterns provide various object creation mechanisms, which increase flexibility and reuse of existing code. Includes:
927 |
928 | - Abstract Factory
929 | - Builder
930 | - Factory Method
931 | - Prototype
932 | - Singleton
933 |
934 | 2. **Structural**: Structural patterns explain how to assemble objects and classes into larger structures while keeping these structures flexible and efficient. Includes:
935 |
936 | - Adapter
937 | - Bridge
938 | - Composite
939 | - Decorator
940 | - Facade
941 | - Flyweight
942 | - Proxy
943 |
944 | 3. **Behavioral**: Behavioral design patterns are concerned with algorithms and the assignment of responsibilities between objects. Includes:
945 |
946 | - Chain of Responsibility
947 | - Command
948 | - Interpreter
949 | - Iterator
950 | - Mediator
951 | - Memento
952 | - Observer
953 | - State
954 | - Strategy
955 | - Template Method
956 | - Visitor
957 |
958 | **Tip**: The order of design patterns isn't important. So, you can choose which one to learn, regardless of the category.
959 |
960 | [`⬆ BACK TO TOP ⬆`](#table-of-contents)
961 |
962 | ### Abstract Factory
963 |
964 | > Abstract Factory is a creational design pattern that lets you produce families of related objects without specifying their concrete classes. This pattern is particularly useful when a system needs to be independent of the way its products are created, composed, and represented. It also helps in enforcing that a set of products follow a consistent theme across different platforms.
965 |
966 |
967 |
968 | #### Example Context
969 |
970 | In this example, we demonstrate the Abstract Factory pattern through a media API system where different types of API providers (`Normal` and `Premium`) can be generated for movies and audio tracks. Each provider offers multiple ways to search within its respective domain.
971 |
972 | - **Interfaces Defined:**
973 | - `Movie`: Represents the structure of a movie object.
974 | - `AudioTrack`: Represents the structure of an audio track object.
975 | - `MovieApi` and `AudioApi`: Define the set of operations available for interacting with movies and audio tracks, respectively.
976 |
977 | - **Concrete Implementations:**
978 | - `NormalMovieApiProvider` and `NormalAudioApiProvider`: Implement the `MovieApi` and `AudioApi` interfaces with standard search operations.
979 | - `PremiumMovieApiProvider` and `PremiumAudioApiProvider`: Similar to the normal providers but designed to represent premium service capabilities.
980 |
981 | - **Factory Interfaces and Implementations:**
982 | - `ApiProviderFactory`: Defines the methods for creating movie and audio API providers.
983 | - `NormalApiProviderFactory` and `PremiumApiProviderFactory`: Implementations of the factory interface that create instances of normal and premium API providers.
984 |
985 | ##### Purpose
986 |
987 | This pattern allows for the dynamic creation of `MovieApi` and `AudioApi` services that adhere to whether the user has access to normal or premium features. The flexibility provided by the Abstract Factory pattern makes it easier to extend and maintain the system, as new provider types can be added with minimal changes to existing code.
988 |
989 | ##### How It Works
990 |
991 | - A client first decides on the type of factory to use (`NormalApiProviderFactory` or `PremiumApiProviderFactory`).
992 | - The factory then creates instances of `MovieApi` and `AudioApi` providers based on the chosen type.
993 | - From there, the client can utilize these providers to perform various search operations tailored to their specific needs.
994 |
995 | By abstracting the creation process, the code is cleaner and adheres to SOLID principles, making it a flexible solution for varying user tiers in media applications.
996 |
997 | ```typescript
998 | interface Movie {
999 | title: string;
1000 | artists: Array;
1001 | director: string;
1002 | releaseYear: number;
1003 | awards: Array;
1004 | duration: number;
1005 | }
1006 |
1007 | interface AudioTrack {
1008 | title: string;
1009 | artist: string;
1010 | genre: string;
1011 | mood: string;
1012 | lyric: string;
1013 | duration: number;
1014 | }
1015 |
1016 | interface MovieApi {
1017 | searchByTitle: (name: string) => Array;
1018 | searchByActors: (actors: Array) => Array;
1019 | searchByAwards: (awards: Array) => Array;
1020 | searchByDirector: (director: string) => Array;
1021 | releaseYear: (releaseYear: Date) => Array;
1022 | }
1023 |
1024 | interface AudioApi {
1025 | searchByTitle: (name: string) => Array;
1026 | searchByArtist: (artist: string) => Array;
1027 | searchByMood: (mood: string) => Array;
1028 | searchByGenre: (genre: string) => Array;
1029 | searchByLyric: (text: string) => Array;
1030 | }
1031 |
1032 | class NormalMovieApiProvider implements MovieApi {
1033 | searchByTitle(name: string) {
1034 | return [];
1035 | }
1036 | searchByActors(actors: Array) {
1037 | return [];
1038 | }
1039 | searchByAwards(awards: Array) {
1040 | return [];
1041 | }
1042 | searchByDirector(director: string) {
1043 | return [];
1044 | }
1045 | releaseYear(releaseYear: Date) {
1046 | return [];
1047 | }
1048 | }
1049 |
1050 | class NormalAudioApiProvider implements AudioApi {
1051 | searchByTitle(name: string) {
1052 | return [];
1053 | }
1054 | searchByArtist(artist: string) {
1055 | return [];
1056 | }
1057 | searchByMood(mood: string) {
1058 | return [];
1059 | }
1060 | searchByGenre(genre: string) {
1061 | return [];
1062 | }
1063 | searchByLyric(text: string) {
1064 | return [];
1065 | }
1066 | }
1067 |
1068 | class PremiumMovieApiProvider implements MovieApi {
1069 | searchByTitle(name: string) {
1070 | return [];
1071 | }
1072 | searchByActors(actors: Array) {
1073 | return [];
1074 | }
1075 | searchByAwards(awards: Array) {
1076 | return [];
1077 | }
1078 | searchByDirector(director: string) {
1079 | return [];
1080 | }
1081 | releaseYear(releaseYear: Date) {
1082 | return [];
1083 | }
1084 | }
1085 |
1086 | class PremiumAudioApiProvider implements AudioApi {
1087 | searchByTitle(name: string) {
1088 | return [];
1089 | }
1090 | searchByArtist(artist: string) {
1091 | return [];
1092 | }
1093 | searchByMood(mood: string) {
1094 | return [];
1095 | }
1096 | searchByGenre(genre: string) {
1097 | return [];
1098 | }
1099 | searchByLyric(text: string) {
1100 | return [];
1101 | }
1102 | }
1103 |
1104 | interface ApiProviderFactory {
1105 | createMovieApiProvider: () => MovieApi;
1106 | createAudioApiProvider: () => AudioApi;
1107 | }
1108 |
1109 | class NormalApiProviderFactory implements ApiProviderFactory {
1110 | createMovieApiProvider() {
1111 | return new NormalMovieApiProvider();
1112 | }
1113 |
1114 | createAudioApiProvider() {
1115 | return new NormalAudioApiProvider();
1116 | }
1117 | }
1118 |
1119 | class PremiumApiProviderFactory implements ApiProviderFactory {
1120 | createMovieApiProvider() {
1121 | return new PremiumMovieApiProvider();
1122 | }
1123 |
1124 | createAudioApiProvider() {
1125 | return new PremiumAudioApiProvider();
1126 | }
1127 | }
1128 |
1129 | ```
1130 |
1131 | [`⬆ BACK TO TOP ⬆`](#table-of-contents)
1132 |
1133 | ### Builder
1134 |
1135 | > Builder is a creational design pattern that lets you construct complex objects step by step. The pattern allows you to produce different types and representations of an object using the same construction code.
1136 |
1137 |
1138 |
1139 | #### Example Context
1140 |
1141 | In this example, we illustrate the Builder pattern through the creation of a `Page` object, which can represent different types of web pages with varying headers, bodies, and footers. We utilize builders to construct pages for specific purposes, like a personal blog or an online shop.
1142 |
1143 | - **Main Class:**
1144 | - `Page`: Represents a web page composed of header, body, and footer parts. It provides methods to set these parts.
1145 |
1146 | - **Builder Interface:**
1147 | - `PageBuilder`: Declares the generic methods for constructing different parts of a `Page` object, including the header, body, and footer.
1148 |
1149 | - **Concrete Builders:**
1150 | - `PersonalBlogPageBuilder`: Implements the `PageBuilder` interface to construct a page suitable for a personal blog. The header, body, and footer have blog-specific parts.
1151 | - `OnlineShopPageBuilder`: Another implementation of the `PageBuilder` interface for creating a page suited for an online shop, with distinct sections in the header, body, and footer.
1152 |
1153 | ##### Purpose
1154 |
1155 | The Builder pattern is employed here to manage the construction of a `Page` object that may consist of various optional parts, which allows for more flexible and maintainable code. By using different builders, we can easily create different representations of a page without altering the underlying logic and construction process.
1156 |
1157 | ##### How It Works
1158 |
1159 | - **Initialization**: Each specific `PageBuilder` implementation initializes a new `Page` object.
1160 | - **Building Process**: The client calls the builder's methods to set up each part of the page:
1161 | - `buildHeader()`: Constructs the header based on the page type.
1162 | - `buildBody()`: Adds the body components specific to the page context.
1163 | - `buildFooter()`: Defines the footer layout.
1164 |
1165 | Upon completion, the `getPage()` method is used to retrieve the fully constructed `Page` object.
1166 |
1167 | This organized step-by-step construction process makes it easier to create complex page objects that can be adapted and extended to accommodate new requirements without modifying existing code directly, following SOLID principles effectively.
1168 |
1169 | ```typescript
1170 | class Page {
1171 | private headerParts: Array;
1172 | private bodyParts: Array;
1173 | private footerParts: Array;
1174 |
1175 | constructor() {
1176 | this.headerParts = [];
1177 | this.bodyParts = [];
1178 | this.footerParts = [];
1179 | }
1180 |
1181 | public setHeaderParts(...parts: Array) {
1182 | this.headerParts = parts;
1183 | }
1184 |
1185 | public setBodyParts(...parts: Array) {
1186 | this.bodyParts = parts;
1187 | }
1188 |
1189 | public setFooterParts(...parts: Array) {
1190 | this.footerParts = parts;
1191 | }
1192 |
1193 | public getPage() {
1194 | return {
1195 | headerParts: this.headerParts,
1196 | bodyParts: this.bodyParts,
1197 | footerParts: this.footerParts,
1198 | };
1199 | }
1200 | }
1201 |
1202 | interface PageBuilder {
1203 | getPage: () => Page;
1204 | buildHeader: () => void;
1205 | buildBody: () => void;
1206 | buildFooter: () => void;
1207 | }
1208 |
1209 | class PersonalBlogPageBuilder implements PageBuilder {
1210 | private page: Page;
1211 |
1212 | constructor() {
1213 | this.page = new Page();
1214 | }
1215 |
1216 | public getPage() {
1217 | return this.page;
1218 | }
1219 |
1220 | public buildHeader() {
1221 | this.page.setHeaderParts("Title", "Author Information");
1222 | }
1223 |
1224 | public buildBody() {
1225 | this.page.setBodyParts("Recent Posts", "Favorite Posts", "Last Comments");
1226 | }
1227 |
1228 | public buildFooter() {
1229 | this.page.setFooterParts("CopyRights", "Author Email Address");
1230 | }
1231 | }
1232 |
1233 | class OnlineShopPageBuilder implements PageBuilder {
1234 | private page: Page;
1235 |
1236 | constructor() {
1237 | this.page = new Page();
1238 | }
1239 |
1240 | public getPage() {
1241 | return this.page;
1242 | }
1243 |
1244 | public buildHeader() {
1245 | this.page.setHeaderParts("Logo", "Description", "Products Category Menu");
1246 | }
1247 |
1248 | public buildBody() {
1249 | this.page.setBodyParts("New Products", "Daily Off", "Suggested Products");
1250 | }
1251 |
1252 | public buildFooter() {
1253 | this.page.setFooterParts("About Us", "Address", "Legal Certificate Link");
1254 | }
1255 | }
1256 |
1257 | ```
1258 |
1259 | [`⬆ BACK TO TOP ⬆`](#table-of-contents)
1260 |
1261 | ### Factory Method
1262 |
1263 | > Factory Method is a creational design pattern that provides an interface for creating objects in a superclass, but allows subclasses to alter the type of objects that will be created.
1264 |
1265 |
1266 |
1267 | ```typescript
1268 | enum PaymentType {
1269 | Paypal = "PAYPAL",
1270 | Bitcoin = "BITCOIN",
1271 | VisaCard = "VISA_CARD",
1272 | }
1273 |
1274 | abstract class PaymentService {
1275 | public abstract payMoney(amount: number): void;
1276 | }
1277 |
1278 | class Paypal extends PaymentService {
1279 | public override payMoney(amount: number) {
1280 | console.log(`You paid ${amount} dollars by Paypal.`);
1281 | }
1282 | }
1283 |
1284 | class Bitcoin extends PaymentService {
1285 | public override payMoney(amount: number) {
1286 | console.log(`You paid ${amount} dollars by Bitcoin.`);
1287 | }
1288 | }
1289 |
1290 | class VisaCard extends PaymentService {
1291 | public override payMoney(amount: number) {
1292 | console.log(`You paid ${amount} dollars by VisaCard.`);
1293 | }
1294 | }
1295 |
1296 | abstract class PaymentFactory {
1297 | public abstract createService(): PaymentService;
1298 | }
1299 |
1300 | class PaypalFactory extends PaymentFactory {
1301 | public override createService(): PaymentService {
1302 | return new Paypal();
1303 | }
1304 | }
1305 |
1306 | class BitcoinFactory extends PaymentFactory {
1307 | public override createService(): PaymentService {
1308 | return new Bitcoin();
1309 | }
1310 | }
1311 |
1312 | class VisaCardFactory extends PaymentFactory {
1313 | public override createService(): PaymentService {
1314 | return new VisaCard();
1315 | }
1316 | }
1317 |
1318 | // Usage
1319 |
1320 | function getPaymentFactory(paymentType: PaymentType): PaymentFactory {
1321 | switch (paymentType) {
1322 | case PaymentType.Paypal:
1323 | return new PaypalFactory();
1324 | case PaymentType.Bitcoin:
1325 | return new BitcoinFactory();
1326 | case PaymentType.VisaCard:
1327 | return new VisaCardFactory();
1328 | default:
1329 | throw new Error("Invalid payment type.");
1330 | }
1331 | }
1332 |
1333 | const paypalService = getPaymentFactory(PaymentType.Paypal).createService();
1334 |
1335 | paypalService.payMoney(100); // You paid 100 dollars by Paypal.
1336 |
1337 | const bitcoinService = getPaymentFactory(PaymentType.Bitcoin).createService();
1338 |
1339 | bitcoinService.payMoney(200); // You paid 200 dollars by Bitcoin.
1340 |
1341 | const visaCardService = getPaymentFactory(PaymentType.VisaCard).createService();
1342 |
1343 | visaCardService.payMoney(300); // You paid 300 dollars by VisaCard.
1344 |
1345 | ```
1346 |
1347 | [`⬆ BACK TO TOP ⬆`](#table-of-contents)
1348 |
1349 | ### Prototype
1350 |
1351 | > Prototype is a creational design pattern that lets you copy existing objects without making your code dependent on their classes.
1352 |
1353 |
1354 |
1355 | ```typescript
1356 | interface IPrototype {
1357 | clone: () => IPrototype;
1358 | }
1359 |
1360 | class Product implements IPrototype {
1361 | private name: string;
1362 | private price: number;
1363 | private warranty: Date | null;
1364 |
1365 | constructor(name: string, price: number, warranty: Date | null) {
1366 | this.name = name;
1367 | this.price = price;
1368 | this.warranty = warranty;
1369 | }
1370 |
1371 | // Assume we implement methods, getters and setters all here
1372 |
1373 | public clone() {
1374 | return new Product(this.name, this.price, this.warranty);
1375 | }
1376 | }
1377 |
1378 | const productOne = new Product("Laptop", 2500000, new Date(2050));
1379 |
1380 | const productTwo = productOne.clone();
1381 |
1382 | // productOne !== productTwo but their properties are the same
1383 |
1384 | ```
1385 |
1386 | [`⬆ BACK TO TOP ⬆`](#table-of-contents)
1387 |
1388 | ### Singleton
1389 |
1390 | > Singleton is a creational design pattern that lets you ensure that a class has only one instance, while providing a global access point to this instance.
1391 |
1392 |
1393 |
1394 | ```typescript
1395 | class Weather {
1396 | private static instance: Weather | null = null;
1397 |
1398 | private statusOfCities: Array<{
1399 | city: string;
1400 | status: "SUNNY" | "CLOUDY" | "RAINY" | "SNOWY";
1401 | }>;
1402 |
1403 | private constructor() {
1404 | const data = []; // Get data from API
1405 |
1406 | this.statusOfCities = data;
1407 | }
1408 |
1409 | public getTemperatureByCity(city: string) {
1410 | return this.statusOfCities.find((data) => data.city === city);
1411 | }
1412 |
1413 | public static getInstance() {
1414 | if (this.instance == null) {
1415 | this.instance = new Weather();
1416 | }
1417 |
1418 | return this.instance;
1419 | }
1420 | }
1421 |
1422 | const instanceOne = Weather.getInstance();
1423 | const instanceTwo = Weather.getInstance();
1424 | // instanceOne is equal to instanceTwo (instanceOne === instanceTwo)
1425 |
1426 | ```
1427 |
1428 | [`⬆ BACK TO TOP ⬆`](#table-of-contents)
1429 |
1430 | ### Adapter (Wrapper)
1431 |
1432 | > Adapter is a structural design pattern that allows objects with incompatible interfaces to collaborate.
1433 |
1434 |
1435 |
1436 | ```typescript
1437 | interface StandardUser {
1438 | fullName: string;
1439 | skills: Array;
1440 | age: number;
1441 | contact: {
1442 | email: string;
1443 | phone: string;
1444 | };
1445 | }
1446 |
1447 | abstract class ResumeServiceApi {
1448 | static generateResume(data: StandardUser) {
1449 | /* Implementation */
1450 | }
1451 | }
1452 |
1453 | class User {
1454 | readonly firstName: string;
1455 | readonly lastName: string;
1456 | readonly birthday: Date;
1457 | readonly skills: Record;
1458 | readonly email?: string;
1459 | readonly phone?: string;
1460 |
1461 | constructor({
1462 | firstName,
1463 | lastName,
1464 | birthday,
1465 | skills,
1466 | email,
1467 | phone,
1468 | }: {
1469 | firstName: string;
1470 | lastName: string;
1471 | birthday: Date;
1472 | skills: Record;
1473 | email?: string;
1474 | phone?: string;
1475 | }) {
1476 | this.firstName = firstName;
1477 | this.lastName = lastName;
1478 | this.birthday = birthday;
1479 | this.skills = skills;
1480 | this.email = email;
1481 | this.phone = phone;
1482 | }
1483 | }
1484 |
1485 | class UserAdapter implements StandardUser {
1486 | private user: User;
1487 |
1488 | constructor(user: User) {
1489 | this.user = user;
1490 | }
1491 |
1492 | get fullName() {
1493 | return `${this.user.firstName} ${this.user.lastName}`;
1494 | }
1495 |
1496 | get skills() {
1497 | return Object.keys(this.user.skills);
1498 | }
1499 |
1500 | get age() {
1501 | return new Date().getFullYear() - this.user.birthday.getFullYear();
1502 | }
1503 |
1504 | get contact() {
1505 | return { email: this.user.email ?? "", phone: this.user.phone ?? "" };
1506 | }
1507 | }
1508 |
1509 | // Usage
1510 |
1511 | const user = new User({
1512 | firstName: "Ahmad",
1513 | lastName: "Jafari",
1514 | birthday: new Date(1999, 1, 1, 0, 0, 0, 0),
1515 | skills: { TypeScript: 4, JavaScript: 3, OOP: 4, CSharp: 2, Java: 1 },
1516 | email: "a99jafari@gmail.com",
1517 | phone: "+98 930 848 XXXX",
1518 | });
1519 |
1520 | // const resume = ResumeServiceApi.generateResume(user); |-> Type Error!
1521 |
1522 | const standardUser = new UserAdapter(user);
1523 | const resume = ResumeServiceApi.generateResume(standardUser); // OK!
1524 |
1525 | ```
1526 |
1527 | [`⬆ BACK TO TOP ⬆`](#table-of-contents)
1528 |
1529 | ### Bridge
1530 |
1531 | > Bridge is a structural design pattern that lets you split a large class or a set of closely related classes into two separate hierarchies—abstraction and implementation—which can be developed independently of each other.
1532 |
1533 |
1534 |
1535 | ```typescript
1536 | interface Player {
1537 | play(): string;
1538 | stop(): string;
1539 | }
1540 |
1541 | class AudioPlayer implements Player {
1542 | play(): string {
1543 | return "Audio is playing";
1544 | }
1545 |
1546 | stop(): string {
1547 | return "Audio is stopped";
1548 | }
1549 | }
1550 |
1551 | class VideoPlayer implements Player {
1552 | play(): string {
1553 | return "Video is playing";
1554 | }
1555 |
1556 | stop(): string {
1557 | return "Video is stopped";
1558 | }
1559 | }
1560 |
1561 | interface Platform {
1562 | play(): string;
1563 | stop(): string;
1564 | }
1565 |
1566 | class Desktop implements Platform {
1567 | private player: Player;
1568 |
1569 | constructor(player: Player) {
1570 | this.player = player;
1571 | }
1572 |
1573 | play(): string {
1574 | return `${this.player.play()} on desktop`;
1575 | }
1576 |
1577 | stop(): string {
1578 | return `${this.player.stop()} on desktop`;
1579 | }
1580 | }
1581 |
1582 | class Mobile implements Platform {
1583 | private player: Player;
1584 |
1585 | constructor(player: Player) {
1586 | this.player = player;
1587 | }
1588 |
1589 | play(): string {
1590 | return `${this.player.play()} on mobile`;
1591 | }
1592 |
1593 | stop(): string {
1594 | return `${this.player.stop()} on mobile`;
1595 | }
1596 | }
1597 |
1598 | // Usage
1599 | const audioPlayer = new AudioPlayer();
1600 | const videoPlayer = new VideoPlayer();
1601 |
1602 | const desktopVideoPlayer = new Desktop(videoPlayer);
1603 | const desktopAudioPlayer = new Desktop(audioPlayer);
1604 | const mobileVideoPlayer = new Mobile(videoPlayer);
1605 | const mobileAudioPlayer = new Mobile(audioPlayer);
1606 |
1607 | ```
1608 |
1609 | [`⬆ BACK TO TOP ⬆`](#table-of-contents)
1610 |
1611 | ### Composite (Object Tree)
1612 |
1613 | > Composite is a structural design pattern that lets you compose objects into tree structures and then work with these structures as if they were individual objects.
1614 |
1615 |
1616 |
1617 | ```typescript
1618 | abstract class BaseUnit {
1619 | constructor(
1620 | private readonly id: string,
1621 | private readonly units: Array> = [],
1622 | ) {}
1623 |
1624 | getUnit(unitId: string): BaseUnit | null {
1625 | return this.units.find((unit) => unit.id === unitId) ?? null;
1626 | }
1627 |
1628 | getSalary(): number {
1629 | return this.units.reduce((acc, unit) => acc + unit.getSalary(), 0);
1630 | }
1631 |
1632 | increaseSalary(percentage: number): void {
1633 | this.units.forEach((unit) => unit.increaseSalary(percentage));
1634 | }
1635 | }
1636 |
1637 | class Employee extends BaseUnit {
1638 | private salary: number;
1639 |
1640 | constructor(id: string, salary: number) {
1641 | super(id);
1642 | this.salary = salary;
1643 | }
1644 |
1645 | getUnit(): never {
1646 | throw new Error("Employee cannot have sub-units");
1647 | }
1648 |
1649 | getSalary() {
1650 | return this.salary;
1651 | }
1652 |
1653 | increaseSalary(percentage: number) {
1654 | this.salary = this.salary + (this.salary * percentage) / 100;
1655 | }
1656 | }
1657 |
1658 | class Department extends BaseUnit {}
1659 |
1660 | class Faculty extends BaseUnit {}
1661 |
1662 | class University extends BaseUnit {}
1663 |
1664 | // Usage
1665 |
1666 | const harvardUniversity = new University("Harvard", [
1667 | new Faculty("Engineering", [
1668 | new Department("Computer", [new Employee("C1", 6200), new Employee("C2", 5400), new Employee("C3", 5600)]),
1669 | new Department("Electrical", [new Employee("E1", 4800), new Employee("E2", 5800)]),
1670 | ]),
1671 | new Faculty("Science", [
1672 | new Department("Physics", [new Employee("P1", 3800), new Employee("P2", 4600)]),
1673 | new Department("Mathematics", [new Employee("M1", 5200), new Employee("M2", 5600), new Employee("M3", 4600)]),
1674 | ]),
1675 | ]);
1676 |
1677 | console.log(harvardUniversity.getSalary());
1678 | harvardUniversity.increaseSalary(10);
1679 | console.log(harvardUniversity.getSalary());
1680 |
1681 | const engineeringFaculty = harvardUniversity.getUnit("Engineering") as Faculty;
1682 |
1683 | console.log(engineeringFaculty.getSalary());
1684 | engineeringFaculty.increaseSalary(10);
1685 | console.log(engineeringFaculty.getSalary());
1686 |
1687 | const computerDepartment = engineeringFaculty.getUnit("Computer") as Department;
1688 |
1689 | console.log(computerDepartment.getSalary());
1690 | computerDepartment.increaseSalary(10);
1691 | console.log(computerDepartment.getSalary());
1692 |
1693 | const employee = computerDepartment.getUnit("C1") as Employee;
1694 |
1695 | console.log(employee.getSalary());
1696 | employee.increaseSalary(10);
1697 | console.log(employee.getSalary());
1698 |
1699 | ```
1700 |
1701 | [`⬆ BACK TO TOP ⬆`](#table-of-contents)
1702 |
1703 | ### Decorator (Wrapper)
1704 |
1705 | > Adapter is a structural design pattern that allows objects with incompatible interfaces to collaborate.
1706 |
1707 |
1708 |
1709 | ```typescript
1710 | interface ImageProcessor {
1711 | processImage: () => File;
1712 | }
1713 |
1714 | class ImageFile implements ImageProcessor {
1715 | private image: File;
1716 |
1717 | constructor(imageBlobs: Array, imageName: string) {
1718 | this.image = new File(imageBlobs, imageName);
1719 | }
1720 |
1721 | processImage() {
1722 | // Converts the blobs to a visible image
1723 | return this.image;
1724 | }
1725 | }
1726 |
1727 | abstract class ImageDecorator implements ImageProcessor {
1728 | protected image: File;
1729 |
1730 | constructor(image: File) {
1731 | this.image = image;
1732 | }
1733 |
1734 | abstract processImage(): File;
1735 | }
1736 |
1737 | class ImageCompressor extends ImageDecorator {
1738 | processImage(): File {
1739 | // Compresses image size
1740 | return this.image;
1741 | }
1742 | }
1743 |
1744 | class ImageEnhancer extends ImageDecorator {
1745 | processImage(): File {
1746 | // Enhances image quality
1747 | return this.image;
1748 | }
1749 | }
1750 |
1751 | class ImageResizer extends ImageDecorator {
1752 | processImage() {
1753 | // Changes image width and height
1754 | return this.image;
1755 | }
1756 | }
1757 |
1758 | // Usage
1759 |
1760 | const image = new ImageFile([], "Picture.jpg").processImage();
1761 | const compressedImage = new ImageCompressor(image).processImage();
1762 | const enhancedImage = new ImageCompressor(compressedImage).processImage();
1763 | const resizedImage = new ImageResizer(enhancedImage).processImage();
1764 |
1765 | ```
1766 |
1767 | [`⬆ BACK TO TOP ⬆`](#table-of-contents)
1768 |
1769 | ### Facade
1770 |
1771 | > Facade is a structural design pattern that provides a simplified interface to a library, a framework, or any other complex set of classes.
1772 |
1773 |
1774 |
1775 | ```typescript
1776 | class GitChecker {
1777 | private repositoryPath: string;
1778 |
1779 | constructor(repositoryPath: string) {
1780 | this.repositoryPath = repositoryPath;
1781 | }
1782 |
1783 | analyzeCommits() {
1784 | // Checks the quality of commit messages
1785 | }
1786 |
1787 | analyzeUnmergedBranches() {
1788 | // Checks the
1789 | }
1790 | }
1791 |
1792 | class Linter {
1793 | private rules: Array;
1794 |
1795 | constructor(rules: Array) {
1796 | this.rules = rules;
1797 | }
1798 |
1799 | findIssues() {
1800 | // Checks codebase and finds all issues
1801 | }
1802 |
1803 | resolveFixableIssues() {
1804 | // Checks codebase and fix all fixable issues
1805 | }
1806 | }
1807 |
1808 | class PackageManager {
1809 | private dependencies: Array<{ name: string; version: number }>;
1810 |
1811 | constructor(dependencies: Array<{ name: string; version: number }>) {
1812 | this.dependencies = dependencies;
1813 | }
1814 |
1815 | findUnsecureLibraries() {
1816 | // Analyzes all dependencies and finds all of unsecure libraries
1817 | }
1818 |
1819 | findDeprecatedLibraries() {
1820 | // Analyzes all dependencies and finds all of deprecated libraries
1821 | }
1822 | }
1823 |
1824 | // Facade Class
1825 | class CodebaseAnalyzer {
1826 | private gitChecker: GitChecker;
1827 | private linter: Linter;
1828 | private packageManager: PackageManager;
1829 |
1830 | constructor({
1831 | repositoryPath,
1832 | linterRules,
1833 | dependencies,
1834 | }: {
1835 | repositoryPath: string;
1836 | linterRules: Array;
1837 | dependencies: Array<{ name: string; version: number }>;
1838 | }) {
1839 | this.gitChecker = new GitChecker(repositoryPath);
1840 | this.linter = new Linter(linterRules);
1841 | this.packageManager = new PackageManager(dependencies);
1842 | }
1843 |
1844 | // This method is the facade method and does all of the work
1845 | analyze() {
1846 | this.gitChecker.analyzeCommits();
1847 | this.gitChecker.analyzeUnmergedBranches();
1848 | this.linter.findIssues();
1849 | this.linter.resolveFixableIssues();
1850 | this.packageManager.findUnsecureLibraries();
1851 | this.packageManager.findDeprecatedLibraries();
1852 | }
1853 | }
1854 |
1855 | // Usage
1856 | const codebaseAnalyzer = new CodebaseAnalyzer({
1857 | repositoryPath: "root/design-patterns/structural/facade/",
1858 | linterRules: ["rule1", "rule2", "rule3", "rule4"],
1859 | dependencies: [
1860 | { name: "ABC", version: 19 },
1861 | { name: "MNP", version: 14 },
1862 | { name: "XYZ", version: 23 },
1863 | ],
1864 | });
1865 |
1866 | codebaseAnalyzer.analyze();
1867 |
1868 | ```
1869 |
1870 | [`⬆ BACK TO TOP ⬆`](#table-of-contents)
1871 |
1872 | ### Flyweight (Cache)
1873 |
1874 | > Flyweight is a structural design pattern that lets you fit more objects into the available amount of RAM by sharing common parts of state between multiple objects instead of keeping all of the data in each object.
1875 |
1876 |
1877 |
1878 | ```typescript
1879 | interface IRequest {
1880 | readonly method: "GET" | "POST" | "PUT" | "DELETE";
1881 | readonly url: string;
1882 | readonly body: Record;
1883 | send(): Promise;
1884 | }
1885 |
1886 | class MinimalRequest implements IRequest {
1887 | constructor(
1888 | public readonly method: "GET" | "POST" | "PUT" | "DELETE",
1889 | public readonly url: string,
1890 | public readonly body: Record = {},
1891 | ) {}
1892 |
1893 | public async send(): Promise {
1894 | const options = { method: this.method, body: JSON.stringify(this.body) };
1895 |
1896 | const response = await fetch(this.url, options);
1897 |
1898 | return response.json();
1899 | }
1900 | }
1901 |
1902 | class RequestFactory {
1903 | private requests: Map = new Map();
1904 |
1905 | public createRequest(
1906 | method: "GET" | "POST" | "PUT" | "DELETE",
1907 | url: string,
1908 | body: Record = {},
1909 | ): IRequest {
1910 | const key = `${method}-${url}`;
1911 |
1912 | if (!this.requests.has(key)) {
1913 | const request = new MinimalRequest(method, url, body);
1914 |
1915 | this.requests.set(key, request);
1916 | }
1917 |
1918 | return this.requests.get(key)!; // Type assertion for clarity
1919 | }
1920 | }
1921 |
1922 | class ParallelRequestsHandler {
1923 | private factory: RequestFactory;
1924 |
1925 | constructor(factory: RequestFactory) {
1926 | this.factory = factory;
1927 | }
1928 |
1929 | public async sendAll(
1930 | requestsInfo: Array<{
1931 | method: "GET" | "POST" | "PUT" | "DELETE";
1932 | url: string;
1933 | body?: Record;
1934 | }>,
1935 | ): Promise> {
1936 | const requests = requestsInfo.map((requestInfo) =>
1937 | this.factory.createRequest(requestInfo.method, requestInfo.url, requestInfo.body),
1938 | );
1939 | const responses = await Promise.all(requests.map((request) => request.send()));
1940 |
1941 | return responses;
1942 | }
1943 | }
1944 |
1945 | ```
1946 |
1947 | [`⬆ BACK TO TOP ⬆`](#table-of-contents)
1948 |
1949 | ### Proxy
1950 |
1951 | > Proxy is a structural design pattern that lets you provide a substitute or placeholder for another object. A proxy controls access to the original object, allowing you to perform something either before or after the request gets through to the original object.
1952 |
1953 |
1954 |
1955 | ```typescript
1956 | interface IRequestHandler {
1957 | sendRequest(method: string, url: string, body?: string): void;
1958 | }
1959 |
1960 | class RequestHandler implements IRequestHandler {
1961 | sendRequest(method: string, url: string, body?: string): void {
1962 | console.log(`Request sent: ${method} ${url} ${body}`);
1963 | }
1964 | }
1965 |
1966 | class RequestHandlerProxy implements IRequestHandler {
1967 | private realApi: RequestHandler;
1968 |
1969 | constructor(realApi: RequestHandler) {
1970 | this.realApi = realApi;
1971 | }
1972 |
1973 | private logRequest(method: string, url: string, body?: string): void {
1974 | console.log(`Request logged: ${method} ${url} ${body}`);
1975 | }
1976 |
1977 | private validateRequestUrl(url: string): boolean {
1978 | return url.startsWith("/api");
1979 | }
1980 |
1981 | sendRequest(method: string, url: string, body?: string): void {
1982 | if (this.validateRequestUrl(url)) {
1983 | this.realApi.sendRequest(method, url, body);
1984 | this.logRequest(method, url, body);
1985 | }
1986 | }
1987 | }
1988 |
1989 | // Usage
1990 |
1991 | const realRequestHandler = new RequestHandler();
1992 | const proxyRequestHandler = new RequestHandlerProxy(realRequestHandler);
1993 |
1994 | proxyRequestHandler.sendRequest("GET", "/api/users");
1995 |
1996 | ```
1997 |
1998 | [`⬆ BACK TO TOP ⬆`](#table-of-contents)
1999 |
2000 | ### Chain of Responsibility
2001 |
2002 | > Chain of Responsibility is a behavioral design pattern that lets you pass requests along a chain of handlers. Upon receiving a request, each handler decides either to process the request or to pass it to the next handler in the chain.
2003 |
2004 |
2005 |
2006 | ```typescript
2007 | interface IResponse {
2008 | statusCode: number;
2009 | body: Record;
2010 | authentication: Record;
2011 | message?: string;
2012 | }
2013 |
2014 | class ResponseHandler {
2015 | private nextHandler?: ResponseHandler;
2016 |
2017 | protected process(response: IResponse): IResponse {
2018 | return response;
2019 | }
2020 |
2021 | public setNext(ResponseHandler: ResponseHandler): ResponseHandler {
2022 | this.nextHandler = ResponseHandler;
2023 |
2024 | return ResponseHandler;
2025 | }
2026 |
2027 | public handle(response: IResponse): IResponse {
2028 | const processedResponse = this.process(response);
2029 |
2030 | if (this.nextHandler == null) {
2031 | return processedResponse;
2032 | } else {
2033 | return this.nextHandler.handle(processedResponse);
2034 | }
2035 | }
2036 | }
2037 |
2038 | class Encryptor extends ResponseHandler {
2039 | private encryptTokens(response: IResponse) {
2040 | const { authentication } = response;
2041 | const encryptedAuthTokens: Record = {};
2042 |
2043 | for (const key in authentication) {
2044 | encryptedAuthTokens[key] = `encrypted-${authentication[key]}`;
2045 | }
2046 |
2047 | return { ...response, authentication: encryptedAuthTokens };
2048 | }
2049 |
2050 | protected process(response: IResponse) {
2051 | const encryptedResponse = this.encryptTokens(response);
2052 |
2053 | return encryptedResponse;
2054 | }
2055 | }
2056 |
2057 | class BodyFormatter extends ResponseHandler {
2058 | private transformKeysToCamelCase(body: Record) {
2059 | const newBody: Record = {};
2060 |
2061 | for (const key in body) {
2062 | const camelCaseKey = key.replace(/_([a-z])/g, (subString) => subString[1].toUpperCase());
2063 |
2064 | newBody[camelCaseKey] = body[key];
2065 | }
2066 |
2067 | return newBody;
2068 | }
2069 |
2070 | protected process(response: IResponse) {
2071 | const clonedResponseBody = JSON.parse(JSON.stringify(response.body));
2072 | const formattedBody = this.transformKeysToCamelCase(clonedResponseBody);
2073 | const formattedResponse = { ...response, body: formattedBody };
2074 |
2075 | return formattedResponse;
2076 | }
2077 | }
2078 |
2079 | class MetadataAdder extends ResponseHandler {
2080 | private getResponseMetadata(statusCode: number) {
2081 | if (statusCode < 200) {
2082 | return "Informational";
2083 | } else if (statusCode < 300) {
2084 | return "Success";
2085 | } else if (statusCode < 400) {
2086 | return "Redirection";
2087 | } else if (statusCode < 500) {
2088 | return "Client Error";
2089 | } else {
2090 | return "Server Error";
2091 | }
2092 | }
2093 |
2094 | protected process(response: IResponse) {
2095 | const updatedResponse = {
2096 | ...response,
2097 | message: this.getResponseMetadata(response.statusCode),
2098 | };
2099 |
2100 | return updatedResponse;
2101 | }
2102 | }
2103 |
2104 | // Usage
2105 | const response: IResponse = {
2106 | statusCode: 200,
2107 | body: {
2108 | design_pattern_name: "Chain of Responsibility",
2109 | pattern_category: "Behavioral",
2110 | complexity_percentage: 80,
2111 | },
2112 | authentication: {
2113 | api_token: "12345678",
2114 | refresh_token: "ABCDEFGH",
2115 | },
2116 | };
2117 |
2118 | const responseHandler = new ResponseHandler();
2119 | const encryptor = new Encryptor();
2120 | const bodyFormatter = new BodyFormatter();
2121 | const metadataAdder = new MetadataAdder();
2122 |
2123 | responseHandler.setNext(encryptor).setNext(bodyFormatter).setNext(metadataAdder);
2124 |
2125 | const resultResponse = responseHandler.handle(response);
2126 |
2127 | console.log(resultResponse);
2128 | /*
2129 | {
2130 | "statusCode": 200,
2131 | "body": {
2132 | "designPatternName": "Chain of Responsibility",
2133 | "patternCategory": "Behavioral",
2134 | "complexityPercentage": 80
2135 | },
2136 | "authentication": {
2137 | "api_token": "encrypted-12345678",
2138 | "refresh_token": "encrypted-ABCDEFGH"
2139 | },
2140 | "message": "Success"
2141 | }
2142 | */
2143 |
2144 | ```
2145 |
2146 | [`⬆ BACK TO TOP ⬆`](#table-of-contents)
2147 |
2148 | ### Command
2149 |
2150 | > Command is a behavioral design pattern that turns a request into a stand-alone object that contains all information about the request. This transformation lets you pass requests as a method arguments, delay or queue a request’s execution, and support undoable operations.
2151 |
2152 |
2153 |
2154 | ```typescript
2155 | interface Command {
2156 | execute(): void;
2157 | undo(): void;
2158 | }
2159 |
2160 | class AddTextCommand implements Command {
2161 | private prevText: string = "";
2162 |
2163 | constructor(
2164 | private editor: TextEditor,
2165 | private text: string,
2166 | ) {}
2167 |
2168 | execute() {
2169 | this.prevText = this.editor.content;
2170 | this.editor.content += this.text;
2171 | }
2172 |
2173 | undo() {
2174 | this.editor.content = this.prevText;
2175 | }
2176 | }
2177 |
2178 | class DeleteTextCommand implements Command {
2179 | private prevText: string = "";
2180 |
2181 | constructor(private editor: TextEditor) {}
2182 |
2183 | execute() {
2184 | this.prevText = this.editor.content;
2185 | this.editor.content = "";
2186 | }
2187 |
2188 | undo() {
2189 | this.editor.content = this.prevText;
2190 | }
2191 | }
2192 |
2193 | class TextEditor {
2194 | content: string = "";
2195 | }
2196 |
2197 | class CommandInvoker {
2198 | private commandHistory: Array = [];
2199 | private currentCommandIndex: number = -1;
2200 |
2201 | executeCommand(command: Command) {
2202 | if (this.currentCommandIndex < this.commandHistory.length - 1) {
2203 | this.commandHistory = this.commandHistory.slice(0, this.currentCommandIndex + 1);
2204 | }
2205 |
2206 | command.execute();
2207 | this.commandHistory.push(command);
2208 | this.currentCommandIndex++;
2209 | }
2210 |
2211 | undo() {
2212 | if (this.currentCommandIndex >= 0) {
2213 | const command = this.commandHistory[this.currentCommandIndex];
2214 |
2215 | command.undo();
2216 | this.currentCommandIndex--;
2217 | } else {
2218 | console.log("Nothing to undo.");
2219 | }
2220 | }
2221 |
2222 | redo() {
2223 | if (this.currentCommandIndex < this.commandHistory.length - 1) {
2224 | const command = this.commandHistory[this.currentCommandIndex + 1];
2225 |
2226 | command.execute();
2227 | this.currentCommandIndex++;
2228 | } else {
2229 | console.log("Nothing to redo.");
2230 | }
2231 | }
2232 | }
2233 |
2234 | // Client Code
2235 | const editor = new TextEditor();
2236 | const invoker = new CommandInvoker();
2237 |
2238 | const addTextCmd = new AddTextCommand(editor, "Hello, World!");
2239 |
2240 | invoker.executeCommand(addTextCmd);
2241 | console.log(editor.content); // "Hello, World!"
2242 |
2243 | const deleteTextCmd = new DeleteTextCommand(editor);
2244 |
2245 | invoker.executeCommand(deleteTextCmd);
2246 | console.log(editor.content); // ""
2247 |
2248 | invoker.undo();
2249 | console.log(editor.content); // "Hello, World!"
2250 |
2251 | invoker.redo();
2252 | console.log(editor.content); // ""
2253 |
2254 | ```
2255 |
2256 | [`⬆ BACK TO TOP ⬆`](#table-of-contents)
2257 |
2258 | ### Interpreter
2259 |
2260 | > Interpreter is a behavioral design pattern that provides a way to interpret and evaluate sentences or expressions in a language. This pattern defines a language grammar, along with an interpreter that can parse and execute the expressions.
2261 |
2262 |
2263 |
2264 | ```typescript
2265 | interface Expression {
2266 | interpret(): number;
2267 | }
2268 |
2269 | class NumberExpression implements Expression {
2270 | constructor(private value: number) {}
2271 |
2272 | interpret(): number {
2273 | return this.value;
2274 | }
2275 | }
2276 |
2277 | class PlusExpression implements Expression {
2278 | constructor(
2279 | private left: Expression,
2280 | private right: Expression,
2281 | ) {}
2282 |
2283 | interpret(): number {
2284 | return this.left.interpret() + this.right.interpret();
2285 | }
2286 | }
2287 |
2288 | class MinusExpression implements Expression {
2289 | constructor(
2290 | private left: Expression,
2291 | private right: Expression,
2292 | ) {}
2293 |
2294 | interpret(): number {
2295 | return this.left.interpret() - this.right.interpret();
2296 | }
2297 | }
2298 |
2299 | class MultiplyExpression implements Expression {
2300 | constructor(
2301 | private left: Expression,
2302 | private right: Expression,
2303 | ) {}
2304 |
2305 | interpret(): number {
2306 | return this.left.interpret() * this.right.interpret();
2307 | }
2308 | }
2309 |
2310 | class DivideExpression implements Expression {
2311 | constructor(
2312 | private left: Expression,
2313 | private right: Expression,
2314 | ) {}
2315 |
2316 | interpret(): number {
2317 | return this.left.interpret() / this.right.interpret();
2318 | }
2319 | }
2320 |
2321 | class Interpreter {
2322 | interpret(expression: string): number {
2323 | const stack: Array = [];
2324 |
2325 | const tokens = expression.split(" ");
2326 |
2327 | for (const token of tokens) {
2328 | if (this.isOperator(token)) {
2329 | const right = stack.pop()!;
2330 | const left = stack.pop()!;
2331 | const operator = this.createExpression(token, left, right);
2332 |
2333 | stack.push(operator);
2334 | } else {
2335 | stack.push(new NumberExpression(parseFloat(token)));
2336 | }
2337 | }
2338 |
2339 | return stack.pop()!.interpret();
2340 | }
2341 |
2342 | private isOperator(token: string): boolean {
2343 | return token === "+" || token === "-" || token === "*" || token === "/";
2344 | }
2345 |
2346 | private createExpression(operator: string, left: Expression, right: Expression): Expression {
2347 | switch (operator) {
2348 | case "+":
2349 | return new PlusExpression(left, right);
2350 | case "-":
2351 | return new MinusExpression(left, right);
2352 | case "*":
2353 | return new MultiplyExpression(left, right);
2354 | case "/":
2355 | return new DivideExpression(left, right);
2356 | default:
2357 | throw new Error(`Invalid operator: ${operator}`);
2358 | }
2359 | }
2360 | }
2361 |
2362 | // Usage
2363 | const interpreter = new Interpreter();
2364 |
2365 | console.log(interpreter.interpret("3 4 +")); // Output: 7
2366 | console.log(interpreter.interpret("5 2 * 3 +")); // Output: 13
2367 | console.log(interpreter.interpret("10 2 /")); // Output: 5
2368 |
2369 | ```
2370 |
2371 | [`⬆ BACK TO TOP ⬆`](#table-of-contents)
2372 |
2373 | ### Iterator
2374 |
2375 | > Iterator is a behavioral design pattern that lets you traverse elements of a collection without exposing its underlying representation (list, stack, tree, etc.).
2376 |
2377 |
2378 |
2379 | ```typescript
2380 | interface MyIterator {
2381 | hasPrevious: () => boolean;
2382 | hasNext: () => boolean;
2383 | previous: () => T;
2384 | next: () => T;
2385 | }
2386 |
2387 | class Book {
2388 | readonly title: string;
2389 | readonly author: string;
2390 | readonly isbn: string;
2391 |
2392 | constructor(title: string, author: string, isbn: string = "") {
2393 | this.title = title;
2394 | this.author = author;
2395 | this.isbn = isbn;
2396 | }
2397 | }
2398 |
2399 | class BookShelf {
2400 | private books: Array = [];
2401 |
2402 | getLength(): number {
2403 | return this.books.length;
2404 | }
2405 |
2406 | addBook(book: Book): void {
2407 | this.books.push(book);
2408 | }
2409 |
2410 | getBookAt(index: number): Book {
2411 | return this.books[index];
2412 | }
2413 |
2414 | createIterator() {
2415 | return new BookShelfIterator(this);
2416 | }
2417 | }
2418 |
2419 | class BookShelfIterator implements MyIterator {
2420 | private bookShelf: BookShelf;
2421 | private currentIndex: number;
2422 |
2423 | constructor(bookShelf: BookShelf) {
2424 | this.bookShelf = bookShelf;
2425 | this.currentIndex = 0;
2426 | }
2427 |
2428 | hasNext() {
2429 | return this.currentIndex < this.bookShelf.getLength();
2430 | }
2431 |
2432 | hasPrevious() {
2433 | return this.currentIndex > 0;
2434 | }
2435 |
2436 | next() {
2437 | this.currentIndex += 1;
2438 |
2439 | return this.bookShelf.getBookAt(this.currentIndex);
2440 | }
2441 |
2442 | previous() {
2443 | this.currentIndex -= 1;
2444 |
2445 | return this.bookShelf.getBookAt(this.currentIndex);
2446 | }
2447 | }
2448 |
2449 | // Usage
2450 | const shelf = new BookShelf();
2451 |
2452 | shelf.addBook(new Book("Design Patterns", "Gang of Four"));
2453 | shelf.addBook(new Book("Clean Code", "Robert C. Martin"));
2454 | shelf.addBook(new Book("You Don't Know JS", "Kyle Simpson"));
2455 |
2456 | const MyIterator = shelf.createIterator();
2457 |
2458 | while (MyIterator.hasNext()) {
2459 | const book = MyIterator.next();
2460 |
2461 | console.log(`${book.title} by ${book.author}`);
2462 | }
2463 |
2464 | ```
2465 |
2466 | [`⬆ BACK TO TOP ⬆`](#table-of-contents)
2467 |
2468 | ### Mediator
2469 |
2470 | > Mediator is a behavioral design pattern that lets you reduce chaotic dependencies between objects. The pattern restricts direct communications between the objects and forces them to collaborate only via a mediator object.
2471 |
2472 |
2473 |
2474 | ```typescript
2475 | interface ChatMediator {
2476 | sendMessage(receiver: User, message: string): void;
2477 | }
2478 |
2479 | class ConcreteChatMediator implements ChatMediator {
2480 | private users: Array = [];
2481 |
2482 | addUser(user: User): void {
2483 | this.users.push(user);
2484 | }
2485 |
2486 | sendMessage(receiver: User, message: string): void {
2487 | for (const user of this.users) {
2488 | // Don't send the message to the user who sent it
2489 | if (user !== receiver) {
2490 | user.receiveMessage(message);
2491 | }
2492 | }
2493 | }
2494 | }
2495 |
2496 | class User {
2497 | private mediator: ChatMediator;
2498 | private name: string;
2499 |
2500 | constructor(mediator: ChatMediator, name: string) {
2501 | this.mediator = mediator;
2502 | this.name = name;
2503 | }
2504 |
2505 | sendMessage(message: string): void {
2506 | console.log(`${this.name} sends: ${message}`);
2507 | this.mediator.sendMessage(this, message);
2508 | }
2509 |
2510 | receiveMessage(message: string): void {
2511 | console.log(`${this.name} receives: ${message}`);
2512 | }
2513 | }
2514 |
2515 | // Usage
2516 | const mediator = new ConcreteChatMediator();
2517 |
2518 | const user1 = new User(mediator, "Alice");
2519 | const user2 = new User(mediator, "Bob");
2520 | const user3 = new User(mediator, "Charlie");
2521 |
2522 | mediator.addUser(user1);
2523 | mediator.addUser(user2);
2524 | mediator.addUser(user3);
2525 |
2526 | user1.sendMessage("Hello, everyone!");
2527 |
2528 | user2.sendMessage("Hi, Alice!");
2529 |
2530 | user3.sendMessage("Hey, Bob!");
2531 |
2532 | ```
2533 |
2534 | [`⬆ BACK TO TOP ⬆`](#table-of-contents)
2535 |
2536 | ### Memento
2537 |
2538 | > Memento is a behavioral design pattern that lets you save and restore the previous state of an object without revealing the details of its implementation.
2539 |
2540 |
2541 |
2542 | ```typescript
2543 | class EditorMemento {
2544 | constructor(private readonly content: string) {}
2545 |
2546 | getContent(): string {
2547 | return this.content;
2548 | }
2549 | }
2550 |
2551 | class Editor {
2552 | constructor(private content: string = "") {}
2553 |
2554 | getContent(): string {
2555 | return this.content;
2556 | }
2557 |
2558 | setContent(content: string): void {
2559 | this.content = content;
2560 | }
2561 |
2562 | createSnapshot(): EditorMemento {
2563 | return new EditorMemento(this.content);
2564 | }
2565 |
2566 | restoreSnapshot(snapshot: EditorMemento): void {
2567 | this.content = snapshot.getContent();
2568 | }
2569 | }
2570 |
2571 | class MinimalHistory {
2572 | private snapshots: Array = [];
2573 |
2574 | push(snapshot: EditorMemento): void {
2575 | this.snapshots.push(snapshot);
2576 | }
2577 |
2578 | pop(): EditorMemento | undefined {
2579 | return this.snapshots.pop();
2580 | }
2581 | }
2582 |
2583 | // Usage
2584 | const editor = new Editor();
2585 | const minimalHistory = new MinimalHistory();
2586 |
2587 | editor.setContent("Hello, World!");
2588 | editor.setContent("Hello, TypeScript!");
2589 | minimalHistory.push(editor.createSnapshot());
2590 | editor.setContent("Hello, Memento Pattern!");
2591 |
2592 | const lastSnapshot = minimalHistory.pop();
2593 |
2594 | if (lastSnapshot) {
2595 | editor.restoreSnapshot(lastSnapshot);
2596 | }
2597 |
2598 | console.log(editor.getContent()); // Output: Hello, TypeScript!
2599 |
2600 | ```
2601 |
2602 | [`⬆ BACK TO TOP ⬆`](#table-of-contents)
2603 |
2604 | ### Observer
2605 |
2606 | > Observer is a behavioral design pattern that lets you define a subscription mechanism to notify multiple objects about any events that happen to the object they’re observing.
2607 |
2608 |
2609 |
2610 | ```typescript
2611 | interface Subject {
2612 | registerObserver(observer: Observer): void;
2613 | removeObserver(observer: Observer): void;
2614 | notifyObservers(): void;
2615 | }
2616 |
2617 | interface Observer {
2618 | update(notification: string): void;
2619 | }
2620 |
2621 | class Celebrity implements Subject {
2622 | private followers: Array;
2623 | private posts: Array;
2624 |
2625 | constructor() {
2626 | this.followers = [];
2627 | this.posts = [];
2628 | }
2629 |
2630 | // Method to make a new post
2631 | sendPost(newPost: string) {
2632 | this.posts = [...this.posts, newPost];
2633 | this.notifyFollowers();
2634 | }
2635 |
2636 | // Method to notify followers
2637 | private notifyFollowers() {
2638 | this.followers.forEach((follower) => {
2639 | const latestPost = this.posts[this.posts.length - 1];
2640 |
2641 | follower.update(latestPost);
2642 | });
2643 | }
2644 |
2645 | // Register a new follower
2646 | registerObserver(observer: Observer) {
2647 | this.followers.push(observer);
2648 | }
2649 |
2650 | // Remove a follower
2651 | removeObserver(observer: Observer) {
2652 | const index = this.followers.indexOf(observer);
2653 |
2654 | if (index !== -1) {
2655 | this.followers.splice(index, 1);
2656 | }
2657 | }
2658 |
2659 | // Notify all followers
2660 | notifyObservers() {
2661 | this.notifyFollowers();
2662 | }
2663 | }
2664 |
2665 | class Follower implements Observer {
2666 | private followerName: string;
2667 |
2668 | constructor(name: string) {
2669 | this.followerName = name;
2670 | }
2671 |
2672 | // Update method to receive notifications
2673 | update(notification: string) {
2674 | console.log(`${this.followerName} received a notification: ${notification}`);
2675 | }
2676 | }
2677 |
2678 | // Usage
2679 | const celebrity1 = new Celebrity();
2680 | const celebrity2 = new Celebrity();
2681 |
2682 | const follower1 = new Follower("John");
2683 | const follower2 = new Follower("Alice");
2684 | const follower3 = new Follower("Bob");
2685 |
2686 | celebrity1.registerObserver(follower1);
2687 | celebrity1.registerObserver(follower2);
2688 | celebrity2.registerObserver(follower3);
2689 |
2690 | celebrity1.sendPost("Hello World!");
2691 | celebrity2.sendPost("I love coding!");
2692 |
2693 | celebrity1.removeObserver(follower1);
2694 | celebrity1.removeObserver(follower2);
2695 |
2696 | celebrity1.sendPost("Observer pattern is awesome!");
2697 |
2698 | // Output:
2699 | // John received a notification: Hello World!
2700 | // Alice received a notification: Hello World!
2701 | // Bob received a notification: I love coding!
2702 |
2703 | ```
2704 |
2705 | [`⬆ BACK TO TOP ⬆`](#table-of-contents)
2706 |
2707 | ### State
2708 |
2709 | > State is a behavioral design pattern that lets an object alter its behavior when its internal state changes. It appears as if the object changed its class.
2710 |
2711 |
2712 |
2713 | ```typescript
2714 | interface PipelineState {
2715 | start(pipeline: Pipeline): void;
2716 | fail(pipeline: Pipeline): void;
2717 | complete(pipeline: Pipeline): void;
2718 | }
2719 |
2720 | class IdleState implements PipelineState {
2721 | start(pipeline: Pipeline) {
2722 | console.log("Pipeline started. Building...");
2723 | pipeline.setState(new BuildingState());
2724 | }
2725 |
2726 | fail(_pipeline: Pipeline) {
2727 | console.log("Pipeline is idle. Nothing to fail.");
2728 | }
2729 |
2730 | complete(_pipeline: Pipeline) {
2731 | console.log("Pipeline is idle. Nothing to complete.");
2732 | }
2733 | }
2734 |
2735 | class BuildingState implements PipelineState {
2736 | start(_pipeline: Pipeline) {
2737 | console.log("Pipeline is already building.");
2738 | }
2739 |
2740 | fail(pipeline: Pipeline) {
2741 | console.log("Build failed.");
2742 | pipeline.setState(new FailedState());
2743 | }
2744 |
2745 | complete(pipeline: Pipeline) {
2746 | console.log("Build complete. Testing...");
2747 | pipeline.setState(new TestingState());
2748 | }
2749 | }
2750 |
2751 | class TestingState implements PipelineState {
2752 | start(_pipeline: Pipeline) {
2753 | console.log("Pipeline is already in progress.");
2754 | }
2755 |
2756 | fail(pipeline: Pipeline) {
2757 | console.log("Testing failed.");
2758 | pipeline.setState(new FailedState());
2759 | }
2760 |
2761 | complete(pipeline: Pipeline) {
2762 | console.log("Testing complete. Deploying...");
2763 | pipeline.setState(new DeployingState());
2764 | }
2765 | }
2766 |
2767 | class DeployingState implements PipelineState {
2768 | start(_pipeline: Pipeline) {
2769 | console.log("Pipeline is already deploying.");
2770 | }
2771 |
2772 | fail(pipeline: Pipeline) {
2773 | console.log("Deployment failed.");
2774 | pipeline.setState(new FailedState());
2775 | }
2776 |
2777 | complete(pipeline: Pipeline) {
2778 | console.log("Deployment successful!");
2779 | pipeline.setState(new IdleState());
2780 | }
2781 | }
2782 |
2783 | class FailedState implements PipelineState {
2784 | start(_pipeline: Pipeline) {
2785 | console.log("Fix the issues and start the pipeline again.");
2786 | }
2787 |
2788 | fail(_pipeline: Pipeline) {
2789 | console.log("Pipeline already in failed state.");
2790 | }
2791 |
2792 | complete(_pipeline: Pipeline) {
2793 | console.log("Cannot complete. The pipeline has failed.");
2794 | }
2795 | }
2796 |
2797 | // 3. Context
2798 | class Pipeline {
2799 | private state: PipelineState;
2800 |
2801 | constructor() {
2802 | // Initial state
2803 | this.state = new IdleState();
2804 | }
2805 |
2806 | setState(state: PipelineState) {
2807 | this.state = state;
2808 | }
2809 |
2810 | start() {
2811 | this.state.start(this);
2812 | }
2813 |
2814 | fail() {
2815 | this.state.fail(this);
2816 | }
2817 |
2818 | complete() {
2819 | this.state.complete(this);
2820 | }
2821 | }
2822 |
2823 | // Client Code
2824 | const pipeline = new Pipeline();
2825 |
2826 | pipeline.start(); // Output: Pipeline started. Building...
2827 | pipeline.complete(); // Output: Build complete. Testing...
2828 | pipeline.fail(); // Output: Testing failed.
2829 |
2830 | pipeline.setState(new BuildingState());
2831 | pipeline.start(); // Output: Pipeline is already building.
2832 | pipeline.complete(); // Output: Testing complete. Deploying...
2833 | pipeline.complete(); // Output: Deployment successful!
2834 |
2835 | pipeline.setState(new TestingState());
2836 | pipeline.start(); // Output: Pipeline is already in progress.
2837 | pipeline.fail(); // Output: Testing failed.
2838 | pipeline.complete(); // Output: Deployment successful!
2839 |
2840 | pipeline.setState(new DeployingState());
2841 | pipeline.start(); // Output: Pipeline is already deploying.
2842 | pipeline.fail(); // Output: Deployment failed.
2843 | pipeline.complete(); // Output: Deployment successful!
2844 |
2845 | pipeline.setState(new FailedState());
2846 | pipeline.start(); // Output: Fix the issues and start the pipeline again.
2847 | pipeline.fail(); // Output: Pipeline already in failed state.
2848 | pipeline.complete(); // Output: Cannot complete. The pipeline has failed.
2849 |
2850 | ```
2851 |
2852 | [`⬆ BACK TO TOP ⬆`](#table-of-contents)
2853 |
2854 | ### Strategy
2855 |
2856 | > Strategy is a behavioral design pattern that lets you define a family of algorithms, put each of them into a separate class, and make their objects interchangeable.
2857 |
2858 |
2859 |
2860 | ```typescript
2861 | interface RenderStrategy {
2862 | renderShape(shape: Shape): void;
2863 | }
2864 |
2865 | class RasterRender implements RenderStrategy {
2866 | renderShape(shape: Shape) {
2867 | console.log(`Raster rendering the ${shape.getName()}`);
2868 | }
2869 | }
2870 |
2871 | class VectorRender implements RenderStrategy {
2872 | renderShape(shape: Shape) {
2873 | console.log(`Vector rendering the ${shape.getName()}`);
2874 | }
2875 | }
2876 |
2877 | class Shape {
2878 | private name: string;
2879 | private renderStrategy: RenderStrategy;
2880 |
2881 | constructor(name: string, strategy: RenderStrategy) {
2882 | this.name = name;
2883 | this.renderStrategy = strategy;
2884 | }
2885 |
2886 | setRenderStrategy(strategy: RenderStrategy) {
2887 | this.renderStrategy = strategy;
2888 | }
2889 |
2890 | render() {
2891 | this.renderStrategy.renderShape(this);
2892 | }
2893 |
2894 | getName(): string {
2895 | return this.name;
2896 | }
2897 | }
2898 |
2899 | // Usage
2900 | const rasterRender = new RasterRender();
2901 | const vectorRender = new VectorRender();
2902 |
2903 | const circle = new Shape("Circle", rasterRender);
2904 |
2905 | circle.render();
2906 |
2907 | circle.setRenderStrategy(vectorRender);
2908 | circle.render();
2909 |
2910 | ```
2911 |
2912 | [`⬆ BACK TO TOP ⬆`](#table-of-contents)
2913 |
2914 | ### Template Method
2915 |
2916 | > Template Method is a behavioral design pattern that defines the skeleton of an algorithm in the superclass but lets subclasses override specific steps of the algorithm without changing its structure.
2917 |
2918 |
2919 |
2920 | ```typescript
2921 | abstract class SocialMediaPostAnalyzer {
2922 | private readonly HARMFUL_WORDS = [
2923 | "dumb",
2924 | "stupid",
2925 | "idiot",
2926 | "loser",
2927 | "ugly",
2928 | "fat",
2929 | "skinny",
2930 | "weird",
2931 | "hate",
2932 | "rude",
2933 | "nasty",
2934 | ];
2935 |
2936 | preprocessData(data: string): Array {
2937 | return data.split(" ").map((word) => word.replace(/[^a-zA-Z ]/g, "").toLowerCase());
2938 | }
2939 |
2940 | analyze(data: Array): Array {
2941 | return data.filter((word) => this.HARMFUL_WORDS.includes(word));
2942 | }
2943 |
2944 | displayResults(data: Array): void {
2945 | console.log(`The number of harmful words in this post is ${data.length}, including ${data.join(", ")}.`);
2946 | }
2947 |
2948 | async analyzePosts(): Promise {
2949 | const data = await this.fetchData();
2950 | const preprocessedData = this.preprocessData(data);
2951 | const analyticsResult = this.analyze(preprocessedData);
2952 |
2953 | this.displayResults(analyticsResult);
2954 | }
2955 |
2956 | abstract fetchData(): Promise;
2957 | }
2958 |
2959 | class TwitterPostAnalyzer extends SocialMediaPostAnalyzer {
2960 | // Fetches data from Twitter API and returns its data
2961 | async fetchData() {
2962 | return ""; // Dummy data
2963 | }
2964 | }
2965 |
2966 | class InstagramPostAnalyzer extends SocialMediaPostAnalyzer {
2967 | // Fetches data from Instagram API and returns its data
2968 | async fetchData() {
2969 | return ""; // Dummy data
2970 | }
2971 | }
2972 |
2973 | // Usage
2974 | const twitterAnalysis = new TwitterPostAnalyzer();
2975 |
2976 | twitterAnalysis.analyzePosts();
2977 |
2978 | const instagramAnalysis = new InstagramPostAnalyzer();
2979 |
2980 | instagramAnalysis.analyzePosts();
2981 |
2982 | ```
2983 |
2984 | [`⬆ BACK TO TOP ⬆`](#table-of-contents)
2985 |
2986 | ### Visitor
2987 |
2988 | > Visitor is a behavioral design pattern that lets you separate algorithms from the objects on which they operate.
2989 |
2990 |
2991 |
2992 | ```typescript
2993 | interface Visitor {
2994 | visitDesigner(manager: Designer): void;
2995 | visitDeveloper(developer: Developer): void;
2996 | }
2997 |
2998 | interface Employee {
2999 | accept(visitor: Visitor): void;
3000 | }
3001 |
3002 | class Designer implements Employee {
3003 | name: string;
3004 | numberOfDesignedPages: number;
3005 |
3006 | constructor(name: string, numberOfDesignedPages: number) {
3007 | this.name = name;
3008 | this.numberOfDesignedPages = numberOfDesignedPages;
3009 | }
3010 |
3011 | accept(visitor: Visitor): void {
3012 | visitor.visitDesigner(this);
3013 | }
3014 | }
3015 |
3016 | class Developer implements Employee {
3017 | name: string;
3018 | baseSalary: number;
3019 | storyPoints: number;
3020 |
3021 | constructor(name: string, baseSalary: number, storyPoints: number) {
3022 | this.name = name;
3023 | this.baseSalary = baseSalary;
3024 | this.storyPoints = storyPoints;
3025 | }
3026 |
3027 | accept(visitor: Visitor): void {
3028 | visitor.visitDeveloper(this);
3029 | }
3030 | }
3031 |
3032 | class SalaryCalculator implements Visitor {
3033 | totalSalary: number = 0;
3034 |
3035 | visitDesigner(manager: Designer): void {
3036 | this.totalSalary += manager.numberOfDesignedPages * 200;
3037 | }
3038 |
3039 | visitDeveloper(developer: Developer): void {
3040 | this.totalSalary += developer.baseSalary + developer.storyPoints * 30;
3041 | }
3042 | }
3043 |
3044 | // Usage
3045 | const employees: Array = [
3046 | new Designer("Alice", 15),
3047 | new Designer("James", 20),
3048 | new Developer("Ahmad", 3000, 40),
3049 | new Developer("Kate", 2000, 60),
3050 | ];
3051 |
3052 | const salaryCalculator = new SalaryCalculator();
3053 |
3054 | for (const employee of employees) {
3055 | employee.accept(salaryCalculator);
3056 | }
3057 |
3058 | console.log("Total salary:", salaryCalculator.totalSalary);
3059 |
3060 | ```
3061 |
3062 | [`⬆ BACK TO TOP ⬆`](#table-of-contents)
3063 |
3064 | ## References
3065 |
3066 | In creating this repository, I aimed to provide original examples to facilitate learning about object-oriented programming (OOP) pillars, SOLID principles, and design patterns using TypeScript. While there may be similarities between examples found in this repository and those in other resources, it's essential to emphasize that all code and documentation within this repository are original creations.
3067 |
3068 | The following list includes resources that have inspired and informed my understanding of OOP, SOLID principles, and design patterns:
3069 |
3070 | 1. Head First Design Patterns (by Eric Freeman and Elisabeth Robson)
3071 | 2. Dive Into Design Patterns (by Alexander Shvets)
3072 | 3. [GeeksForGeeks Website](https://www.geeksforgeeks.org/)
3073 | 4. [WikiPedia Website](https://www.wikipedia.org/)
3074 | 5. +5 years of experience in the software development industry
3075 |
3076 | The resources for the images in the repository are:
3077 |
3078 | 1. [The S.O.L.I.D Principles in Pictures](https://medium.com/backticks-tildes/the-s-o-l-i-d-principles-in-pictures-b34ce2f1e898)
3079 | 2. [Refactoring.guru Website](https://refactoring.guru/)
3080 | 3. Google!
3081 |
3082 | > Please note that while the concepts discussed in these resources may overlap with the content of this repository, all examples and code within this repository have been independently developed by myself with the goal of providing real-world scenarios and applications.
3083 |
3084 | [`⬆ BACK TO TOP ⬆`](#table-of-contents)
3085 |
3086 | ## Contributing and Supporting
3087 |
3088 | Thank you for exploring **OOP Expert with TypeScript**. This repository serves as a comprehensive resource for mastering object-oriented programming principles, SOLID design, and design patterns through the lens of TypeScript.
3089 |
3090 | Your contributions can enhance the learning experience for countless individuals. Whether it's correcting a typo, suggesting improvements to code examples, or adding new content, your input is invaluable in ensuring the repository remains a top-notch educational tool.
3091 |
3092 | By collaborating with me, you not only enrich the learning journey for others but also sharpen your own skills. Every line of code, every explanation, and every suggestion can make a significant difference.
3093 |
3094 | If you've found this repository helpful, kindly consider giving it a ⭐. Your support encourages me to continue refining and expanding its content, benefitting the entire community of developers striving to master object-oriented programming with TypeScript.
3095 |
3096 | Let's work together to cultivate a vibrant learning environment where knowledge is shared, refined, and celebrated. Your contributions are deeply appreciated. Thank you for being a part of this journey.
3097 |
3098 | [`⬆ BACK TO TOP ⬆`](#table-of-contents)
--------------------------------------------------------------------------------
/biome.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
3 | "vcs": { "enabled": false, "clientKind": "git", "useIgnoreFile": false },
4 | "files": { "ignoreUnknown": false, "ignore": [] },
5 | "formatter": {
6 | "enabled": true,
7 | "useEditorconfig": true,
8 | "formatWithErrors": false,
9 | "indentStyle": "space",
10 | "indentWidth": 2,
11 | "lineEnding": "lf",
12 | "lineWidth": 120,
13 | "attributePosition": "auto",
14 | "bracketSpacing": true
15 | },
16 | "organizeImports": { "enabled": true },
17 | "linter": {
18 | "enabled": true,
19 | "rules": {
20 | "recommended": false,
21 | "complexity": {
22 | "noExtraBooleanCast": "error",
23 | "noMultipleSpacesInRegularExpressionLiterals": "error",
24 | "noUselessCatch": "error",
25 | "noUselessConstructor": "off",
26 | "noUselessRename": "error",
27 | "noUselessStringConcat": "error",
28 | "noUselessTypeConstraint": "error",
29 | "noWith": "error"
30 | },
31 | "correctness": {
32 | "noConstAssign": "error",
33 | "noConstantCondition": "error",
34 | "noEmptyCharacterClassInRegex": "error",
35 | "noEmptyPattern": "error",
36 | "noGlobalObjectCalls": "error",
37 | "noInvalidBuiltinInstantiation": "error",
38 | "noInvalidConstructorSuper": "error",
39 | "noNonoctalDecimalEscape": "error",
40 | "noPrecisionLoss": "error",
41 | "noSelfAssign": "error",
42 | "noSetterReturn": "error",
43 | "noSwitchDeclarations": "error",
44 | "noUndeclaredVariables": "error",
45 | "noUnreachable": "error",
46 | "noUnreachableSuper": "error",
47 | "noUnsafeFinally": "error",
48 | "noUnsafeOptionalChaining": "error",
49 | "noUnusedLabels": "error",
50 | "useArrayLiterals": "off",
51 | "useIsNan": "error",
52 | "useValidForDirection": "error",
53 | "useYield": "error"
54 | },
55 | "style": {
56 | "noNamespace": "error",
57 | "noNegationElse": "error",
58 | "noYodaExpression": "error",
59 | "useAsConstAssertion": "error",
60 | "useConst": "error",
61 | "useDefaultSwitchClause": "error",
62 | "useTemplate": "error"
63 | },
64 | "suspicious": {
65 | "noAsyncPromiseExecutor": "error",
66 | "noCatchAssign": "error",
67 | "noClassAssign": "error",
68 | "noCompareNegZero": "error",
69 | "noControlCharactersInRegex": "error",
70 | "noDebugger": "error",
71 | "noDoubleEquals": "error",
72 | "noDuplicateCase": "error",
73 | "noDuplicateClassMembers": "error",
74 | "noDuplicateObjectKeys": "error",
75 | "noDuplicateParameters": "error",
76 | "noEmptyBlockStatements": "error",
77 | "noExplicitAny": "off",
78 | "noExtraNonNullAssertion": "error",
79 | "noFallthroughSwitchClause": "error",
80 | "noFunctionAssign": "error",
81 | "noGlobalAssign": "error",
82 | "noImportAssign": "error",
83 | "noMisleadingCharacterClass": "error",
84 | "noMisleadingInstantiator": "error",
85 | "noPrototypeBuiltins": "error",
86 | "noRedeclare": "error",
87 | "noShadowRestrictedNames": "error",
88 | "noSparseArray": "error",
89 | "noUnsafeDeclarationMerging": "error",
90 | "noUnsafeNegation": "error",
91 | "useGetterReturn": "error",
92 | "useNamespaceKeyword": "error",
93 | "useValidTypeof": "error"
94 | }
95 | },
96 | "ignore": ["node_modules/**/*"]
97 | },
98 | "javascript": {
99 | "formatter": {
100 | "jsxQuoteStyle": "double",
101 | "quoteProperties": "asNeeded",
102 | "trailingCommas": "all",
103 | "semicolons": "always",
104 | "arrowParentheses": "always",
105 | "bracketSameLine": false,
106 | "quoteStyle": "double",
107 | "attributePosition": "auto",
108 | "bracketSpacing": true
109 | }
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/bun.lockb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jafari-dev/oop-expert-with-typescript/5c63e08aaa73f6bf798553b7d505e4b0eed13bfa/bun.lockb
--------------------------------------------------------------------------------
/html-generator.ts:
--------------------------------------------------------------------------------
1 | import { marked } from "marked";
2 | import { gfmHeadingId } from "marked-gfm-heading-id";
3 | import fs from "node:fs";
4 |
5 | marked.use(gfmHeadingId());
6 |
7 | async function generateHTML() {
8 | const htmlFileContent = fs.readFileSync("./website/template.html", "utf-8");
9 | const markdownFileContent = fs.readFileSync("./README.md", "utf-8");
10 | const convertedContent = await marked.parse(markdownFileContent, {
11 | gfm: true,
12 | });
13 | const newHTMLContent = htmlFileContent.replace("", convertedContent);
14 |
15 | fs.writeFileSync("./website/index.html", newHTMLContent);
16 | }
17 |
18 | generateHTML();
19 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "oop-expert-with-typescript",
3 | "version": "1.0.0",
4 | "description": "A complete guide for learning object oriented programming pillars, SOLID principles and design patterns with TypeScript!",
5 | "main": "script.ts",
6 | "type": "module",
7 | "scripts": {
8 | "readme": "bun ./script.ts && bun ./html-generator.ts",
9 | "lint": "bunx biome check"
10 | },
11 | "devDependencies": {
12 | "@biomejs/biome": "^1.9.4",
13 | "@types/bun": "latest"
14 | },
15 | "peerDependencies": {
16 | "typescript": "^5.0.0"
17 | },
18 | "dependencies": {
19 | "marked": "^14.1.3",
20 | "marked-gfm-heading-id": "^4.1.0"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/script.ts:
--------------------------------------------------------------------------------
1 | import fs from "node:fs";
2 |
3 | const JOINER = "\n\n[`⬆ BACK TO TOP ⬆`](#table-of-contents)";
4 | const CODE_ADDRESS = "[EXAMPLE-FILE-ADDRESS]";
5 | const DOCS_DIRECTORY = "./Documentations";
6 |
7 | function wrapInTypeScriptBlock(content: string) {
8 | const PREFIX = "```typescript";
9 | const SUFFIX = "```";
10 |
11 | return `${PREFIX}\n${content}\n${SUFFIX}`;
12 | }
13 |
14 | const contents = fs.readdirSync(DOCS_DIRECTORY).map((fileName) => {
15 | const fileContent = fs.readFileSync(`${DOCS_DIRECTORY}/${fileName}`, "utf-8");
16 |
17 | const lines = fileContent.split("\n");
18 |
19 | const newLines = lines.map((line) => {
20 | if (line.startsWith(CODE_ADDRESS)) {
21 | const codeFileAddress = line.replace(CODE_ADDRESS, "").slice(1, -1);
22 | const code = fs.readFileSync(`.${codeFileAddress}`, "utf-8");
23 |
24 | return wrapInTypeScriptBlock(code);
25 | } else {
26 | return line;
27 | }
28 | });
29 |
30 | const joinedLines = newLines.join("\n").trim();
31 |
32 | return `${joinedLines}${JOINER}`;
33 | });
34 |
35 | const fullDocumentation = contents.join(`\n\n`);
36 |
37 | fs.writeFileSync("./README.md", fullDocumentation);
38 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | // Enable latest features
4 | "lib": ["ESNext", "DOM"],
5 | "target": "ESNext",
6 | "module": "ESNext",
7 | "moduleDetection": "force",
8 | "allowJs": true,
9 |
10 | // Bundler mode
11 | "moduleResolution": "bundler",
12 | "allowImportingTsExtensions": true,
13 | "verbatimModuleSyntax": true,
14 | "noEmit": true,
15 |
16 | // Best practices
17 | "strict": true,
18 | "skipLibCheck": true,
19 | "noFallthroughCasesInSwitch": true,
20 |
21 | // Some stricter flags (disabled by default)
22 | "noUnusedLocals": false,
23 | "noUnusedParameters": false,
24 | "noPropertyAccessFromIndexSignature": false
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/website/DankMono-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jafari-dev/oop-expert-with-typescript/5c63e08aaa73f6bf798553b7d505e4b0eed13bfa/website/DankMono-Regular.ttf
--------------------------------------------------------------------------------
/website/styles.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: "DankMono";
3 | src: url("./DankMono-Regular.ttf") format("truetype");
4 | }
5 | * {
6 | margin: 0;
7 | padding: 0;
8 | box-sizing: border-box;
9 | font-family: "Roboto", sans-serif;
10 | }
11 |
12 | body {
13 | background-color: #ffffff;
14 | scroll-behavior: smooth;
15 | width: 100%;
16 | }
17 |
18 | .container {
19 | max-width: 1200px;
20 | padding-inline: 12px;
21 | margin-inline: auto;
22 | }
23 |
24 | html {
25 | --text-primary: #000000;
26 | --link-primary: #8754ff;
27 | --spec-primary: #ff6b6b;
28 | }
29 |
30 | header {
31 | text-align: right;
32 | padding-block: 16px;
33 | font-size: 20px;
34 | font-weight: 500;
35 | }
36 | header > a {
37 | color: var(--spec-primary);
38 | }
39 |
40 | h1 {
41 | text-align: center;
42 | margin-block: 32px;
43 | font-weight: 700;
44 | }
45 |
46 | h2 {
47 | margin-bottom: 16px;
48 | margin-top: 32px;
49 | padding-block: 8px;
50 | }
51 |
52 | h3 {
53 | margin-top: 16px;
54 | }
55 |
56 | h4 {
57 | margin-top: 12px;
58 | }
59 |
60 | h5 {
61 | margin-top: 8px;
62 | }
63 |
64 | h3,
65 | h4,
66 | h5,
67 | h6 {
68 | margin-bottom: 8px;
69 | }
70 |
71 | p {
72 | line-height: 24px;
73 | text-align: justify;
74 | }
75 |
76 | img {
77 | width: 100%;
78 | max-width: 600px;
79 | height: auto;
80 | display: block;
81 | margin-inline: auto;
82 | margin-block: 16px;
83 | border-radius: 8px;
84 | }
85 |
86 | ol,
87 | ul {
88 | list-style-position: inside;
89 | padding-left: 16px;
90 | margin-top: 12px;
91 | }
92 |
93 | li {
94 | margin-bottom: 12px;
95 | }
96 |
97 | pre {
98 | margin-block: 16px;
99 | }
100 | pre code {
101 | border-radius: 8px;
102 | }
103 | pre * {
104 | font-family: "DankMono", serif;
105 | font-weight: 600;
106 | font-size: 18px;
107 | }
108 |
109 | a {
110 | color: var(--link-primary);
111 | text-decoration: none;
112 | }
113 | a[href="#table-of-contents"] {
114 | color: var(--spec-primary);
115 | border: 2px dashed var(--spec-primary);
116 | padding: 8px 32px;
117 | border-radius: 8px;
118 | display: block;
119 | width: -moz-fit-content;
120 | width: fit-content;
121 | margin-inline: auto;
122 | margin-top: 16px;
123 | margin-bottom: 64px;
124 | font-weight: 700;
125 | font-size: 20px;
126 | }/*# sourceMappingURL=styles.css.map */
--------------------------------------------------------------------------------
/website/styles.scss:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: "DankMono";
3 | src: url("./DankMono-Regular.ttf") format("truetype");
4 | }
5 |
6 | * {
7 | margin: 0;
8 | padding: 0;
9 | box-sizing: border-box;
10 | font-family: "Roboto", sans-serif;
11 | }
12 |
13 | body {
14 | background-color: #ffffff;
15 | scroll-behavior: smooth;
16 | width: 100%;
17 | }
18 |
19 | .container {
20 | max-width: 1200px;
21 | padding-inline: 12px;
22 | margin-inline: auto;
23 | }
24 |
25 | html {
26 | --text-primary: #000000;
27 | --link-primary: #8754ff;
28 | --spec-primary: #ff6b6b;
29 | }
30 |
31 | header {
32 | text-align: right;
33 | padding-block: 16px;
34 | font-size: 20px;
35 | font-weight: 500;
36 |
37 | > a {
38 | color: var(--spec-primary);
39 | }
40 | }
41 |
42 | h1 {
43 | text-align: center;
44 | margin-block: 32px;
45 | font-weight: 700;
46 | }
47 |
48 | h2 {
49 | margin-bottom: 16px;
50 | margin-top: 32px;
51 | padding-block: 8px;
52 | }
53 |
54 | h3 {
55 | margin-top: 16px;
56 | }
57 |
58 | h4 {
59 | margin-top: 12px;
60 | }
61 |
62 | h5 {
63 | margin-top: 8px;
64 | }
65 |
66 | h3,
67 | h4,
68 | h5,
69 | h6 {
70 | margin-bottom: 8px;
71 | }
72 |
73 | p {
74 | line-height: 24px;
75 | text-align: justify;
76 | }
77 |
78 | img {
79 | width: 100%;
80 | max-width: 600px;
81 | height: auto;
82 | display: block;
83 | margin-inline: auto;
84 | margin-block: 16px;
85 | border-radius: 8px;
86 | }
87 |
88 | ol,
89 | ul {
90 | list-style-position: inside;
91 | padding-left: 16px;
92 | margin-top: 12px;
93 | }
94 |
95 | li {
96 | margin-bottom: 12px;
97 | }
98 |
99 | pre {
100 | margin-block: 16px;
101 |
102 | code {
103 | border-radius: 8px;
104 | }
105 |
106 | * {
107 | font-family: "DankMono", serif;
108 | font-weight: 600;
109 | font-size: 18px;
110 | }
111 | }
112 |
113 | a {
114 | color: var(--link-primary);
115 | text-decoration: none;
116 |
117 | &[href="#table-of-contents"] {
118 | color: var(--spec-primary);
119 | border: 2px dashed var(--spec-primary);
120 | padding: 8px 32px;
121 | border-radius: 8px;
122 | display: block;
123 | width: fit-content;
124 | margin-inline: auto;
125 | margin-top: 16px;
126 | margin-bottom: 64px;
127 | font-weight: 700;
128 | font-size: 20px;
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/website/template.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
13 |
14 |
15 |
16 |
17 |
24 |
25 |
26 |
27 |
30 |
31 |
--------------------------------------------------------------------------------