├── .vscode └── tasks.json ├── README.md ├── dist ├── app.js ├── examples │ └── ChainOfResponsibility.js └── system.js ├── index.html ├── node_modules └── .yarn-integrity ├── package.json ├── src └── app.ts ├── tsconfig.json └── yarn.lock /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "0.1.0", 5 | "command": "tsc", 6 | "isShellCommand": true, 7 | "args": ["-p", "."], 8 | "showOutput": "silent", 9 | "problemMatcher": "$tsc" 10 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Design Patterns in Typescript 2 | 3 | 4 | ### Observer / Listener 5 | The observer pattern is used to allow an object to publish changes to its state. Other objects subscribe to be immediately notified of any changes. 6 | 7 | ```typescript 8 | interface TextChangedListener { 9 | onTextChanged(newText: string): void 10 | } 11 | 12 | class PrintingTextChangedListener implements TextChangedListener { 13 | onTextChanged(newText: string) { 14 | console.log(`Text is changed to: ${newText}`); 15 | } 16 | } 17 | 18 | class TextView { 19 | listener?: TextChangedListener = null 20 | set text(newText: string) { 21 | this.listener.onTextChanged(newText); 22 | } 23 | } 24 | 25 | // usage 26 | const textView = new TextView(); 27 | textView.listener = new PrintingTextChangedListener(); 28 | textView.text = "Lorem ipsum"; 29 | textView.text = "dolor sit amet"; 30 | 31 | // output 32 | Text is changed to: Lorem ipsum 33 | Text is changed to: dolor sit amet 34 | ``` 35 | 36 | ## Strategy 37 | The strategy pattern is used to create an interchangeable family of algorithms from which the required process is chosen at run-time. 38 | 39 | ```typescript 40 | class Printer { 41 | constructor(public stringFormatterStrategy: Function) { } 42 | printString(text: string) { 43 | console.log(this.stringFormatterStrategy(text)); 44 | } 45 | } 46 | 47 | const lowerCaseFormatter = (text: string) => text.toLowerCase() 48 | const upperCaseFormatter = (text: string) => text.toUpperCase() 49 | 50 | // usage 51 | const lowerCasePrinter = new Printer(lowerCaseFormatter); 52 | lowerCasePrinter.printString("LOREM ipsum DOLOR sit amet"); 53 | 54 | const upperCasePrinter = new Printer(upperCaseFormatter); 55 | upperCasePrinter.printString("LOREM ipsum DOLOR sit amet"); 56 | 57 | const prefixPrinter = new Printer((text: string) => `Prefix: ${text}`); 58 | prefixPrinter.printString("LOREM ipsum DOLOR sit amet"); 59 | 60 | // output 61 | lorem ipsum dolor sit amet 62 | LOREM IPSUM DOLOR SIT AMET 63 | Prefix: LOREM ipsum DOLOR sit amet 64 | ``` 65 | 66 | ## Command 67 | The command pattern is used to express a request, including the call to be made and all of its required parameters, in a command object. The command may then be executed immediately or held for later use. 68 | 69 | ```typescript 70 | interface OrderCommand { 71 | execute(): void 72 | } 73 | 74 | class OrderAddCommand implements OrderCommand { 75 | constructor(public id: number) { } 76 | execute() { 77 | console.log(`adding order with id: ${this.id}`); 78 | } 79 | } 80 | 81 | class OrderPayCommand implements OrderCommand { 82 | constructor(public id: number) { } 83 | execute() { 84 | console.log(`paying for order with id: ${this.id}`); 85 | } 86 | } 87 | 88 | class CommandProcessor { 89 | 90 | private queue = Array(); 91 | 92 | addToQueue(orderCommand: OrderCommand): CommandProcessor { 93 | this.queue.push(orderCommand); 94 | return this; 95 | } 96 | 97 | processCommands(): CommandProcessor { 98 | this.queue.forEach((commad: OrderCommand) => { 99 | commad.execute(); 100 | }); 101 | this.queue = Array(); 102 | return this; 103 | } 104 | } 105 | 106 | // usage 107 | new CommandProcessor() 108 | .addToQueue(new OrderAddCommand(1)) 109 | .addToQueue(new OrderAddCommand(2)) 110 | .addToQueue(new OrderPayCommand(2)) 111 | .addToQueue(new OrderPayCommand(1)) 112 | .processCommands(); 113 | 114 | 115 | // output 116 | adding order with id: 1 117 | adding order with id: 2 118 | paying for order with id: 2 119 | paying for order with id: 1 120 | ``` 121 | 122 | ## State 123 | The state pattern is used to alter the behaviour of an object as its internal state changes. The pattern allows the class for an object to apparently change at run-time. 124 | 125 | ```typescript 126 | module State { 127 | export class Authorization { 128 | 129 | } 130 | export class Unauthorized extends Authorization { 131 | 132 | } 133 | export class Authorized extends Authorization { 134 | constructor(public username: string) { 135 | super(); 136 | } 137 | } 138 | } 139 | 140 | class AuthorizationPresenter { 141 | 142 | private state: State.Authorization = new State.Unauthorized() 143 | 144 | loginUser(userLogin: string) { 145 | this.state = new State.Authorized(userLogin); 146 | } 147 | 148 | logoutUser() { 149 | this.state = new State.Unauthorized(); 150 | } 151 | 152 | get isAuthorized() { 153 | switch (this.state.constructor.name) { 154 | case 'Authorized': 155 | return true 156 | default: 157 | return false 158 | } 159 | } 160 | 161 | get userLogin() { 162 | switch (this.state.constructor.name) { 163 | case 'Authorized': 164 | return (this.state as State.Authorized).username; 165 | default: 166 | return 'Unknown' 167 | } 168 | } 169 | 170 | toString(): string { 171 | return `User '${this.userLogin}' is logged in: ${this.isAuthorized}`; 172 | } 173 | } 174 | 175 | // usage 176 | const authorization = new AuthorizationPresenter(); 177 | authorization.loginUser("admin"); 178 | console.log(authorization.toString()); 179 | authorization.logoutUser(); 180 | console.log(authorization.toString()); 181 | 182 | // output 183 | User 'admin' is logged in: true 184 | User 'Unknown' is logged in: false 185 | ``` 186 | 187 | ## Adapter 188 | The adapter pattern is used to provide a link between two otherwise incompatible types by wrapping the "adaptee" with a class that supports the interface required by the client. 189 | 190 | ```typescript 191 | interface Temperature { 192 | temperature: number 193 | } 194 | 195 | class CelsiusTemperature implements Temperature { 196 | constructor(public temperature: number) {} 197 | } 198 | 199 | class FahrenheitTemperature implements Temperature { 200 | 201 | constructor(public celsiusTemperature: CelsiusTemperature) {} 202 | 203 | get temperature(): number { 204 | return this.convertCelsiusToFahrenheit(this.celsiusTemperature.temperature); 205 | } 206 | set temperature(temperatureInF) { 207 | this.celsiusTemperature.temperature = this.convertFahrenheitToCelsius(temperatureInF); 208 | } 209 | 210 | private convertFahrenheitToCelsius(f: number): number { 211 | return (f - 32) * 5 / 9; 212 | } 213 | 214 | private convertCelsiusToFahrenheit(c: number): number { 215 | return (c * 9 / 5) + 32; 216 | } 217 | } 218 | 219 | // usage 220 | const celsiusTemperature = new CelsiusTemperature(0.0); 221 | const fahrenheitTemperature = new FahrenheitTemperature(celsiusTemperature); 222 | 223 | celsiusTemperature.temperature = 36.6; 224 | console.log(`${celsiusTemperature.temperature} C -> ${fahrenheitTemperature.temperature} F`); 225 | 226 | fahrenheitTemperature.temperature = 100.0; 227 | console.log(`${fahrenheitTemperature.temperature} F -> ${celsiusTemperature.temperature} C`); 228 | 229 | // output 230 | 36.6 C -> 97.88000000000001 F 231 | 100 F -> 37.77777777777778 C 232 | ``` 233 | 234 | ## Decorator 235 | The decorator pattern is used to extend or alter the functionality of objects at run-time by wrapping them in an object of a decorator class. This provides a flexible alternative to using inheritance to modify behaviour. 236 | 237 | ```typescript 238 | interface CoffeeMachine { 239 | makeSmallCoffee(): void 240 | makeLargeCoffee(): void 241 | } 242 | 243 | class NormalCoffeeMachine implements CoffeeMachine { 244 | makeSmallCoffee() { 245 | console.log("Normal: Making small coffee"); 246 | } 247 | makeLargeCoffee() { 248 | console.log("Normal: Making large coffee"); 249 | } 250 | } 251 | 252 | class EnhancedCoffeeMachine { 253 | 254 | constructor(public coffeeMachine: CoffeeMachine) {} 255 | 256 | makeCoffeeWithMilk() { 257 | console.log("Enhanced: Making coffee with milk"); 258 | this.coffeeMachine.makeSmallCoffee(); 259 | console.log("Enhanced: Adding milk"); 260 | } 261 | 262 | makeDoubleLargeCoffee() { 263 | console.log("Enhanced: Making double large coffee"); 264 | this.coffeeMachine.makeLargeCoffee(); 265 | this.coffeeMachine.makeLargeCoffee(); 266 | } 267 | } 268 | 269 | // usage 270 | const normalMachine = new NormalCoffeeMachine(); 271 | const enhancedMachine = new EnhancedCoffeeMachine(normalMachine); 272 | 273 | enhancedMachine.makeCoffeeWithMilk(); 274 | enhancedMachine.makeDoubleLargeCoffee(); 275 | 276 | // output 277 | Enhanced: Making coffee with milk 278 | Normal: Making small coffee 279 | Enhanced: Adding milk 280 | Enhanced: Making double large coffee 281 | Normal: Making large coffee 282 | Normal: Making large coffee 283 | ``` 284 | 285 | ##Facade 286 | The facade pattern is used to define a simplified interface to a more complex subsystem. 287 | 288 | ```typescript 289 | class ComplexSystemStore { 290 | 291 | private _store = new Map() 292 | 293 | constructor(public filePath: string) { 294 | console.log(`Reading data from file: ${this.filePath}`); 295 | } 296 | 297 | store(key: string, payload: string) { 298 | this._store.set(key, payload); 299 | } 300 | 301 | read(key: string): string { 302 | return this._store.has(key) ? this._store.get(key) : ''; 303 | } 304 | 305 | commit() { 306 | const keys = Array.from(this._store.keys()); 307 | console.log(`Storing cached data: ${keys} to file: ${this.filePath}`); 308 | } 309 | } 310 | 311 | class User { 312 | constructor(public login: string) {} 313 | } 314 | 315 | //Facade: 316 | class UserRepository { 317 | systemPreferences = new ComplexSystemStore("/data/default.prefs"); 318 | 319 | save(user: User) { 320 | this.systemPreferences.store("USER_KEY", user.login); 321 | this.systemPreferences.commit(); 322 | } 323 | 324 | findFirst(): User { 325 | return new User(this.systemPreferences.read("USER_KEY")); 326 | } 327 | } 328 | 329 | // usage 330 | const userRepository = new UserRepository(); 331 | const user = new User("dbacinski"); 332 | userRepository.save(user); 333 | const resultUser = userRepository.findFirst(); 334 | console.log(`Found stored user: ${resultUser.login}`); 335 | 336 | // output 337 | Reading data from file: /data/default.prefs 338 | Storing cached data: USER_KEY to file: /data/default.prefs 339 | Found stored user: dbacinski 340 | ``` 341 | -------------------------------------------------------------------------------- /dist/app.js: -------------------------------------------------------------------------------- 1 | var __extends = (this && this.__extends) || (function () { 2 | var extendStatics = Object.setPrototypeOf || 3 | ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || 4 | function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; 5 | return function (d, b) { 6 | extendStatics(d, b); 7 | function __() { this.constructor = d; } 8 | d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); 9 | }; 10 | })(); 11 | console.log('### Observer / Listener ###'); 12 | var PrintingTextChangedListener = (function () { 13 | function PrintingTextChangedListener() { 14 | } 15 | PrintingTextChangedListener.prototype.onTextChanged = function (newText) { 16 | console.log("Text is changed to: " + newText); 17 | }; 18 | return PrintingTextChangedListener; 19 | }()); 20 | var TextView = (function () { 21 | function TextView() { 22 | this.listener = null; 23 | } 24 | Object.defineProperty(TextView.prototype, "text", { 25 | set: function (newText) { 26 | this.listener.onTextChanged(newText); 27 | }, 28 | enumerable: true, 29 | configurable: true 30 | }); 31 | return TextView; 32 | }()); 33 | var textView = new TextView(); 34 | textView.listener = new PrintingTextChangedListener(); 35 | textView.text = "Lorem ipsum"; 36 | textView.text = "dolor sit amet"; 37 | console.log('### Strategy ###'); 38 | var Printer = (function () { 39 | function Printer(stringFormatterStrategy) { 40 | this.stringFormatterStrategy = stringFormatterStrategy; 41 | } 42 | Printer.prototype.printString = function (text) { 43 | console.log(this.stringFormatterStrategy(text)); 44 | }; 45 | return Printer; 46 | }()); 47 | var lowerCaseFormatter = function (text) { return text.toLowerCase(); }; 48 | var upperCaseFormatter = function (text) { return text.toUpperCase(); }; 49 | var lowerCasePrinter = new Printer(lowerCaseFormatter); 50 | lowerCasePrinter.printString("LOREM ipsum DOLOR sit amet"); 51 | var upperCasePrinter = new Printer(upperCaseFormatter); 52 | upperCasePrinter.printString("LOREM ipsum DOLOR sit amet"); 53 | var prefixPrinter = new Printer(function (text) { return "Prefix: " + text; }); 54 | prefixPrinter.printString("LOREM ipsum DOLOR sit amet"); 55 | console.log('### Command ###'); 56 | var OrderAddCommand = (function () { 57 | function OrderAddCommand(id) { 58 | this.id = id; 59 | } 60 | OrderAddCommand.prototype.execute = function () { 61 | console.log("adding order with id: " + this.id); 62 | }; 63 | return OrderAddCommand; 64 | }()); 65 | var OrderPayCommand = (function () { 66 | function OrderPayCommand(id) { 67 | this.id = id; 68 | } 69 | OrderPayCommand.prototype.execute = function () { 70 | console.log("paying for order with id: " + this.id); 71 | }; 72 | return OrderPayCommand; 73 | }()); 74 | var CommandProcessor = (function () { 75 | function CommandProcessor() { 76 | this.queue = Array(); 77 | } 78 | CommandProcessor.prototype.addToQueue = function (orderCommand) { 79 | this.queue.push(orderCommand); 80 | return this; 81 | }; 82 | CommandProcessor.prototype.processCommands = function () { 83 | this.queue.forEach(function (commad) { 84 | commad.execute(); 85 | }); 86 | this.queue = Array(); 87 | return this; 88 | }; 89 | return CommandProcessor; 90 | }()); 91 | new CommandProcessor() 92 | .addToQueue(new OrderAddCommand(1)) 93 | .addToQueue(new OrderAddCommand(2)) 94 | .addToQueue(new OrderPayCommand(2)) 95 | .addToQueue(new OrderPayCommand(1)) 96 | .processCommands(); 97 | console.log('### State ###'); 98 | var State; 99 | (function (State) { 100 | var Authorization = (function () { 101 | function Authorization() { 102 | } 103 | return Authorization; 104 | }()); 105 | State.Authorization = Authorization; 106 | var Unauthorized = (function (_super) { 107 | __extends(Unauthorized, _super); 108 | function Unauthorized() { 109 | return _super !== null && _super.apply(this, arguments) || this; 110 | } 111 | return Unauthorized; 112 | }(Authorization)); 113 | State.Unauthorized = Unauthorized; 114 | var Authorized = (function (_super) { 115 | __extends(Authorized, _super); 116 | function Authorized(username) { 117 | var _this = _super.call(this) || this; 118 | _this.username = username; 119 | return _this; 120 | } 121 | return Authorized; 122 | }(Authorization)); 123 | State.Authorized = Authorized; 124 | })(State || (State = {})); 125 | var AuthorizationPresenter = (function () { 126 | function AuthorizationPresenter() { 127 | this.state = new State.Unauthorized(); 128 | } 129 | AuthorizationPresenter.prototype.loginUser = function (userLogin) { 130 | this.state = new State.Authorized(userLogin); 131 | }; 132 | AuthorizationPresenter.prototype.logoutUser = function () { 133 | this.state = new State.Unauthorized(); 134 | }; 135 | Object.defineProperty(AuthorizationPresenter.prototype, "isAuthorized", { 136 | get: function () { 137 | switch (this.state.constructor.name) { 138 | case 'Authorized': 139 | return true; 140 | default: 141 | return false; 142 | } 143 | }, 144 | enumerable: true, 145 | configurable: true 146 | }); 147 | Object.defineProperty(AuthorizationPresenter.prototype, "userLogin", { 148 | get: function () { 149 | switch (this.state.constructor.name) { 150 | case 'Authorized': 151 | return this.state.username; 152 | default: 153 | return 'Unknown'; 154 | } 155 | }, 156 | enumerable: true, 157 | configurable: true 158 | }); 159 | AuthorizationPresenter.prototype.toString = function () { 160 | return "User '" + this.userLogin + "' is logged in: " + this.isAuthorized; 161 | }; 162 | return AuthorizationPresenter; 163 | }()); 164 | var authorization = new AuthorizationPresenter(); 165 | authorization.loginUser("admin"); 166 | console.log(authorization.toString()); 167 | authorization.logoutUser(); 168 | console.log(authorization.toString()); 169 | console.log('### Adapter ###'); 170 | var CelsiusTemperature = (function () { 171 | function CelsiusTemperature(temperature) { 172 | this.temperature = temperature; 173 | } 174 | return CelsiusTemperature; 175 | }()); 176 | var FahrenheitTemperature = (function () { 177 | function FahrenheitTemperature(celsiusTemperature) { 178 | this.celsiusTemperature = celsiusTemperature; 179 | } 180 | Object.defineProperty(FahrenheitTemperature.prototype, "temperature", { 181 | get: function () { 182 | return this.convertCelsiusToFahrenheit(this.celsiusTemperature.temperature); 183 | }, 184 | set: function (temperatureInF) { 185 | this.celsiusTemperature.temperature = this.convertFahrenheitToCelsius(temperatureInF); 186 | }, 187 | enumerable: true, 188 | configurable: true 189 | }); 190 | FahrenheitTemperature.prototype.convertFahrenheitToCelsius = function (f) { 191 | return (f - 32) * 5 / 9; 192 | }; 193 | FahrenheitTemperature.prototype.convertCelsiusToFahrenheit = function (c) { 194 | return (c * 9 / 5) + 32; 195 | }; 196 | return FahrenheitTemperature; 197 | }()); 198 | var celsiusTemperature = new CelsiusTemperature(0.0); 199 | var fahrenheitTemperature = new FahrenheitTemperature(celsiusTemperature); 200 | celsiusTemperature.temperature = 36.6; 201 | console.log(celsiusTemperature.temperature + " C -> " + fahrenheitTemperature.temperature + " F"); 202 | fahrenheitTemperature.temperature = 100.0; 203 | console.log(fahrenheitTemperature.temperature + " F -> " + celsiusTemperature.temperature + " C"); 204 | console.log('### Decorator ###'); 205 | var NormalCoffeeMachine = (function () { 206 | function NormalCoffeeMachine() { 207 | } 208 | NormalCoffeeMachine.prototype.makeSmallCoffee = function () { 209 | console.log("Normal: Making small coffee"); 210 | }; 211 | NormalCoffeeMachine.prototype.makeLargeCoffee = function () { 212 | console.log("Normal: Making large coffee"); 213 | }; 214 | return NormalCoffeeMachine; 215 | }()); 216 | var EnhancedCoffeeMachine = (function () { 217 | function EnhancedCoffeeMachine(coffeeMachine) { 218 | this.coffeeMachine = coffeeMachine; 219 | } 220 | EnhancedCoffeeMachine.prototype.makeCoffeeWithMilk = function () { 221 | console.log("Enhanced: Making coffee with milk"); 222 | this.coffeeMachine.makeSmallCoffee(); 223 | console.log("Enhanced: Adding milk"); 224 | }; 225 | EnhancedCoffeeMachine.prototype.makeDoubleLargeCoffee = function () { 226 | console.log("Enhanced: Making double large coffee"); 227 | this.coffeeMachine.makeLargeCoffee(); 228 | this.coffeeMachine.makeLargeCoffee(); 229 | }; 230 | return EnhancedCoffeeMachine; 231 | }()); 232 | var normalMachine = new NormalCoffeeMachine(); 233 | var enhancedMachine = new EnhancedCoffeeMachine(normalMachine); 234 | enhancedMachine.makeCoffeeWithMilk(); 235 | enhancedMachine.makeDoubleLargeCoffee(); 236 | console.log('### Facade ###'); 237 | var ComplexSystemStore = (function () { 238 | function ComplexSystemStore(filePath) { 239 | this.filePath = filePath; 240 | this._store = new Map(); 241 | console.log("Reading data from file: " + this.filePath); 242 | } 243 | ComplexSystemStore.prototype.store = function (key, payload) { 244 | this._store.set(key, payload); 245 | }; 246 | ComplexSystemStore.prototype.read = function (key) { 247 | return this._store.has(key) ? this._store.get(key) : ''; 248 | }; 249 | ComplexSystemStore.prototype.commit = function () { 250 | var keys = Array.from(this._store.keys()); 251 | console.log("Storing cached data: " + keys + " to file: " + this.filePath); 252 | }; 253 | return ComplexSystemStore; 254 | }()); 255 | var User = (function () { 256 | function User(login) { 257 | this.login = login; 258 | } 259 | return User; 260 | }()); 261 | var UserRepository = (function () { 262 | function UserRepository() { 263 | this.systemPreferences = new ComplexSystemStore("/data/default.prefs"); 264 | } 265 | UserRepository.prototype.save = function (user) { 266 | this.systemPreferences.store("USER_KEY", user.login); 267 | this.systemPreferences.commit(); 268 | }; 269 | UserRepository.prototype.findFirst = function () { 270 | return new User(this.systemPreferences.read("USER_KEY")); 271 | }; 272 | return UserRepository; 273 | }()); 274 | var userRepository = new UserRepository(); 275 | var user = new User("dbacinski"); 276 | userRepository.save(user); 277 | var resultUser = userRepository.findFirst(); 278 | console.log("Found stored user: " + resultUser.login); 279 | -------------------------------------------------------------------------------- /dist/examples/ChainOfResponsibility.js: -------------------------------------------------------------------------------- 1 | console.log('Chain of Responsibility'); 2 | var AuthenticationHeader = (function () { 3 | function AuthenticationHeader(token, next) { 4 | this.token = token; 5 | this.next = next; 6 | } 7 | AuthenticationHeader.prototype.addLines = function (inputHeader) { 8 | if (this.token === null) 9 | throw new Error("Token should be not null"); 10 | var text = inputHeader + " Authorization: Bearer " + this.token; 11 | if (this.next) 12 | this.next.addLines(text); 13 | return text; 14 | }; 15 | return AuthenticationHeader; 16 | }()); 17 | var ContentTypeHeader = (function () { 18 | function ContentTypeHeader(contentType, next) { 19 | this.contentType = contentType; 20 | this.next = next; 21 | } 22 | ContentTypeHeader.prototype.addLines = function (inputHeader) { 23 | var text = inputHeader + " ContentType: " + this.contentType; 24 | if (this.next) 25 | this.next.addLines(text); 26 | return text; 27 | }; 28 | return ContentTypeHeader; 29 | }()); 30 | var BodyPayload = (function () { 31 | function BodyPayload(body, next) { 32 | this.body = body; 33 | this.next = next; 34 | } 35 | BodyPayload.prototype.addLines = function (inputHeader) { 36 | var text = inputHeader + " " + this.body; 37 | if (this.next) 38 | this.next.addLines(text); 39 | return text; 40 | }; 41 | return BodyPayload; 42 | }()); 43 | var authenticationHeader = new AuthenticationHeader("123456"); 44 | var contentTypeHeader = new ContentTypeHeader("json"); 45 | var messageBody = new BodyPayload('{"username"="dbacinski"}'); 46 | var messageChainWithAuthorization = function (authenticationHeader, contentTypeHeader, messageBody) { 47 | authenticationHeader.next = contentTypeHeader; 48 | contentTypeHeader.next = messageBody; 49 | return authenticationHeader; 50 | }; 51 | var _messageChainWithAuthorization = messageChainWithAuthorization(authenticationHeader, contentTypeHeader, messageBody); 52 | var _messageWithAuthentication = _messageChainWithAuthorization.addLines("Message with Authentication:\n"); 53 | console.log(_messageWithAuthentication); 54 | -------------------------------------------------------------------------------- /dist/system.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SystemJS v0.19.41 3 | */ 4 | !function(){function e(){!function(e){function t(e,r){if("string"!=typeof e)throw new TypeError("URL must be a string");var n=String(e).replace(/^\s+|\s+$/g,"").match(/^([^:\/?#]+:)?(?:\/\/(?:([^:@\/?#]*)(?::([^:@\/?#]*))?@)?(([^:\/?#]*)(?::(\d*))?))?([^?#]*)(\?[^#]*)?(#[\s\S]*)?/);if(!n)throw new RangeError("Invalid URL format");var a=n[1]||"",o=n[2]||"",i=n[3]||"",s=n[4]||"",l=n[5]||"",u=n[6]||"",d=n[7]||"",c=n[8]||"",f=n[9]||"";if(void 0!==r){var m=r instanceof t?r:new t(r),p=!a&&!s&&!o;!p||d||c||(c=m.search),p&&"/"!==d[0]&&(d=d?(!m.host&&!m.username||m.pathname?"":"/")+m.pathname.slice(0,m.pathname.lastIndexOf("/")+1)+d:m.pathname);var h=[];d.replace(/^(\.\.?(\/|$))+/,"").replace(/\/(\.(\/|$))+/g,"/").replace(/\/\.\.$/,"/../").replace(/\/?[^\/]*/g,function(e){"/.."===e?h.pop():h.push(e)}),d=h.join("").replace(/^\//,"/"===d[0]?"/":""),p&&(u=m.port,l=m.hostname,s=m.host,i=m.password,o=m.username),a||(a=m.protocol)}d=d.replace(/\\/g,"/"),this.origin=s?a+(""!==a||""!==s?"//":"")+s:"",this.href=a+(a&&s||"file:"==a?"//":"")+(""!==o?o+(""!==i?":"+i:"")+"@":"")+s+d+c+f,this.protocol=a,this.username=o,this.password=i,this.host=s,this.hostname=l,this.port=u,this.pathname=d,this.search=c,this.hash=f}e.URLPolyfill=t}("undefined"!=typeof self?self:global),function(e){function t(e,t){if(!e.originalErr)for(var r=((e.message||e)+(e.stack?"\n"+e.stack:"")).toString().split("\n"),n=[],a=0;as.length?(o[s]&&"/"||"")+t.substr(s.length):"")}else{var d=s.split("*");if(d.length>2)throw new TypeError("Only one wildcard in a path is permitted");var f=d[0].length;f>=a&&t.substr(0,d[0].length)==d[0]&&t.substr(t.length-d[1].length)==d[1]&&(a=f,n=s,r=t.substr(d[0].length,t.length-d[1].length-d[0].length))}}var m=o[n];return"string"==typeof r&&(m=m.replace("*",r)),m}function m(e){for(var t=[],r=[],n=0,a=e.length;n",linkSets:[],dependencies:[],metadata:{}}}function a(e,t,r){return new Promise(u({step:r.address?"fetch":"locate",loader:e,moduleName:t,moduleMetadata:r&&r.metadata||{},moduleSource:r.source,moduleAddress:r.address}))}function o(t,r,n,a){return new Promise(function(e,o){e(t.loaderObj.normalize(r,n,a))}).then(function(r){var n;if(t.modules[r])return n=e(r),n.status="linked",n.module=t.modules[r],n;for(var a=0,o=t.loads.length;a0)){var r=e.startingLoad;if(e.loader.loaderObj.execute===!1){for(var n=[].concat(e.loads),a=0,o=n.length;a "'+n.paths[o]+'" uses wildcards which are being deprecated for just leaving a trailing "/" to indicate folder paths.')}if(e.defaultJSExtensions&&(n.defaultJSExtensions=e.defaultJSExtensions,w.call(n,"The defaultJSExtensions configuration option is deprecated, use packages configuration instead.")),e.pluginFirst&&(n.pluginFirst=e.pluginFirst),e.map)for(var o in e.map){var i=e.map[o];if("string"!=typeof i){var s=n.defaultJSExtensions&&".js"!=o.substr(o.length-3,3),l=n.decanonicalize(o);s&&".js"==l.substr(l.length-3,3)&&(l=l.substr(0,l.length-3));var u="";for(var c in n.packages)l.substr(0,c.length)==c&&(!l[c.length]||"/"==l[c.length])&&u.split("/").lengtha&&(r=o,a=n));return r}function t(e,t,r,n,a){if(!n||"/"==n[n.length-1]||a||t.defaultExtension===!1)return n;var o=!1;if(t.meta&&p(t.meta,n,function(e,t,r){if(0==r||e.lastIndexOf("*")!=e.length-1)return o=!0}),!o&&e.meta&&p(e.meta,r+"/"+n,function(e,t,r){if(0==r||e.lastIndexOf("*")!=e.length-1)return o=!0}),o)return n;var i="."+(t.defaultExtension||"js");return n.substr(n.length-i.length)!=i?n+i:n}function r(e,r,n,a,i){if(!a){if(!r.main)return n+(e.defaultJSExtensions?".js":"");a="./"==r.main.substr(0,2)?r.main.substr(2):r.main}if(r.map){var s="./"+a,l=S(r.map,s);if(l||(s="./"+t(e,r,n,a,i),s!="./"+a&&(l=S(r.map,s))),l){var u=o(e,r,n,l,s,i);if(u)return u}}return n+"/"+t(e,r,n,a,i)}function n(e,t,r,n){if("."==e)throw new Error("Package "+r+' has a map entry for "." which is not permitted.');return!(t.substr(0,e.length)==e&&n.length>e.length)}function o(e,r,a,o,i,s){"/"==i[i.length-1]&&(i=i.substr(0,i.length-1));var l=r.map[o];if("object"==typeof l)throw new Error("Synchronous conditional normalization not supported sync normalizing "+o+" in "+a);if(n(o,l,a,i)&&"string"==typeof l){if("."==l)l=a;else if("./"==l.substr(0,2))return a+"/"+t(e,r,a,l.substr(2)+i.substr(o.length),s);return e.normalizeSync(l+i.substr(o.length),a+"/")}}function l(e,r,n,a,o){if(!a){if(!r.main)return Promise.resolve(n+(e.defaultJSExtensions?".js":""));a="./"==r.main.substr(0,2)?r.main.substr(2):r.main}var i,s;return r.map&&(i="./"+a,s=S(r.map,i),s||(i="./"+t(e,r,n,a,o),i!="./"+a&&(s=S(r.map,i)))),(s?d(e,r,n,s,i,o):Promise.resolve()).then(function(i){return i?Promise.resolve(i):Promise.resolve(n+"/"+t(e,r,n,a,o))})}function u(e,r,n,a,o,i,s){if("."==o)o=n;else if("./"==o.substr(0,2))return Promise.resolve(n+"/"+t(e,r,n,o.substr(2)+i.substr(a.length),s)).then(function(t){return C.call(e,t,n+"/")});return e.normalize(o+i.substr(a.length),n+"/")}function d(e,t,r,a,o,i){"/"==o[o.length-1]&&(o=o.substr(0,o.length-1));var s=t.map[a];if("string"==typeof s)return n(a,s,r,o)?u(e,t,r,a,s,o,i):Promise.resolve();if(e.builder)return Promise.resolve(r+"/#:"+o);var l=[],d=[];for(var c in s){var f=z(c);d.push({condition:f,map:s[c]}),l.push(e.import(f.module,r))}return Promise.all(l).then(function(e){for(var t=0;tl&&(l=r),v(s,t,r&&l>r)}),v(r.metadata,s)}o.format&&!r.metadata.loader&&(r.metadata.format=r.metadata.format||o.format)}return t})}})}(),function(){function t(){if(s&&"interactive"===s.script.readyState)return s.load;for(var e=0;e=0;i--){for(var s=a[i],l=0;l100&&!i.metadata.format&&(i.metadata.format="global","traceur"===s.transpiler&&(i.metadata.exports="traceur"),"typescript"===s.transpiler&&(i.metadata.exports="ts")),s._loader.loadedTranspiler=!0),s._loader.loadedTranspilerRuntime===!1&&(i.name!=s.normalizeSync("traceur-runtime")&&i.name!=s.normalizeSync("babel/external-helpers*")||(o.length>100&&(i.metadata.format=i.metadata.format||"global"),s._loader.loadedTranspilerRuntime=!0)),("register"==i.metadata.format||i.metadata.bundle)&&s._loader.loadedTranspilerRuntime!==!0){if("traceur"==s.transpiler&&!e.$traceurRuntime&&i.source.match(n))return s._loader.loadedTranspilerRuntime=s._loader.loadedTranspilerRuntime||!1,s.import("traceur-runtime").then(function(){return o});if("babel"==s.transpiler&&!e.babelHelpers&&i.source.match(a))return s._loader.loadedTranspilerRuntime=s._loader.loadedTranspilerRuntime||!1,s.import("babel/external-helpers").then(function(){return o})}return o})}})}();var ie="undefined"!=typeof self?"self":"global";i("fetch",function(e){return function(t){return t.metadata.exports&&!t.metadata.format&&(t.metadata.format="global"),e.call(this,t)}}),i("instantiate",function(e){return function(t){var r=this;if(t.metadata.format||(t.metadata.format="global"),"global"==t.metadata.format&&!t.metadata.entry){var n=k();t.metadata.entry=n,n.deps=[];for(var a in t.metadata.globals){var o=t.metadata.globals[a];o&&n.deps.push(o)}n.execute=function(e,n,a){var o;if(t.metadata.globals){o={};for(var i in t.metadata.globals)t.metadata.globals[i]&&(o[i]=e(t.metadata.globals[i]))}var s=t.metadata.exports;s&&(t.source+="\n"+ie+'["'+s+'"] = '+s+";");var l=r.get("@@global-helpers").prepareGlobal(a.id,s,o,!!t.metadata.encapsulateGlobal);return ee.call(r,t),l()}}return e.call(this,t)}}),i("reduceRegister_",function(e){return function(t,r){if(r||!t.metadata.exports&&(!A||"global"!=t.metadata.format))return e.call(this,t,r);t.metadata.format="global";var n=t.metadata.entry=k();n.deps=t.metadata.deps;var a=R(t.metadata.exports);n.execute=function(){return a}}}),s(function(t){return function(){function r(t){if(Object.keys)Object.keys(e).forEach(t);else for(var r in e)i.call(e,r)&&t(r)}function n(t){r(function(r){if(U.call(s,r)==-1){try{var n=e[r]}catch(e){s.push(r)}t(r,n)}})}var a=this;t.call(a);var o,i=Object.prototype.hasOwnProperty,s=["_g","sessionStorage","localStorage","clipboardData","frames","frameElement","external","mozAnimationStartTime","webkitStorageInfo","webkitIndexedDB","mozInnerScreenY","mozInnerScreenX"];a.set("@@global-helpers",a.newModule({prepareGlobal:function(t,r,a,i){var s=e.define;e.define=void 0;var l;if(a){l={};for(var u in a)l[u]=e[u],e[u]=a[u]}return r||(o={},n(function(e,t){o[e]=t})),function(){var t,a=r?R(r):{},u=!!r;if(r&&!i||n(function(n,s){o[n]!==s&&"undefined"!=typeof s&&(i&&(e[n]=void 0),r||(a[n]=s,"undefined"!=typeof t?u||t===s||(u=!0):t=s))}),a=u?a:t,l)for(var d in l)e[d]=l[d];return e.define=s,a}}}))}}),function(){function t(e){function t(e,t){for(var r=0;rt.index)return!0;return!1}n.lastIndex=a.lastIndex=o.lastIndex=0;var r,i=[],s=[],l=[];if(e.length/e.split("\n").length<200){for(;r=o.exec(e);)s.push([r.index,r.index+r[0].length]);for(;r=a.exec(e);)t(s,r)||l.push([r.index+r[1].length,r.index+r[0].length-1])}for(;r=n.exec(e);)if(!t(s,r)&&!t(l,r)){var u=r[1].substr(1,r[1].length-2);if(u.match(/"|'/))continue;"/"==u[u.length-1]&&(u=u.substr(0,u.length-1)),i.push(u)}return i}var r=/(?:^\uFEFF?|[^$_a-zA-Z\xA0-\uFFFF.])(exports\s*(\[['"]|\.)|module(\.exports|\['exports'\]|\["exports"\])\s*(\[['"]|[=,\.]))/,n=/(?:^\uFEFF?|[^$_a-zA-Z\xA0-\uFFFF."'])require\s*\(\s*("[^"\\]*(?:\\.[^"\\]*)*"|'[^'\\]*(?:\\.[^'\\]*)*')\s*\)/g,a=/(^|[^\\])(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/gm,o=/("[^"\\\n\r]*(\\.[^"\\\n\r]*)*"|'[^'\\\n\r]*(\\.[^'\\\n\r]*)*')/g,s=/^\#\!.*/;i("instantiate",function(a){return function(o){var i=this;if(o.metadata.format||(r.lastIndex=0,n.lastIndex=0,(n.exec(o.source)||r.exec(o.source))&&(o.metadata.format="cjs")),"cjs"==o.metadata.format){var l=o.metadata.deps,u=o.metadata.cjsRequireDetection===!1?[]:t(o.source);for(var d in o.metadata.globals)o.metadata.globals[d]&&u.push(o.metadata.globals[d]);var c=k();o.metadata.entry=c,c.deps=u,c.executingRequire=!0,c.execute=function(t,r,n){function a(e){return"/"==e[e.length-1]&&(e=e.substr(0,e.length-1)),t.apply(this,arguments)}if(a.resolve=function(e){return i.get("@@cjs-helpers").requireResolve(e,n.id)},n.paths=[],n.require=t,!o.metadata.cjsDeferDepsExecute)for(var u=0;u1;)n=a.shift(),e=e[n]=e[n]||{};n=a.shift(),n in e||(e[n]=r)}s(function(e){return function(){this.meta={},e.call(this)}}),i("locate",function(e){return function(t){var r,n=this.meta,a=t.name,o=0;for(var i in n)if(r=i.indexOf("*"),r!==-1&&i.substr(0,r)===a.substr(0,r)&&i.substr(r+1)===a.substr(a.length-i.length+r+1)){var s=i.split("/").length;s>o&&(o=s),v(t.metadata,n[i],o!=s)}return n[a]&&v(t.metadata,n[a]),e.call(this,t)}});var t=/^(\s*\/\*[^\*]*(\*(?!\/)[^\*]*)*\*\/|\s*\/\/[^\n]*|\s*"[^"]+"\s*;?|\s*'[^']+'\s*;?)+/,r=/\/\*[^\*]*(\*(?!\/)[^\*]*)*\*\/|\/\/[^\n]*|"[^"]+"\s*;?|'[^']+'\s*;?/g;i("translate",function(n){return function(a){if("defined"==a.metadata.format)return a.metadata.deps=a.metadata.deps||[],Promise.resolve(a.source);var o=a.source.match(t);if(o)for(var i=o[0].match(r),s=0;s')}else e()}else if("undefined"!=typeof importScripts){var a="";try{throw new Error("_")}catch(e){e.stack.replace(/(?:at|@).*(http.+):[\d]+:[\d]+/,function(e,t){$__curScript={src:t},a=t.replace(/\/[^\/]*$/,"/")})}t&&importScripts(a+"system-polyfills.js"),e()}else $__curScript="undefined"!=typeof __filename?{src:__filename}:null,e()}(); 6 | //# sourceMappingURL=system.js.map 7 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /node_modules/.yarn-integrity: -------------------------------------------------------------------------------- 1 | { 2 | "flags": [], 3 | "linkedModules": [], 4 | "topLevelPatters": [ 5 | "immutable@^3.8.1", 6 | "rxjs@^5.4.0" 7 | ], 8 | "lockfileEntries": { 9 | "immutable@^3.8.1": "https://registry.yarnpkg.com/immutable/-/immutable-3.8.1.tgz#200807f11ab0f72710ea485542de088075f68cd2", 10 | "rxjs@^5.4.0": "https://registry.yarnpkg.com/rxjs/-/rxjs-5.4.0.tgz#a7db14ab157f9d7aac6a56e655e7a3860d39bf26", 11 | "symbol-observable@^1.0.1": "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.4.tgz#29bf615d4aa7121bdd898b22d4b3f9bc4e2aa03d" 12 | }, 13 | "files": [], 14 | "artifacts": {} 15 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ts-design-patterns", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT" 6 | } 7 | -------------------------------------------------------------------------------- /src/app.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Observer / Listener 3 | * The observer pattern is used to allow an object to publish changes to its state. Other objects subscribe to be immediately notified of any changes. 4 | */ 5 | console.log('### Observer / Listener ###'); 6 | 7 | // implement 8 | interface TextChangedListener { 9 | onTextChanged(newText: string): void 10 | } 11 | 12 | class PrintingTextChangedListener implements TextChangedListener { 13 | onTextChanged(newText: string) { 14 | console.log(`Text is changed to: ${newText}`); 15 | } 16 | } 17 | 18 | class TextView { 19 | listener?: TextChangedListener = null 20 | set text(newText: string) { 21 | this.listener.onTextChanged(newText); 22 | } 23 | } 24 | 25 | // usage 26 | const textView = new TextView(); 27 | textView.listener = new PrintingTextChangedListener(); 28 | textView.text = "Lorem ipsum"; 29 | textView.text = "dolor sit amet"; 30 | 31 | /** 32 | * Strategy 33 | * The strategy pattern is used to create an interchangeable family of algorithms from which the required process is chosen at run-time. 34 | */ 35 | console.log('### Strategy ###'); 36 | 37 | // implement 38 | class Printer { 39 | constructor(public stringFormatterStrategy: Function) { } 40 | printString(text: string) { 41 | console.log(this.stringFormatterStrategy(text)); 42 | } 43 | } 44 | 45 | const lowerCaseFormatter = (text: string) => text.toLowerCase() 46 | const upperCaseFormatter = (text: string) => text.toUpperCase() 47 | 48 | // usage 49 | const lowerCasePrinter = new Printer(lowerCaseFormatter); 50 | lowerCasePrinter.printString("LOREM ipsum DOLOR sit amet"); 51 | 52 | const upperCasePrinter = new Printer(upperCaseFormatter); 53 | upperCasePrinter.printString("LOREM ipsum DOLOR sit amet"); 54 | 55 | const prefixPrinter = new Printer((text: string) => `Prefix: ${text}`); 56 | prefixPrinter.printString("LOREM ipsum DOLOR sit amet"); 57 | 58 | /** 59 | * Command 60 | * The command pattern is used to express a request, including the call to be made and all of its required parameters, in a command object. The command may then be executed immediately or held for later use. 61 | */ 62 | console.log('### Command ###'); 63 | 64 | // implement 65 | interface OrderCommand { 66 | execute(): void 67 | } 68 | 69 | class OrderAddCommand implements OrderCommand { 70 | constructor(public id: number) { } 71 | execute() { 72 | console.log(`adding order with id: ${this.id}`); 73 | } 74 | } 75 | 76 | class OrderPayCommand implements OrderCommand { 77 | constructor(public id: number) { } 78 | execute() { 79 | console.log(`paying for order with id: ${this.id}`); 80 | } 81 | } 82 | 83 | class CommandProcessor { 84 | 85 | private queue = Array(); 86 | 87 | addToQueue(orderCommand: OrderCommand): CommandProcessor { 88 | this.queue.push(orderCommand); 89 | return this; 90 | } 91 | 92 | processCommands(): CommandProcessor { 93 | this.queue.forEach((commad: OrderCommand) => { 94 | commad.execute(); 95 | }); 96 | this.queue = Array(); 97 | return this; 98 | } 99 | } 100 | 101 | // usage 102 | new CommandProcessor() 103 | .addToQueue(new OrderAddCommand(1)) 104 | .addToQueue(new OrderAddCommand(2)) 105 | .addToQueue(new OrderPayCommand(2)) 106 | .addToQueue(new OrderPayCommand(1)) 107 | .processCommands(); 108 | 109 | /** 110 | * State 111 | * The state pattern is used to alter the behaviour of an object as its internal state changes. The pattern allows the class for an object to apparently change at run-time. 112 | */ 113 | console.log('### State ###'); 114 | 115 | // implement 116 | module State { 117 | export class Authorization { 118 | 119 | } 120 | export class Unauthorized extends Authorization { 121 | 122 | } 123 | export class Authorized extends Authorization { 124 | constructor(public username: string) { 125 | super(); 126 | } 127 | } 128 | } 129 | 130 | class AuthorizationPresenter { 131 | 132 | private state: State.Authorization = new State.Unauthorized() 133 | 134 | loginUser(userLogin: string) { 135 | this.state = new State.Authorized(userLogin); 136 | } 137 | 138 | logoutUser() { 139 | this.state = new State.Unauthorized(); 140 | } 141 | 142 | get isAuthorized() { 143 | switch (this.state.constructor.name) { 144 | case 'Authorized': 145 | return true 146 | default: 147 | return false 148 | } 149 | } 150 | 151 | get userLogin() { 152 | switch (this.state.constructor.name) { 153 | case 'Authorized': 154 | return (this.state as State.Authorized).username; 155 | default: 156 | return 'Unknown' 157 | } 158 | } 159 | 160 | toString(): string { 161 | return `User '${this.userLogin}' is logged in: ${this.isAuthorized}`; 162 | } 163 | } 164 | 165 | // usage 166 | const authorization = new AuthorizationPresenter(); 167 | authorization.loginUser("admin"); 168 | console.log(authorization.toString()); 169 | authorization.logoutUser(); 170 | console.log(authorization.toString()); 171 | 172 | /** 173 | * Adapter 174 | * The adapter pattern is used to provide a link between two otherwise incompatible types by wrapping the "adaptee" with a class that supports the interface required by the client. 175 | */ 176 | console.log('### Adapter ###'); 177 | 178 | // implement 179 | interface Temperature { 180 | temperature: number 181 | } 182 | 183 | class CelsiusTemperature implements Temperature { 184 | constructor(public temperature: number) {} 185 | } 186 | 187 | class FahrenheitTemperature implements Temperature { 188 | 189 | constructor(public celsiusTemperature: CelsiusTemperature) {} 190 | 191 | get temperature(): number { 192 | return this.convertCelsiusToFahrenheit(this.celsiusTemperature.temperature); 193 | } 194 | set temperature(temperatureInF) { 195 | this.celsiusTemperature.temperature = this.convertFahrenheitToCelsius(temperatureInF); 196 | } 197 | 198 | private convertFahrenheitToCelsius(f: number): number { 199 | return (f - 32) * 5 / 9; 200 | } 201 | 202 | private convertCelsiusToFahrenheit(c: number): number { 203 | return (c * 9 / 5) + 32; 204 | } 205 | } 206 | 207 | // usage 208 | const celsiusTemperature = new CelsiusTemperature(0.0); 209 | const fahrenheitTemperature = new FahrenheitTemperature(celsiusTemperature); 210 | 211 | celsiusTemperature.temperature = 36.6; 212 | console.log(`${celsiusTemperature.temperature} C -> ${fahrenheitTemperature.temperature} F`); 213 | 214 | fahrenheitTemperature.temperature = 100.0; 215 | console.log(`${fahrenheitTemperature.temperature} F -> ${celsiusTemperature.temperature} C`); 216 | 217 | /** 218 | * Decorator 219 | * The decorator pattern is used to extend or alter the functionality of objects at run-time by wrapping them in an object of a decorator class. This provides a flexible alternative to using inheritance to modify behaviour. 220 | */ 221 | console.log('### Decorator ###'); 222 | 223 | // implement 224 | interface CoffeeMachine { 225 | makeSmallCoffee(): void 226 | makeLargeCoffee(): void 227 | } 228 | 229 | class NormalCoffeeMachine implements CoffeeMachine { 230 | makeSmallCoffee() { 231 | console.log("Normal: Making small coffee"); 232 | } 233 | makeLargeCoffee() { 234 | console.log("Normal: Making large coffee"); 235 | } 236 | } 237 | 238 | // or @Decorator(...) ? 239 | class EnhancedCoffeeMachine { 240 | 241 | constructor(public coffeeMachine: CoffeeMachine) {} 242 | 243 | makeCoffeeWithMilk() { 244 | console.log("Enhanced: Making coffee with milk"); 245 | this.coffeeMachine.makeSmallCoffee(); 246 | console.log("Enhanced: Adding milk"); 247 | } 248 | 249 | makeDoubleLargeCoffee() { 250 | console.log("Enhanced: Making double large coffee"); 251 | this.coffeeMachine.makeLargeCoffee(); 252 | this.coffeeMachine.makeLargeCoffee(); 253 | } 254 | } 255 | 256 | // usage 257 | const normalMachine = new NormalCoffeeMachine(); 258 | const enhancedMachine = new EnhancedCoffeeMachine(normalMachine); 259 | 260 | enhancedMachine.makeCoffeeWithMilk(); 261 | enhancedMachine.makeDoubleLargeCoffee(); 262 | 263 | /** 264 | * Facade 265 | * The facade pattern is used to define a simplified interface to a more complex subsystem. 266 | */ 267 | console.log('### Facade ###'); 268 | 269 | // implement 270 | class ComplexSystemStore { 271 | 272 | private _store = new Map() 273 | 274 | constructor(public filePath: string) { 275 | console.log(`Reading data from file: ${this.filePath}`); 276 | } 277 | 278 | store(key: string, payload: string) { 279 | this._store.set(key, payload); 280 | } 281 | 282 | read(key: string): string { 283 | return this._store.has(key) ? this._store.get(key) : ''; 284 | } 285 | 286 | commit() { 287 | const keys = Array.from(this._store.keys()); 288 | console.log(`Storing cached data: ${keys} to file: ${this.filePath}`); 289 | } 290 | } 291 | 292 | class User { 293 | constructor(public login: string) {} 294 | } 295 | 296 | //Facade: 297 | class UserRepository { 298 | systemPreferences = new ComplexSystemStore("/data/default.prefs"); 299 | 300 | save(user: User) { 301 | this.systemPreferences.store("USER_KEY", user.login); 302 | this.systemPreferences.commit(); 303 | } 304 | 305 | findFirst(): User { 306 | return new User(this.systemPreferences.read("USER_KEY")); 307 | } 308 | } 309 | 310 | // usage 311 | const userRepository = new UserRepository(); 312 | const user = new User("dbacinski"); 313 | userRepository.save(user); 314 | const resultUser = userRepository.findFirst(); 315 | console.log(`Found stored user: ${resultUser.login}`); 316 | 317 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1", 3 | "compilerOptions": { 4 | "target": "es5", 5 | "module": "system", 6 | "moduleResolution": "node", 7 | "isolatedModules": false, 8 | "experimentalDecorators": true, 9 | "emitDecoratorMetadata": true, 10 | "declaration": false, 11 | "noImplicitAny": true, 12 | "removeComments": true, 13 | "noLib": false, 14 | "lib": ["dom", "es6", "es2016"], 15 | "preserveConstEnums": true, 16 | "suppressImplicitAnyIndexErrors": true, 17 | "outDir": "./dist/"//, 18 | //"outFile": "./dist/bundle.js" 19 | }, 20 | "include": [ 21 | "./src/**/*" 22 | ] 23 | } -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | immutable@^3.8.1: 6 | version "3.8.1" 7 | resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.1.tgz#200807f11ab0f72710ea485542de088075f68cd2" 8 | 9 | rxjs@^5.4.0: 10 | version "5.4.0" 11 | resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.4.0.tgz#a7db14ab157f9d7aac6a56e655e7a3860d39bf26" 12 | dependencies: 13 | symbol-observable "^1.0.1" 14 | 15 | symbol-observable@^1.0.1: 16 | version "1.0.4" 17 | resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.4.tgz#29bf615d4aa7121bdd898b22d4b3f9bc4e2aa03d" 18 | --------------------------------------------------------------------------------