├── LICENSE ├── README.md └── patterns ├── abstract-factory.ts ├── adapter.ts ├── builder.ts ├── composite.ts ├── decorator.ts ├── facade.ts ├── factory-method.ts ├── prototype.ts ├── proxy.ts └── singleton.ts /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Ali Nazari 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TypeScript Design Patterns 2 | Real world examples of design patterns written in TypeScript. Read a deep, detailed explanation of these patterns here: https://ditty.ir/series/design-patterns (In Persian). 3 | 4 | Check out the patterns: 5 | - [Factory Method](https://github.com/AliN11/typescript-design-patterns/blob/main/patterns/factory-method.ts) 6 | - [Abstract Factory](https://github.com/AliN11/typescript-design-patterns/blob/main/patterns/abstract-factory.ts) 7 | - [Builder](https://github.com/AliN11/typescript-design-patterns/blob/main/patterns/builder.ts) 8 | - [Prototype](https://github.com/AliN11/typescript-design-patterns/blob/main/patterns/prototype.ts) 9 | - [Singleton](https://github.com/AliN11/typescript-design-patterns/blob/main/patterns/singleton.ts) 10 | - [Adapter](https://github.com/AliN11/typescript-design-patterns/blob/main/patterns/adapter.ts) 11 | - [Composite](https://github.com/AliN11/typescript-design-patterns/blob/main/patterns/composite.ts) 12 | - [Decorator](https://github.com/AliN11/typescript-design-patterns/blob/main/patterns/decorator.ts) 13 | - [Facade](https://github.com/AliN11/typescript-design-patterns/blob/main/patterns/facade.ts) 14 | - [Proxy](https://github.com/AliN11/typescript-design-patterns/blob/main/patterns/proxy.ts) 15 | - More on the way... 16 | -------------------------------------------------------------------------------- /patterns/abstract-factory.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Abstract Factory Design Pattern. 3 | * This pattern lets us produce related objects that belong to a family, without 4 | * exposing their concrete classes to the client. 5 | * In this example, we are going to create an application that contains various 6 | * tech manufactures with various products. The client will create and interact 7 | * with products and isn't aware of which manufactorer it is working with. This 8 | * example is inspired by: 9 | * https://refactoring.guru/design-patterns/abstract-factory 10 | * You can also read a detailed explanation of this pattern here (in Persian): 11 | * https://ditty.ir/posts/abstract-factory-design-pattern/XbqOn 12 | */ 13 | 14 | /** 15 | * The Abstract Factory Interface. 16 | * In this interface we define the methods that return the abstract products of 17 | * a family. 18 | * In this example, a family is a manufacturer that produces various products. 19 | */ 20 | interface DeviceFactory { 21 | createSmartphone(): Smartphone; 22 | 23 | createTablet(): Tablet; 24 | } 25 | 26 | /** 27 | * Concrete Factory. 28 | * Concrete factories must implement the Abstract Factory interface. Each 29 | * factory produces various products that belong to the same family 30 | * (e.g. Apple Factory that produces its own products). 31 | */ 32 | class AppleFactory implements DeviceFactory { 33 | /** 34 | * The signature of each method is the abstract type of the product that is 35 | * returning by the method. It guarantees that the client will work with 36 | * products through abstraction. 37 | */ 38 | public createSmartphone(): Smartphone { 39 | return new AppleSmartphone(); 40 | } 41 | 42 | public createTablet(): Tablet { 43 | return new AppleTablet(); 44 | } 45 | } 46 | 47 | /** 48 | * Another Concrete Factory. We can create an arbitrary amount of facotories. 49 | * The client will interact with factories through their interface and isn't 50 | * aware of which concrete factory is passed to it. 51 | */ 52 | class SamsungFactory implements DeviceFactory { 53 | public createSmartphone(): Smartphone { 54 | return new SamsungSmartphone(); 55 | } 56 | 57 | public createTablet(): Tablet { 58 | return new SamsungTablet(); 59 | } 60 | } 61 | 62 | /** 63 | * Products Interface. 64 | * We define an interface for each distinct product. All concrete products must 65 | * implement their related interface. 66 | * The below interface is for Tablets and must be implemented by the tablets in 67 | * each family. 68 | */ 69 | interface Tablet { 70 | /** 71 | * We define the operations that are common across all tablets 72 | */ 73 | switchOn(): boolean; 74 | } 75 | 76 | /** 77 | * Another Product interface. 78 | * It includes the set of methods that are common between smartphones 79 | */ 80 | interface Smartphone { 81 | switchOn(): boolean; 82 | ring(): void; 83 | } 84 | 85 | /** 86 | * Concrete Products. Each concrete product implements its respective interface 87 | * and will be created within their respective concrete factories. 88 | * The below product is a Samsung smartphone and then will be created in 89 | * SamsungFactory factory. 90 | */ 91 | class SamsungSmartphone implements Smartphone { 92 | public switchOn(): boolean { 93 | console.log("Samsung Smartphone: Switching on"); 94 | 95 | return true; 96 | } 97 | 98 | public ring() { 99 | console.log("Samsung Smartphone: Ringing"); 100 | } 101 | } 102 | 103 | /** 104 | * Another concrete product. Apple Smartphones that will be created in the 105 | * AppleFactory factory 106 | */ 107 | class AppleSmartphone implements Smartphone { 108 | public switchOn(): boolean { 109 | console.log("Apple Smartphone: Switching on"); 110 | 111 | return true; 112 | } 113 | 114 | public ring() { 115 | console.log("Apple Smartphone: Ringing"); 116 | } 117 | } 118 | 119 | /** 120 | * Apple tablets that will be created in its respective factory 121 | */ 122 | class AppleTablet implements Tablet { 123 | public switchOn(): boolean { 124 | console.log("Apple Tablet: Switching on"); 125 | 126 | return true; 127 | } 128 | } 129 | 130 | /** 131 | * And another product. We can create an arbitrary amount of products if their 132 | * interface exists and being used in their factories. 133 | */ 134 | class SamsungTablet implements Tablet { 135 | public switchOn(): boolean { 136 | console.log("Samsung Tablet: Switching on"); 137 | 138 | return true; 139 | } 140 | } 141 | 142 | /** 143 | * The client. 144 | * The client code will work with factories and products only through abstraction. 145 | * This allows us to pass any kind of factories to the client and work with any 146 | * kind of products without breaking the client code. 147 | */ 148 | function client(factory: DeviceFactory) { 149 | // As we can see, the client isn't aware of which factory is working with 150 | const smartphone = factory.createSmartphone(); 151 | smartphone.ring(); 152 | 153 | const tablet = factory.createTablet(); 154 | 155 | tablet.switchOn(); 156 | } 157 | 158 | /** 159 | * The application and configurations will decide which concrete factory should 160 | * be passed to the client 161 | */ 162 | client(new SamsungFactory()); 163 | 164 | // Samsung Smartphone: Ringing 165 | // Samsung Tablet: Switching on 166 | -------------------------------------------------------------------------------- /patterns/adapter.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Adapter Design Pattern. 3 | * 4 | * This pattern allows objects with incompatible interfaces to work together 5 | * without modifying their source code. 6 | * 7 | * In this example, clients want to send notifications to users with various 8 | * services (e.g. SMS, Email, push). Each service has its own implementations. 9 | * So the client code would be tightly coupled with the services and their 10 | * implementation steps. 11 | * Adapter pattern provides a unified interface. Each service should be wrapped 12 | * into a distinct class that implements that interface. The client works with 13 | * the services only through Adapter interface. 14 | * 15 | * This content is inspired by: 16 | * https://refactoring.guru/design-patterns/adapter 17 | * 18 | * You can also read a detailed explanation of this pattern here (in Persian): 19 | * https://ditty.ir/posts/adapter-design-pattern/JmRx5 20 | */ 21 | 22 | /** 23 | * Adapter Interface. 24 | * This interface defines a method that will be used by the client in order to 25 | * work with the service. 26 | */ 27 | interface Notification { 28 | send(): void; 29 | } 30 | 31 | /** 32 | * An External Service (Adaptee) 33 | * This service lets us send notifications to users via SMS. But its interface 34 | * is not compatible with the client code. In many cases modifying external 35 | * libraries is not possible. We might be able to change client code but what if 36 | * client code wants to work with another service? It is always prone to change. 37 | */ 38 | class XYZ_SMS { 39 | login() {} 40 | setPort() {} 41 | sendSms() { 42 | console.log('Sending SMS'); 43 | } 44 | } 45 | 46 | /** 47 | * Concrete Adapter. 48 | * An Adapter for an external library. This class implements Adapter interface 49 | * and then must implement required methods. 50 | * Concrete adapters should wrap external services. Instead of the client, usually 51 | * adapters directly work with service. An instance of this class will be passed 52 | * to the client and the client will work with the service through its interface. 53 | */ 54 | class XyzSmsAdapter implements Notification { 55 | private service: XYZ_SMS; 56 | 57 | // Adapter wraps the service 58 | constructor(service: XYZ_SMS) { 59 | this.service = service; 60 | } 61 | 62 | // Adapter directly works with the service 63 | public send() { 64 | this.service.login(); 65 | this.service.setPort(); 66 | this.service.sendSms(); 67 | } 68 | } 69 | 70 | // Another Adapter 71 | class EmailNotification implements Notification { 72 | public send() { 73 | console.log('Sending Email'); 74 | } 75 | } 76 | 77 | 78 | /** 79 | * Client Code. 80 | * 81 | * The client works with the services through the Adapter interface. 82 | * The client is able to work with any services as long as the service is an 83 | * implementation of Adapter interface. 84 | */ 85 | function notifyUsers(notifier: Notification) { 86 | notifier.send(); 87 | } 88 | 89 | // We first instantiate our desired adapter and then pass it to the client. 90 | // Notify with SMS: 91 | const SmsNotifier = new XyzSmsAdapter(new XYZ_SMS); 92 | notifyUsers(SmsNotifier); 93 | 94 | // Notify with email: 95 | const emailNotifier = new EmailNotification(); 96 | notifyUsers(emailNotifier); 97 | -------------------------------------------------------------------------------- /patterns/builder.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Builder Design Pattern. 3 | * This pattern allows us to construct a complex objects step by step. Builder 4 | * is useful for when we want to construct an object that needs a lot of 5 | * customization before creation. So Instead of relying on functions with huge 6 | * list of parameters to customize the objects, we use builder pattern that lets 7 | * us build and customize the objects step by step in a cleaner way. 8 | * 9 | * SQL Query Builder is one of the most common use cases of Builder pattern. So 10 | * in this example we are going to create a SQL query builder using Builder Pattern. 11 | * This content is inspired by: 12 | * https://refactoring.guru/design-patterns/builder 13 | * 14 | * You can also read a detailed explanation of this pattern here (in Persian): 15 | * https://ditty.ir/posts/builder-design-pattern/XEW35 16 | */ 17 | 18 | 19 | /** 20 | * The Builder Interface. 21 | * This interface specifies the set of methods for creating the different parts 22 | * of the resulting objects. In SQL, each query is made of several clauses and 23 | * keywords. To build a complex and complete SQL query, we separate each clause 24 | * into a distinct method in this interface. 25 | */ 26 | interface QueryBuilder { 27 | /** 28 | * Some methods' output signature is a type of the Builder Interface. This is 29 | * useful for implementing the method chaining behavior. 30 | */ 31 | table(table): QueryBuilder; 32 | select(cols): QueryBuilder; 33 | limit(value: Number): QueryBuilder; 34 | where(col: string, value: Number|String): QueryBuilder; 35 | 36 | // To get the final result (product) 37 | getQuery(): String; 38 | 39 | /* +100 Other SQL related methods */ 40 | } 41 | 42 | /** 43 | * Concrete Builder class. The program may contain several variations of builders 44 | * that may return totally different type of objects in comparison to another 45 | * builder. 46 | * Each concrete builder must follow the Builder Interface and implement its 47 | * own build steps. 48 | * This class, for example is a query builder for MySQL. 49 | */ 50 | class MySqlQueryBuilder implements QueryBuilder { 51 | /** 52 | * At the end, every builder returns its result. The result is also called 53 | * Product. The result of a SQL query builder, is a valid string of SQL query. 54 | */ 55 | private query: String; 56 | private tableName: String; 57 | 58 | /** 59 | * Each instance of a builder, should be created 60 | * with a raw and empty query (Product) 61 | */ 62 | public constructor() { 63 | this.query = ""; 64 | } 65 | 66 | /** 67 | * We do "return this" to implement the method chaining behavior. 68 | */ 69 | public table(table): QueryBuilder { 70 | this.tableName = table; 71 | 72 | return this; 73 | } 74 | 75 | public select(cols): QueryBuilder { 76 | /** ... */ 77 | return this; 78 | } 79 | 80 | public limit(value: Number): QueryBuilder { 81 | /** ... */ 82 | return this; 83 | } 84 | 85 | public where(col, value): QueryBuilder { 86 | /** ... */ 87 | return this; 88 | } 89 | 90 | public getQuery(): String { 91 | return 'This is a MySQL query'; 92 | } 93 | } 94 | 95 | /** 96 | * Another concrete builder. Concrete builders implement Builder Interface so 97 | * they contain similar methods. But the final product may totally be different 98 | * from the final product of another builder (e.g. MySQL queries are totally 99 | * different from MongoDB queries). Although the client may not be aware of what 100 | * kind of builder is working with. 101 | */ 102 | class MongoDbQueryBuilder implements QueryBuilder { 103 | private query: String; 104 | private tableName: String; 105 | 106 | public constructor() { 107 | this.query = ''; 108 | } 109 | 110 | public table(table): QueryBuilder { 111 | this.tableName = table; 112 | 113 | return this; 114 | } 115 | 116 | public select(cols): QueryBuilder { 117 | /** ... */ 118 | return this; 119 | } 120 | 121 | public limit(value: Number): QueryBuilder { 122 | /** ... */ 123 | return this; 124 | } 125 | 126 | public where(col, value): QueryBuilder { 127 | /** ... */ 128 | return this; 129 | } 130 | 131 | public getQuery(): String { 132 | return 'This is a MongoDB query'; 133 | } 134 | } 135 | 136 | /** 137 | * The Client works with the builders through abstraction. We are able to replace 138 | * the builders passed to the client without breaking the client code. 139 | */ 140 | function client(builder: QueryBuilder) { 141 | const query = builder.table('posts') 142 | .where('id', 429) 143 | .limit(10) 144 | .select(['id', 'title']) 145 | .getQuery(); 146 | 147 | console.log(query); 148 | } 149 | 150 | /** 151 | * The application configuration decides which builder should be used in the 152 | * client 153 | */ 154 | const config = { 155 | database1: MongoDbQueryBuilder, 156 | database2: MySqlQueryBuilder 157 | } 158 | 159 | // Client uses MongoDB: 160 | client(new config.database1); // This is a MongoDB query 161 | 162 | // Client uses MySQL: 163 | client(new config.database2); // This is a MySQL query 164 | 165 | -------------------------------------------------------------------------------- /patterns/composite.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Composite Design Pattern. 3 | * 4 | * This pattern allows us to compose different objects into a tree structure 5 | * and then treat the objects uniformly. In other words, we can treat a group of 6 | * object the same way as a single object. 7 | * 8 | * In this example, we are going to build a tree of employees in an organization. 9 | * An organization may contain many departments and the departments may contain 10 | * other departments and also employees. By using this pattern we want to get the 11 | * information and revenue of whole organization (departments + employees). 12 | * 13 | * This content is inspired by: 14 | * https://refactoring.guru/design-patterns/composite 15 | * 16 | * You can also read a detailed explanation of this pattern here (in Persian): 17 | * https://ditty.ir/posts/composite-design-pattern/5v695 18 | */ 19 | 20 | 21 | /** 22 | * The Component Interface. 23 | * This interface describes operations that make sense in both simple and complex 24 | * components in the tree. 25 | * This 26 | */ 27 | abstract class Organ { 28 | abstract getInformation(): string; 29 | abstract getRevenue(): number; 30 | 31 | /** 32 | * Methods to add or remove other components. Compound components should override 33 | * these methods. 34 | */ 35 | public add(organ: Organ): void { } 36 | 37 | public remove(organ: Organ): void { } 38 | } 39 | 40 | 41 | /** 42 | * A simple component. Also called "Leaf". 43 | * A leaf is located at the end of tree and doesn't have any sub-components. 44 | * Usually all the demanded works are delegated to the leaves. In other words, 45 | * in a tree, a leaf does the actuall job. Compound components usually delegate 46 | * the request to their sub-components and finally sum-up the result. 47 | */ 48 | class SimpleOrgan extends Organ { 49 | public name: string; 50 | 51 | constructor(name: string) { 52 | super(); 53 | this.name = name; 54 | } 55 | 56 | public getInformation() { 57 | return `- My name is ${this.name}\n`; 58 | } 59 | 60 | public getRevenue() { 61 | return Math.floor(Math.random() * 10000) + 1000; 62 | } 63 | } 64 | 65 | /** 66 | * A compound component. Also called "Container". 67 | * As its name implies, a cotainer includes other components. A container may 68 | * contain another containers or a simple leaf. 69 | */ 70 | class CompoundOrgan extends Organ { 71 | public children: Organ[] = []; 72 | public name: string; 73 | 74 | constructor(name: string) { 75 | super(); 76 | this.name = name; 77 | } 78 | 79 | /** 80 | * A container collects data from current component and its sub-components. 81 | */ 82 | public getInformation() { 83 | let output = `- This is ${this.name} organ.`; 84 | output += ` It contains ${this.children.length} members \n`; 85 | 86 | this.children.forEach(organ => { 87 | output += organ.getInformation(); 88 | }); 89 | 90 | return output; 91 | } 92 | 93 | /** 94 | * Getting revenue from sub-components (sub-containers and leaves) 95 | */ 96 | getRevenue(): number { 97 | let output = 0; 98 | 99 | this.children.forEach(organ => { 100 | output += organ.getRevenue(); 101 | }); 102 | 103 | return output; 104 | } 105 | 106 | public add(organ: Organ): void { 107 | this.children.push(organ); 108 | } 109 | 110 | public remove(organ: Organ): void { 111 | const index = this.children.indexOf(organ); 112 | this.children.splice(index, 1); 113 | } 114 | } 115 | 116 | 117 | // Constructing the Organization tree. 118 | const organization = new CompoundOrgan('Main'); 119 | organization.add(new SimpleOrgan('Alex as Founder')); 120 | organization.add(new SimpleOrgan('Morgan as HR')); 121 | 122 | const officers = new CompoundOrgan('Officers'); 123 | officers.add(new SimpleOrgan('John as CEO')); 124 | officers.add(new SimpleOrgan('John as CTO')); 125 | organization.add(officers); 126 | 127 | const employees = new CompoundOrgan('Employees'); 128 | const it = new CompoundOrgan('IT'); 129 | const designers = new CompoundOrgan('Desingers'); 130 | designers.add(new SimpleOrgan('Sarah as Desinger')); 131 | designers.add(new SimpleOrgan('John as Desinger')); 132 | designers.add(new SimpleOrgan('Emily as Desinger')); 133 | designers.add(new SimpleOrgan('Mario as Desinger')); 134 | 135 | it.add(designers); 136 | employees.add(it); 137 | organization.add(employees); 138 | 139 | 140 | /** 141 | * The client code. 142 | * The client works with the structure only via the base interface. So the 143 | * client is able to work with any component (simple, complex) without depending 144 | * on their concrete classes. 145 | */ 146 | function client(organization: Organ) { 147 | console.log(organization.getInformation()); 148 | } 149 | 150 | -------------------------------------------------------------------------------- /patterns/decorator.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Decorator Design Pattern 3 | * 4 | * This pattern allows us to attach new behaviors to an existing object 5 | * at run-time without changing their classes and relying on sub-classing. Each 6 | * behavior is defined in a distinct class called decorator and will wrap the 7 | * intended object to add the behavior to that. 8 | * 9 | * In this example we have a hotel and rooms with basic specifications. We want 10 | * to add behaviors to rooms on demand. 11 | * 12 | * This content is inspired by: 13 | * https://refactoring.guru/design-patterns/decorator 14 | * 15 | * You can also read a detailed explanation of this pattern here (in Persian): 16 | * https://ditty.ir/posts/decorator-design-pattern/5dPv5 17 | */ 18 | 19 | /** 20 | * Base Component Interface 21 | * This interface defines the operations that are common to both the intended 22 | * object and the decorators (behaviors). 23 | */ 24 | interface RoomInterface { 25 | getDescription(): string; 26 | getPrice(): number; 27 | } 28 | 29 | /** 30 | * Concrete Component (The object to be decorated) 31 | * The Concrete Components should implement the Base Component interface in order 32 | * for the instances to be compatible with decorators. 33 | * Here we have a simple room, but we can also have more variations. 34 | */ 35 | class SimpleRoom implements RoomInterface { 36 | getDescription() { 37 | return "Base room"; 38 | } 39 | 40 | getPrice() { 41 | return 2.0; 42 | } 43 | } 44 | 45 | /** 46 | * Base Decorator Class 47 | * This class follows the same Interface as the other components. By doing this, 48 | * the Concrete Components and the decorators are compatible with each other and 49 | * then we are able to wrap the concrete components and the decorators into 50 | * each other. 51 | */ 52 | abstract class BaseDecorator implements RoomInterface { 53 | /** 54 | * To implement wrapping feature, we define a field for storing wrapping 55 | * components. 56 | */ 57 | protected room: RoomInterface; 58 | 59 | constructor(room: RoomInterface) { 60 | this.room = room; 61 | } 62 | 63 | /** 64 | * The Decorator delegates all works to the wrapped component. 65 | */ 66 | public getDescription() { 67 | return this.room.getDescription(); 68 | } 69 | 70 | public getPrice() { 71 | return this.room.getPrice(); 72 | } 73 | } 74 | 75 | /** 76 | * Concrete Decorators. 77 | * Each Decorator extends the Base Decorator class, defines and adds the desired 78 | * behaviors to the decorated object. 79 | */ 80 | class WiFiDecorator extends BaseDecorator { 81 | public getDescription() { 82 | return `${super.getDescription()} + WiFi`; 83 | } 84 | 85 | public getPrice() { 86 | return super.getPrice() + 0.2; 87 | } 88 | } 89 | 90 | /** 91 | * Another Concrete Decorator: Adds "breakfast" behaviors to the component (room) 92 | */ 93 | class BreakfastDecorator extends BaseDecorator { 94 | public getDescription() { 95 | return `${super.getDescription()} + Breakfast`; 96 | } 97 | 98 | public getPrice() { 99 | return super.getPrice() + 2.00; 100 | } 101 | } 102 | 103 | /** 104 | * Client 105 | * The client works with all objects via Base Interface. This guarantees that 106 | * the client is able to work with simple components or decorated versions of 107 | * them. 108 | */ 109 | function client(room: RoomInterface) { 110 | console.log(room.getDescription()); 111 | console.log(room.getPrice()); 112 | } 113 | 114 | // Creating a simple component 115 | let room = new SimpleRoom(); 116 | // Wrapping the component into a decorator to add the Wi-Fi behavior 117 | room = new WiFiDecorator(room); 118 | // Wrap decorated component into another decorator 119 | room = new BreakfastDecorator(room); 120 | 121 | client(room); 122 | 123 | -------------------------------------------------------------------------------- /patterns/facade.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Facade Design Pattern 3 | * This pattern provides a simplified interface to a complex system. 4 | * This pattern is useful for when a library or a class has some complexities 5 | * in order to be uses. Facade pattern hides those complexities in itself and 6 | * provides an interface to use that library or class. 7 | * In this example we want to simplify using a sms library by using Facade pattern. 8 | * 9 | * This content is inspired by: 10 | * https://refactoring.guru/design-patterns/facade 11 | * 12 | * You can also read a detailed explanation of this pattern here (in Persian): 13 | * https://ditty.ir/posts/facade-design-pattern/nx765 14 | */ 15 | 16 | 17 | /** 18 | * Facade Class 19 | * Facade class constructs the desired object within itself and provides a simple 20 | * interface for the client to use that object. Facade also manages the lifecycle 21 | * of the object. 22 | * 23 | */ 24 | class SmsFacade { 25 | /** 26 | * The send method only takes the required arguments from the client. 27 | * The complexities of initializing and constructing the SMS library are never 28 | * exposed to the client. 29 | */ 30 | public static send(text, recipient) { 31 | const client_id = config('sms.client_id'); 32 | const client_secret = config('sms.client_secret'); 33 | const sms_driver = config('sms.driver'); 34 | 35 | const sms = new SmsLibrary(client_id, client_secret, sms_driver); 36 | 37 | sms.recipient(recipient); 38 | sms.send(text); 39 | } 40 | } 41 | 42 | /** 43 | * To send SMS, client code uses the interface that Facade pattern provided. 44 | * As we can see, the client is not aware of what is happening behind the scenes 45 | */ 46 | SmsFacade.send('Welcome!', '+989...'); 47 | // ... 48 | SmsFacade.send('Your 2FA code', '+001...'); 49 | -------------------------------------------------------------------------------- /patterns/factory-method.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Factory Method Design Pattern. 3 | * By this pattern, we can move the object constructions to a separate, special 4 | * methods named Factory Methods. The factory method is responsible for constructing 5 | * and delivering desired objects. 6 | * 7 | * In this example, we are going to create a delivery app with various delivery 8 | * methods. This example is inspired by: 9 | * https://refactoring.guru/design-patterns/factory-method 10 | */ 11 | 12 | /** 13 | * Creator Class. 14 | * The Creator class contains the factory method (in this case: makeVehicle()). 15 | * Its factory method returns an object of a product (Bike, Car, ...). 16 | * The Creator is usually an abstract class and its subclasses are supposed to 17 | * provide the implementation of its factory method. 18 | */ 19 | abstract class Delivery { 20 | public abstract makeVehicle(): Vehicle; 21 | 22 | public handle(): void { 23 | // Calling the factory method to get the product 24 | const vehicle = this.makeVehicle(); 25 | 26 | // Finally, working with the product 27 | vehicle.move(); 28 | } 29 | } 30 | 31 | /** 32 | * Concrete Creator. 33 | * Concrete Creators are supposed to override the factory method and return 34 | * their own configured product. 35 | */ 36 | class BikeDelivery extends Delivery { 37 | /** 38 | * The factory method implementation. 39 | * Creation and configuration of the concrete product happens here. 40 | * The factory method's return type is the abstract product. It ensures the 41 | * Creator to be independent of concrete products. 42 | */ 43 | public makeVehicle(): Vehicle { 44 | const bike = new Bike(); 45 | bike.setMode('eco'); 46 | 47 | return bike; 48 | } 49 | } 50 | 51 | /** 52 | * Another Concrete Creator. 53 | * We usually make a concrete creator for every 54 | * concrete product. 55 | */ 56 | class CarDelivery extends Delivery { 57 | public makeVehicle(): Vehicle { 58 | const car = new Car(); 59 | car.setColor('green'); 60 | 61 | return car; 62 | } 63 | } 64 | 65 | /** 66 | * Products Interface. 67 | * Interface for concrete products. 68 | * Products Interface contains the operations that concrete products 69 | * (Bike, Car, Train, ...) must implement. 70 | */ 71 | interface Vehicle { 72 | setMode(mode: String): void; 73 | move(): void; 74 | } 75 | 76 | /** 77 | * Concrete Product. 78 | * Concrete products, in their own way, must provide the implementation of the 79 | * Product Interface. 80 | */ 81 | class Bike implements Vehicle { 82 | setMode(mode: String): void { 83 | // this.mode = mode; 84 | } 85 | 86 | move(): void { 87 | console.log('Delivering by bike'); 88 | }; 89 | } 90 | 91 | /** 92 | * Another Concrete Product that in its own way, implements the Product 93 | * Interface. 94 | */ 95 | class Car implements Vehicle { 96 | setMode(mode: String): void { 97 | // this.mode = mode; 98 | } 99 | 100 | move(): void { 101 | console.log('Delivering by car'); 102 | } 103 | 104 | setColor(color: String): void { 105 | 106 | } 107 | } 108 | 109 | /** 110 | * Client code. 111 | * The client works with the concrete creators through their interface. 112 | * By that, we can pass any creator without client caring about which concrete 113 | * creator is working with. 114 | */ 115 | function client(delivery: Delivery) { 116 | delivery.handle(); 117 | } 118 | 119 | // Delivery by car 120 | client(new CarDelivery()); 121 | 122 | // Delivery by bike 123 | client(new BikeDelivery()); 124 | -------------------------------------------------------------------------------- /patterns/prototype.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Prototype Design Pattern. 3 | * This pattern allows us to copy (clone) an object without depending their classes. 4 | * This is useful for when instantiating an object constructing it is costly or real 5 | * object's class is unknown to us. 6 | * This content is inspired by: 7 | * https://refactoring.guru/design-patterns/prototype 8 | * 9 | * You can also read a detailed explanation of this pattern here (in Persian): 10 | * https://ditty.ir/posts/prototype-design-pattern/X8dLX 11 | */ 12 | 13 | /** 14 | * Prototype Interface 15 | * We make an interface for cloneable objects. If a class implement this interface, 16 | * it means its objects can be cloned. 17 | */ 18 | interface Prototype { 19 | /** 20 | * clone method should return an object with the same type 21 | */ 22 | clone(): T; 23 | } 24 | 25 | /** 26 | * Concrete Cloneable Classes 27 | */ 28 | class Book implements Prototype { 29 | private title; 30 | private price; 31 | private content = null; 32 | 33 | constructor(title, price, content = null) { 34 | this.title = title; 35 | this.price = price; 36 | 37 | /** 38 | * When constructing an object for the first time, the book content is not 39 | * available and should be fetched from the database. We've added an ability 40 | * to provide the previous fetched content when cloning the object in order 41 | * to reduce resources' usage and increase the application speed. 42 | */ 43 | this.content = content !== null ? content : this.fetchContentFromDb(); 44 | } 45 | 46 | /** 47 | * clone method in concrete classes usually works with the current object, 48 | * may customize and re-configure its properties and then return it. 49 | */ 50 | public clone(): Book { 51 | const content = this.content + ' (cached)'; 52 | 53 | // The previous content get passed here 54 | return new Book(this.title, this.price, content); 55 | } 56 | 57 | public fetchContentFromDb() { 58 | // const content = db.books.where('title', title).find().content; 59 | const content = "The book content"; 60 | 61 | return content; 62 | } 63 | 64 | public getContent() { 65 | return this.content; 66 | } 67 | } 68 | 69 | // The initial object 70 | const original = new Book('Funny JS', 36); 71 | 72 | // Cloning the object 73 | const cloned = original.clone(); 74 | 75 | console.log(original.getContent()); // The book content 76 | console.log(cloned.getContent()); // The book content (cached) 77 | 78 | 79 | -------------------------------------------------------------------------------- /patterns/proxy.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Proxy Design Pattern 3 | * 4 | * Proxy pattern acts as an interface or wrapper for an object and allows us to 5 | * control access to the original object. 6 | * In this example, we've implemented a downloader class. Since downloading 7 | * files is considered as a heavy operation, we'd like to cache the downloaded 8 | * files and return the cached files in subsequent requests. 9 | * The Proxy pattern allows us to implement this feature without modifying the 10 | * downloader class. 11 | * 12 | * This content is inspired by: 13 | * https://refactoring.guru/design-patterns/proxy 14 | * 15 | * You can also read a detailed explanation of this pattern here (in Persian): 16 | * https://ditty.ir/posts/proxy-design-pattern/Jq6W5 17 | */ 18 | 19 | /** 20 | * Subject Interface 21 | * This interface declares the operations that are common to both real subject (the 22 | * object that we want to improve it) and the proxy itself. The client works with 23 | * this interface. So we can pass both real object and the proxied object. 24 | */ 25 | interface DownloaderInterface { 26 | download(path); 27 | } 28 | 29 | /** 30 | * Real Subject 31 | * This class does some useful but heavy work. It downloads desired files whenever 32 | * `download` method is called and if the client wants to download a file multiple 33 | * times, it's not going to be an optimal solution. 34 | * 35 | */ 36 | class FileDownloader implements DownloaderInterface { 37 | public download(path) { 38 | console.log(`Downloading ${path}...`); 39 | } 40 | } 41 | 42 | /** 43 | * The Proxy 44 | * This class implements to Subject Interface and maintains a reference to the 45 | * Real Subject so that it can forward requests to it. 46 | * The proxy object receives requests form the client and does some works before 47 | * or after forwarding the requests to the Real Subject. 48 | * In this example, download method first checks whether the desired file is 49 | * downloaded before. If not, the file will be downloaded via `download` method 50 | * in the Real Subject and will be cached for subsequent requests. 51 | */ 52 | class FileDownloaderProxy implements DownloaderInterface { 53 | private downloader: FileDownloader; 54 | private cachedFiles = {}; 55 | 56 | constructor() { 57 | this.downloader = new FileDownloader; 58 | } 59 | 60 | public download(path) { 61 | if (this.cachedFiles.hasOwnProperty(path)) { 62 | console.log(`Read ${path} from cache`); 63 | 64 | // Returning cached file 65 | return this.cachedFiles[path]; 66 | } else { 67 | const result = this.downloader.download(path); 68 | 69 | this.cachedFiles[path] = result; 70 | } 71 | } 72 | } 73 | 74 | /** 75 | * The client works with various downloaders via Subject Interface. So both Real 76 | * Subject and the proxied version can be passed to it. 77 | */ 78 | function client(downloader: DownloaderInterface) { 79 | downloader.download('http://path-to-file.jpg'); 80 | downloader.download('http://path-to-file.jpg'); 81 | downloader.download('http://path-to-file.jpg'); 82 | downloader.download('http://path-to-file.jpg'); 83 | downloader.download('http://path-to-file.jpg'); 84 | } 85 | 86 | client(new FileDownloaderProxy()); 87 | 88 | // logs: 89 | // Downloading http://path-to-file.jpg... 90 | // Read http://path-to-file.jpg from cache 91 | // Read http://path-to-file.jpg from cache 92 | // Read http://path-to-file.jpg from cache 93 | // Read http://path-to-file.jpg from cache 94 | -------------------------------------------------------------------------------- /patterns/singleton.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Singleton Design Pattern. 3 | * This pattern ensures that a class only has one instance throughout the 4 | * program and provides a method to get that instance. 5 | * 6 | * In this example we want to make a class for accessing application configuration 7 | * variables. 8 | * Assuming that this class loads whole variables from several distinct config 9 | * files on local file-system and remote server, this could be a heavy operation 10 | * and will slow down the application if the clients try to instantiate this 11 | * class every time they need the variables. 12 | * By applying Singleton pattern to this problem, we can load the config files 13 | * only once, cache them and then return same result for the later usages. 14 | * 15 | * This content is inspired by: 16 | * https://refactoring.guru/design-patterns/singleton 17 | * 18 | * You can also read a detailed explanation of this pattern here (in Persian): 19 | * https://ditty.ir/posts/singleton-design-pattern/XNrxX 20 | */ 21 | 22 | /** 23 | * Config, the Singleton class. 24 | * It should define a `getInstance` method to allow clients to access the instance. 25 | */ 26 | class Config { 27 | /** 28 | * The instance object that `getInstance` method will return it. 29 | */ 30 | private static instance: Config = null; 31 | 32 | /** 33 | * The application configuration variables that will be get and set only once. 34 | */ 35 | private items: Object; 36 | 37 | /** 38 | * The singleton's constructor sould always be private to disallow direct 39 | * object construction with the `new` operator, outside the class. 40 | */ 41 | private constructor() { 42 | this.items = {}; 43 | const files = this.loadAllConfigFiles(); 44 | 45 | for (const file in files) { 46 | this.items[file] = files[file]; 47 | } 48 | } 49 | 50 | /** 51 | * The static `getInstance` method that creates the instance if it is not been 52 | * created yet and is the only way to access the instance. 53 | */ 54 | public static getInstance() { 55 | if (this.instance === null) { 56 | this.instance = new Config(); 57 | } 58 | 59 | return this.instance; 60 | } 61 | 62 | /** 63 | * Some business logics that we intend to execute them once. 64 | */ 65 | private loadAllConfigFiles(): Object { 66 | /** 67 | * Suppose these variables are fetched from the server and file-system. 68 | */ 69 | return { 70 | app_locale: 'en', 71 | db_host: '127.0.0.1', 72 | db_port: '3306', 73 | }; 74 | } 75 | 76 | public get(key) { 77 | return this.items[key]; 78 | } 79 | 80 | public set(key, value) { 81 | this.items[key] = value; 82 | } 83 | } 84 | 85 | /** 86 | * The Client code. 87 | * The only way the access the config variables is by using the `getInstance` 88 | * method. 89 | */ 90 | const config = Config.getInstance(); 91 | config.set('app_locale', 'fa'); 92 | 93 | // Accessing the variables somewhere else in the program. 94 | const config2 = Config.getInstance(); 95 | 96 | // Checking whether the instances are the same: 97 | console.log(config === config2); // true 98 | --------------------------------------------------------------------------------