├── 00_0_intro.md ├── 00_1_0-creational-patterns.md ├── 00_1_1_0-factory.md ├── 00_1_1_1-abstract-factory.md ├── 00_1_2-singleton.md ├── 00_1_4-builder.md ├── 00_1_5_0-prototype.md └── README.md /00_0_intro.md: -------------------------------------------------------------------------------- 1 | # Design Patterns 2 | 3 | -------------------------------------------------------------------------------- /00_1_0-creational-patterns.md: -------------------------------------------------------------------------------- 1 | # Creational Patterns 2 | 3 | -------------------------------------------------------------------------------- /00_1_1_0-factory.md: -------------------------------------------------------------------------------- 1 | # Factory 2 | 3 | 10 | 11 | * We have the interface `IUser` which is the `blueprint` of the base class `User` (or, what's the same, `User` class implements the interface `IUSer`) 12 | * We have the subclasses `Reader`, `Writer` and `Admin` which extend the base class `User`. 13 | * We have the factory `UserFactory` which is going to build specific classes of `Users` without the calling class knowing how Users are created. 14 | 15 | ```ts 16 | enum Role { 17 | READER, 18 | WRITER, 19 | ADMIN 20 | } 21 | 22 | interface IUser { 23 | name: string; 24 | role: Role; 25 | salary: number; 26 | } 27 | 28 | class User implements IUser { 29 | name = ''; 30 | role = 0; 31 | salary = 0; 32 | } 33 | 34 | class Reader extends User { 35 | constructor(name: string) { 36 | super(); 37 | this.name = name; 38 | this.role = Role.READER 39 | this.salary = 1 40 | } 41 | } 42 | 43 | class Writer extends User { 44 | constructor(name: string) { 45 | super(); 46 | this.name = name; 47 | this.role = Role.WRITER 48 | this.salary = 2 49 | } 50 | } 51 | 52 | class Admin extends User { 53 | constructor(name: string) { 54 | super(); 55 | this.name = name; 56 | this.role = Role.ADMIN 57 | this.salary = 3 58 | } 59 | } 60 | 61 | class UserFactory { 62 | public static getUser(name: string, role: Role): IUser { 63 | if (role === Role.READER ) { 64 | return new Reader(name); 65 | } else if (role === Role.WRITER) { 66 | return new Writer(name); 67 | } else { 68 | return new Admin(name); 69 | } 70 | } 71 | } 72 | 73 | const reader = UserFactory.getUser('Peter', 0); 74 | console.log(reader); 75 | // Reader: { 76 | // "name": "Peter", 77 | // "role": 0, 78 | // "salary": 1 79 | // } 80 | 81 | const writer = UserFactory.getUser('Wendy', 1); 82 | console.log(writer); 83 | // Writer: { 84 | // "name": "Wendy", 85 | // "role": 1, 86 | // "salary": 2 87 | // } 88 | 89 | const admin = UserFactory.getUser('', 2); 90 | console.log(admin); 91 | // Admin: { 92 | // "name": "", 93 | // "role": 2, 94 | // "salary": 3 95 | // } 96 | ``` -------------------------------------------------------------------------------- /00_1_1_1-abstract-factory.md: -------------------------------------------------------------------------------- 1 | # Abstract Factory 2 | 3 | 10 | 11 | * We have the `DogFactory` which returns a new instance of `FrenchBulldog`, `Husky` or throw an error. 12 | * We have the `CatFactory` which returns a new instance of `Ragdoll` or throw an error. 13 | * We have the `AnimalFactory` which has the static method `getAnimal()`. This method takes an animal (name and breed) and return an animal (either of the type DogBreed or CatBreed). 14 | 15 | ```ts 16 | 17 | // Dog 18 | type DogBreed = 'French Bulldog' | 'Husky'; 19 | 20 | interface IDog { 21 | name: string; 22 | breed: DogBreed | unknown; 23 | } 24 | 25 | class Dog implements IDog { 26 | name = ''; 27 | breed = ''; 28 | } 29 | 30 | class FrenchBulldog extends Dog { 31 | constructor(name: string) { 32 | super() 33 | this.name = name; 34 | this.breed = 'French Bulldog'; 35 | } 36 | } 37 | 38 | class Husky extends Dog { 39 | constructor(name: string) { 40 | super() 41 | this.name = name; 42 | this.breed = 'Husky'; 43 | } 44 | } 45 | 46 | class DogFactory { 47 | static getDog(name: string, breed: DogBreed): IDog | never { 48 | if (breed === 'French Bulldog') { 49 | return new FrenchBulldog(name); 50 | } else if (breed === 'Husky') { 51 | return new Husky(name); 52 | } else { 53 | throw new Error(`Dog breed not supported!`); 54 | } 55 | } 56 | } 57 | 58 | // Cat 59 | 60 | type CatBreed = 'Ragdoll' 61 | 62 | interface ICat { 63 | name: string; 64 | breed: CatBreed | unknown; 65 | } 66 | 67 | class Cat implements ICat { 68 | name = ''; 69 | breed = '' 70 | } 71 | 72 | class Ragdoll extends Cat { 73 | constructor(name: string) { 74 | super() 75 | this.name = name; 76 | this.breed = 'Ragdoll'; 77 | } 78 | } 79 | 80 | 81 | class CatFactory { 82 | static getCat(name: string, breed: CatBreed): ICat { 83 | if (breed === 'Ragdoll') { 84 | return new Ragdoll(name); 85 | } else { 86 | throw new Error(`Cat breed not supported!`); 87 | } 88 | } 89 | } 90 | 91 | 92 | // Animal Asbtract Factory 93 | 94 | interface IAnimal extends IDog, ICat {} 95 | 96 | class AnimalFactory { 97 | static getAnimal(breed: DogBreed | CatBreed, name: string): IAnimal | void { 98 | try { 99 | if (['French Bulldog', 'Husky'].includes(breed)) return DogFactory.getDog(name, breed as DogBreed); 100 | if (['Ragdoll'].includes(breed)) return CatFactory.getCat(name, breed as CatBreed); 101 | 102 | throw new Error('We do not have that Factory'); 103 | 104 | } catch (err) { 105 | console.log(err); 106 | } 107 | } 108 | } 109 | 110 | 111 | const animal1 = AnimalFactory.getAnimal('Ragdoll', 'Peter'); 112 | console.log(animal1); 113 | // Ragdoll: { 114 | // "name": "Peter", 115 | // "breed": "Ragdoll" 116 | // } 117 | 118 | const animal2 = AnimalFactory.getAnimal('Husky', 'Wendy'); 119 | console.log(animal2); 120 | // Husky: { 121 | // "name": "Wendy", 122 | // "breed": "Husky" 123 | // } 124 | 125 | const animal3 = AnimalFactory.getAnimal('French Bulldog', 'Hook'); 126 | console.log(animal3); 127 | // FrenchBulldog: { 128 | // "name": "Hook", 129 | // "breed": "French Bulldog" 130 | // } 131 | 132 | const animal4 = AnimalFactory.getAnimal('Other', 'Peter'); 133 | console.log(animal4); 134 | // We do not have that Factory 135 | // undefined 136 | ``` -------------------------------------------------------------------------------- /00_1_2-singleton.md: -------------------------------------------------------------------------------- 1 | # Singleton 2 | 3 | 15 | 16 | * We have the interface `IUser` which is the `blueprint` of the base class `User` (or, what's the same, `User` class implements the interface `IUSer`) 17 | * We have the subclasses `Reader`, `Writer` and `Admin` which extend the base class `User`. 18 | * We have the factory `UserFactory` which is going to build specific classes of `Users` without the calling class knowing how Users are created. 19 | 20 | 21 | * We have the class `LeaderBoard` that has the following members: 22 | * instance (static) 23 | * #players (private field for players) 24 | * constructor() 25 | * addWinner() 26 | * show() 27 | 28 | Quick note... Remember: 29 | * public -> everyone (default) 30 | * protected -> the own class and the subclasses 31 | * private -> the own class 32 | * static -> called on the class itself (not instances) 33 | 34 | Going back to the explanation. 35 | * We have the `instance` which is of type `LeaderBoard` 36 | * We have the private field `#players` 37 | * Every time the constructor runs we `check if instance has been initialized`. If so, we return the instance, if not, we instantiate it. 38 | * Then we have 2 methods exposed `addWiner()` and `show()`, one for adding the winner of a game and the other for showing the leaderboard. 39 | 40 | After this, we initialize the class `let leaderBoard = new LeaderBoard()` and add the winners. 41 | Once we initialize the class, we are always going to operate with the same (unique) instance of that class. 42 | 43 | 44 | ```ts 45 | interface IPlayer { 46 | [name: string]: number 47 | } 48 | 49 | class LeaderBoard { 50 | static instance: LeaderBoard; 51 | #players: IPlayer = {} 52 | 53 | constructor() { 54 | if (LeaderBoard.instance) { 55 | return LeaderBoard.instance; 56 | } 57 | LeaderBoard.instance = this; 58 | } 59 | 60 | public addWinner(name: string, points: number): void { 61 | this.#players[name] = (!this.#players[name] ? 0 : this.#players[name]) + points; 62 | } 63 | 64 | public show(): void { 65 | console.log(this.#players); 66 | } 67 | } 68 | 69 | 70 | let leaderBoard = new LeaderBoard() 71 | 72 | console.log(leaderBoard); 73 | // LeaderBoard: {} 74 | 75 | type Players = 'Peter' | 'Wendy' | 'Hook' 76 | 77 | interface IGame { 78 | players: Array, 79 | winner: Players, 80 | points: number 81 | } 82 | 83 | // Peter plays aganist Wendy and wins 84 | let game1Results: IGame = { 85 | players: ['Peter', 'Wendy'], 86 | winner: 'Peter', 87 | points: 1 88 | } 89 | 90 | leaderBoard.addWinner(game1Results.winner, game1Results.points); 91 | 92 | leaderBoard.show(); 93 | // { 94 | // "Peter": 1 95 | // } 96 | 97 | // Wendy plays aganist Hook and wins 98 | let game2Results: IGame = { 99 | players: ['Hook', 'Wendy'], 100 | winner: 'Wendy', 101 | points: 1 102 | } 103 | 104 | leaderBoard.addWinner(game2Results.winner, game2Results.points); 105 | 106 | leaderBoard.show(); 107 | // { 108 | // "Peter": 1 109 | // } 110 | 111 | // Wendy plays aganist Peter and wins 112 | let game3Results: IGame = { 113 | players: ['Wendy', 'Peter'], 114 | winner: 'Wendy', 115 | points: 1 116 | } 117 | 118 | leaderBoard.addWinner(game3Results.winner, game3Results.points); 119 | 120 | leaderBoard.show(); 121 | // { 122 | // "Peter": 1, 123 | // "Wendy": 2 124 | // } 125 | 126 | 127 | const lead = new LeaderBoard(); 128 | lead.show(); 129 | // { 130 | // "Peter": 1, 131 | // "Wendy": 2 132 | // } 133 | 134 | leaderBoard.show(); 135 | // { 136 | // "Peter": 1, 137 | // "Wendy": 2 138 | // } 139 | ``` 140 | 141 | 142 | -------------------------------------------------------------------------------- /00_1_4-builder.md: -------------------------------------------------------------------------------- 1 | # Builder 2 | 3 | 10 | 11 | * We have a `User` class, 2 Director classes (`ReaderDirector` and `WriterDirector`) and that use the `UserBuilder`. 12 | * The Directors uses the `UserBuilder` class to build a `User`. Each Director is going to generate a different type of `User` 13 | (the `WriterDirector` sets also a salary). 14 | * Inside the Director, we can chose which methods use and their order (indistinct). 15 | * Methods are chained. This is possible because each method except the last one, `getUser()`, returns `this`. 16 | (each methods except getUser() returns a reference to `UserBuilder`) 17 | 18 | ```ts 19 | class User { 20 | name = ''; 21 | salary = 0; 22 | role = '' 23 | 24 | construction(): string { 25 | return `I'm a ${this.role}. My name is ${this.name}. My salary is ${this.salary}` 26 | } 27 | } 28 | 29 | type Role = 'reader' | 'writer' 30 | 31 | interface IUserBuilder { 32 | user: User; 33 | setRole(userRole: Role): this; 34 | setSalary(userSalary: number): this; 35 | setName(name: string): this; 36 | getUser(): User; 37 | } 38 | 39 | class UserBuilder implements IUserBuilder { 40 | user: User; 41 | 42 | constructor() { 43 | this.user = new User(); 44 | } 45 | 46 | setRole(userRole: Role): this { 47 | this.user.role = userRole; 48 | return this; 49 | } 50 | 51 | setSalary(userSalary: number): this { 52 | this.user.salary = userSalary; 53 | return this; 54 | } 55 | 56 | setName(userName: string): this { 57 | this.user.name = userName; 58 | return this; 59 | } 60 | 61 | getUser() { 62 | return this.user; 63 | } 64 | } 65 | 66 | class ReaderDirector { 67 | static construct(name: string): User { 68 | return new UserBuilder() 69 | .setRole('reader') 70 | .setName(name) 71 | .getUser(); 72 | } 73 | } 74 | 75 | class WriterDirector { 76 | static construct(name: string): User { 77 | return new UserBuilder() 78 | .setRole('writer') 79 | .setName(name) 80 | .setSalary(100) 81 | .getUser(); 82 | } 83 | } 84 | 85 | 86 | 87 | const reader = ReaderDirector.construct('Peter'); 88 | const writer = WriterDirector.construct('Wendy'); 89 | 90 | console.log(reader); 91 | // User: { 92 | // "name": "", 93 | // "salary": 0, 94 | // "role": "reader" 95 | // } 96 | 97 | console.log(writer); 98 | // User: { 99 | // "name": "", 100 | // "salary": 100, 101 | // "role": "writer" 102 | // } 103 | 104 | console.log(reader.construction()); 105 | // "I'm a reader. My name is Peter. My salary is 0" 106 | 107 | console.log(writer.construction()); 108 | // "I'm a writer. My name is Wendy. My salary is 100" 109 | ``` -------------------------------------------------------------------------------- /00_1_5_0-prototype.md: -------------------------------------------------------------------------------- 1 | # Prototype 2 | 3 | 14 | 15 | * We have the `User` class that has the property `data` and the method `clone()` 16 | * We create a new object instantiating the class. Then, we clone that object and assign the returned value to the variable `copiedObject` 17 | * If we change the vakue of a nested property of the object `copiedObject` the `originalObject` WILL not be affected. 18 | 19 | Important: Since the method `clone()` returns a new `User` instead of a user object, ALL methods are available in the new objects. 20 | 21 | --- 22 | 23 | Depending oin the shape of you data youy might want to do a `shallow` or `deep` copy. 24 | 25 | Shallow vs Deep copy [Shallow vs Deep copy](./00_1_5_1-prototype-shallow-vs-deep-copy.md). 26 | 27 | --- 28 | 29 | 30 | ```ts 31 | interface IUser { 32 | name: string; 33 | age: number; 34 | hobbies?: string[]; 35 | } 36 | 37 | interface IClone { 38 | data: IUser; 39 | clone(): User; 40 | } 41 | 42 | class User implements IClone { 43 | 44 | data: IUser = { 45 | name: '', 46 | age: 0 47 | } 48 | 49 | constructor(data: IUser) { 50 | this.data = data; 51 | } 52 | 53 | clone(): User { 54 | let copiedObj = JSON.parse(JSON.stringify(this.data)); 55 | return new User(copiedObj); 56 | } 57 | } 58 | 59 | 60 | const originalObject = new User({ name: 'Peter', age: 33, hobbies: [ 'writing' ] }); 61 | 62 | const copiedObject = originalObject.clone(); 63 | 64 | console.log(originalObject, copiedObject); 65 | 66 | // User: { 67 | // "userObject": { 68 | // "name": "Peter", 69 | // "age": 33, 70 | // "hobbies": [ 71 | // "writing" 72 | // ] 73 | // } 74 | // }, 75 | // User: { 76 | // "userObject": { 77 | // "name": "Peter", 78 | // "age": 33, 79 | // "hobbies": [ 80 | // "writing" 81 | // ] 82 | // } 83 | // } 84 | 85 | copiedObject.data.age = 11; 86 | 87 | console.log(originalObject, copiedObject); 88 | 89 | // User: { 90 | // "data": { 91 | // "name": "Peter", 92 | // "age": 33, 93 | // "hobbies": [ 94 | // "writing" 95 | // ] 96 | // } 97 | // }, 98 | // User: { 99 | // "data": { 100 | // "name": "Peter", 101 | // "age": 11, 102 | // "hobbies": [ 103 | // "writing" 104 | // ] 105 | // } 106 | // } 107 | ``` 108 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Design Patterns --------------------------------------------------------------------------------