├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── adapter-pattern ├── index.js └── localStorage.js ├── assets └── design-patterns.jpg ├── builder-pattern ├── Person.js ├── PersonBuilder.js └── index.js ├── chain-of-responsibility-pattern ├── Storage.js ├── Store.js ├── index.js └── inventory.json ├── command-pattern ├── commands.js ├── conductor.js └── index.js ├── composite-pattern ├── CatalogGroup.js ├── CatalogItem.js └── index.js ├── decorator-pattern ├── InventoryItem.js ├── Shopper.js └── index.js ├── factory-pattern ├── Employee.js ├── Person.js ├── Shopper.js ├── index.js └── userFactory.js ├── iterator-pattern ├── InventoryItem.js ├── Iterator.js └── index.js ├── observer-pattern ├── Mall.js ├── Shopper.js ├── Store.js └── index.js ├── prototype-pattern ├── Shopper.js ├── index.js └── scout_prototype.js ├── proxy-pattern ├── FS_Proxy.js ├── Readme.md ├── Readme.txt └── index.js ├── singleton-pattern ├── Logger.js ├── Shopper.js ├── Store.js └── index.js └── strategy-pattern ├── LogStrategy.js ├── Logger.js ├── config.json └── index.js /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | :+1::tada: First off, thanks for taking the time to contribute! :tada::+1: 4 | 5 | The following is a set of guidelines for contributing to this project and its packages, which are hosted [diegomais](https://www.linkedin.com/in/diegomais/). These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request. 6 | 7 | ## To do 8 | 9 | - [ ] **Create a Monorepo with Yarn Workspaces**: In this project we have different domains in each Design Pattern and using a monorepo with Yarn Workspaces we could separate each Design Pattern into different modules. 10 | - [ ] **Add problem solve and not just pattern**: Design Pattern is a solution that help to resolve one or more problems. It will be interesting to shown for each implementation what problem is solved. For example state machine for product where state depends on some conditions. 11 | - [ ] **Implement tests**: Testing is necessary because we all make mistakes. Some of those mistakes are unimportant, but some of them are expensive or dangerous. We need to check everything and anything we produce because things can always go wrong – _humans make mistakes all the time_. 12 | - [ ] **Implement continuous integration**: Establish a consistent and automated way to build, package, and test application and its modules. 13 | 14 | ## Styleguides 15 | 16 | ### Git Commit Messages 17 | 18 | - Use the present tense ("Add feature" not "Added feature") 19 | - Use the imperative mood ("Move cursor to..." not "Moves cursor to...") 20 | - Limit the first line to 72 characters or less 21 | - Consider starting the commit message with an applicable emoji: 22 | - :art: `:art:` when improving the format/structure of the code 23 | - :racehorse: `:racehorse:` when improving performance 24 | - :non-potable_water: `:non-potable_water:` when plugging memory leaks 25 | - :memo: `:memo:` when writing docs 26 | - :penguin: `:penguin:` when fixing something on Linux 27 | - :apple: `:apple:` when fixing something on macOS 28 | - :checkered_flag: `:checkered_flag:` when fixing something on Windows 29 | - :bug: `:bug:` when fixing a bug 30 | - :fire: `:fire:` when removing code or files 31 | - :green_heart: `:green_heart:` when fixing the CI build 32 | - :white_check_mark: `:white_check_mark:` when adding tests 33 | - :lock: `:lock:` when dealing with security 34 | - :arrow_up: `:arrow_up:` when upgrading dependencies 35 | - :arrow_down: `:arrow_down:` when downgrading dependencies 36 | - :shirt: `:shirt:` when removing linter warnings 37 | 38 | ### JavaScript Styleguide 39 | 40 | All JavaScript must adhere to [Airbnb JavaScript Style Guide() {](https://github.com/airbnb/javascript). 41 | 42 | - Prefer the object spread operator (`{...anotherObj}`) to `Object.assign()` 43 | - Inline `export`s with expressions whenever possible 44 | 45 | ```javascript 46 | // Use this: 47 | export default class ClassName {} 48 | 49 | // Instead of: 50 | class ClassName {} 51 | export default ClassName; 52 | ``` 53 | 54 | - Place requires in the following order: 55 | - Built in Node Modules (such as `path`) 56 | - Local Modules (using relative paths) 57 | - Place class properties in the following order: 58 | - Class methods and properties (methods starting with `static`) 59 | - Instance methods and properties 60 | 61 | ### Tests Styleguide 62 | 63 | - Include thoughtfully-worded, well-structured [Jest](https://jestjs.io/) tests. 64 | - Treat `describe` as a noun or situation. 65 | - Treat `it` as a statement about state or how an operation changes state. 66 | 67 | #### Example 68 | 69 | ```javascript 70 | describe 'a dog', -> 71 | it 'barks', -> 72 | # test here 73 | describe 'when the dog is happy', -> 74 | it 'wags its tail', -> 75 | # test here 76 | ``` 77 | 78 | #### Where to put test files 79 | 80 | Unit tests run against specific lines of code. So it makes sense to place them right next to that code. 81 | 82 | ``` 83 | |- /main 84 | | |- index.js 85 | | |- index.spec.js 86 | ``` 87 | 88 | Integration tests run against many lines of code in many files. There is no single place that would make sense, so it’s best to have them in a `/__tests__` directory. 89 | 90 | ``` 91 | |- /main 92 | |- /__tests__ 93 | | |- /int 94 | | | |- api.int.spec.js 95 | | |- index.js 96 | |- /supporting 97 | | |- fetch.js 98 | ``` 99 | 100 | #### How to name test files 101 | 102 | Naming every level of test `\*.spec.js` doesn’t make much sense. So include the type of test right in the name of the file. 103 | Example: `index.unit.spec.js` and `api.int.spec.js` 104 | 105 | --- 106 | 107 | Made with :blue_heart: by [Diego Mais](https://diegomais.github.io) :wave: 108 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Diego Mais 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 |

Design Patterns

2 | 3 | # Node.js: Design Patterns 4 | 5 | 13 of the most popular object-oriented design patterns applied to Node.js. 6 | 7 | ## Installation 8 | 9 | The project run with **Node.js**. [Download](https://nodejs.org/en/download/) the Node.js source code or a pre-built installer for your platform 10 | 11 | ## Usage 12 | 13 | Download or clone the repository. Each folder has a working example of a pattern. Access the chosen pattern and run `node index.js`. 14 | 15 | ### Singleton pattern 16 | 17 | > The singleton pattern is a design pattern that restricts the instantiation of a class to one object. 18 | 19 | ### Prototype pattern 20 | 21 | > Specify the kind of objects to create using a prototypical instance, and create new objects by copying this prototype. 22 | 23 | ### Factory pattern 24 | 25 | > Define an interface for creating an object, but let subclasses decide which class to instantiate. The Factory method lets a class defer instantiation it uses to subclasses. 26 | 27 | ### Builder pattern 28 | 29 | > Separate the construction of a complex object from its representation so that the same construction process can create different representations. 30 | 31 | ### Adapter pattern 32 | 33 | > Convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn’t otherwise because of incompatible interfaces. 34 | 35 | ### Proxy pattern 36 | 37 | > Provide a surrogate or placeholder for another object to control access to it. 38 | 39 | ### Composite pattern 40 | 41 | > Compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly. 42 | 43 | ### Decorator pattern 44 | 45 | > Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality. 46 | 47 | ### Chain of Responsibility pattern 48 | 49 | > Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it. 50 | 51 | ### Command pattern 52 | 53 | > Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations. 54 | 55 | ### Iterator pattern 56 | 57 | > Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation. 58 | 59 | ### Observer pattern 60 | 61 | > Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically. 62 | 63 | ### Strategy pattern 64 | 65 | > Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it. 66 | 67 | ## Contributing 68 | 69 | Please check out the [How to contribute guide](CONTRIBUTING.md) for guidelines about how to proceed. 70 | 71 | ## License 72 | 73 | This project is under MIT license. See the [LICENSE](LICENSE) file for more details. 74 | 75 | --- 76 | 77 | Made with :blue_heart: by [Diego Mais](https://diegomais.github.io) :wave: 78 | -------------------------------------------------------------------------------- /adapter-pattern/index.js: -------------------------------------------------------------------------------- 1 | var localStorage = require("./localStorage"); 2 | 3 | console.log("localStorage length: ", localStorage.length); 4 | 5 | var uid = localStorage.getItem("user_id"); 6 | 7 | console.log("user_id: ", uid); 8 | 9 | if (!uid) { 10 | console.log("User ID not found. Setting the user id and token..."); 11 | localStorage.setItem("token", "TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ"); 12 | localStorage.setItem("user_id", "12345"); 13 | } else { 14 | console.log("User ID found.", uid); 15 | console.log("clearing the User ID..."); 16 | localStorage.clear(); 17 | } 18 | -------------------------------------------------------------------------------- /adapter-pattern/localStorage.js: -------------------------------------------------------------------------------- 1 | var { writeFile, existsSync, readFileSync, unlink } = require("fs"); 2 | 3 | class LocalStorage { 4 | constructor() { 5 | if (existsSync("localStorage.json")) { 6 | console.log("Loading items from localStorage.json"); 7 | var txt = readFileSync("localStorage.json"); 8 | this.items = JSON.parse(txt); 9 | } else { 10 | this.items = {}; 11 | } 12 | } 13 | 14 | get length() { 15 | return Object.keys(this.items).length; 16 | } 17 | 18 | getItem(key) { 19 | return this.items[key]; 20 | } 21 | 22 | setItem(key, value) { 23 | this.items[key] = value; 24 | writeFile("localStorage.json", JSON.stringify(this.items), (error) => { 25 | if (error) { 26 | console.error(error); 27 | } 28 | }); 29 | } 30 | 31 | clear() { 32 | this.items = {}; 33 | unlink("localStorage.json", () => { 34 | console.log("localStorage file removed"); 35 | }); 36 | } 37 | } 38 | 39 | module.exports = new LocalStorage(); 40 | -------------------------------------------------------------------------------- /assets/design-patterns.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diegomais/node-js-design-patterns/874f5509fdb6d656f241b8a8c3114574957180b4/assets/design-patterns.jpg -------------------------------------------------------------------------------- /builder-pattern/Person.js: -------------------------------------------------------------------------------- 1 | class Person { 2 | constructor(builder) { 3 | this.name = builder.name; 4 | this.isEmployee = builder.isEmployee; 5 | this.isManager = builder.isManager; 6 | this.hours = builder.hours || 0; 7 | this.money = builder.money || 0; 8 | this.shoppingList = builder.shoppingList || []; 9 | } 10 | 11 | toString() { 12 | return JSON.stringify(this); 13 | } 14 | } 15 | 16 | module.exports = Person; 17 | -------------------------------------------------------------------------------- /builder-pattern/PersonBuilder.js: -------------------------------------------------------------------------------- 1 | var Person = require("./Person"); 2 | 3 | class PersonBuilder { 4 | constructor(name) { 5 | this.name = name; 6 | } 7 | 8 | makeEmployee() { 9 | this.isEmployee = true; 10 | return this; 11 | } 12 | 13 | makeManager(hours = 40) { 14 | this.isManager = true; 15 | this.hours = hours; 16 | return this; 17 | } 18 | 19 | makePartTime(hours = 20) { 20 | this.hours = hours; 21 | return this; 22 | } 23 | 24 | withMoney(money) { 25 | this.money = money; 26 | return this; 27 | } 28 | 29 | withShoppingList(list = []) { 30 | this.shoppingList = list; 31 | return this; 32 | } 33 | 34 | build() { 35 | return new Person(this); 36 | } 37 | } 38 | 39 | module.exports = PersonBuilder; 40 | -------------------------------------------------------------------------------- /builder-pattern/index.js: -------------------------------------------------------------------------------- 1 | var PersonBuilder = require("./PersonBuilder"); 2 | 3 | // Employees 4 | var sue = new PersonBuilder("Sue").makeEmployee().makeManager(60).build(); 5 | var bill = new PersonBuilder("Bill").makeEmployee().makePartTime().build(); 6 | var phil = new PersonBuilder("Phil").makeEmployee().build(); 7 | 8 | // Shoppers 9 | var charles = new PersonBuilder("Charles") 10 | .withMoney(500) 11 | .withShoppingList(["jeans", "sunglasses"]) 12 | .build(); 13 | 14 | var tabbitha = new PersonBuilder("Tabbitha").withMoney(1000).build(); 15 | 16 | console.log(sue.toString()); 17 | console.log(charles.toString()); 18 | -------------------------------------------------------------------------------- /chain-of-responsibility-pattern/Storage.js: -------------------------------------------------------------------------------- 1 | class Storage { 2 | constructor(name, inventory = [], deliveryTime = 0) { 3 | this.name = name; 4 | this.inventory = inventory; 5 | this.deliveryTime = deliveryTime; 6 | this.next = null; 7 | } 8 | 9 | setNext(storage) { 10 | this.next = storage; 11 | } 12 | 13 | lookInLocalInventory(itemName) { 14 | var index = this.inventory.map((item) => item.name).indexOf(itemName); 15 | return this.inventory[index]; 16 | } 17 | 18 | find(itemName) { 19 | var foundItem = this.lookInLocalInventory(itemName); 20 | if (foundItem) { 21 | return { 22 | name: foundItem.name, 23 | qty: foundItem.qty, 24 | location: this.name, 25 | deliveryTime: 26 | this.deliveryTime === 0 ? "now" : `${this.deliveryTime} day(s)`, 27 | }; 28 | } else if (this.next) { 29 | return this.next.find(itemName); 30 | } else { 31 | return `we do not carry ${itemName}`; 32 | } 33 | } 34 | } 35 | 36 | module.exports = Storage; 37 | -------------------------------------------------------------------------------- /chain-of-responsibility-pattern/Store.js: -------------------------------------------------------------------------------- 1 | var Storage = require("./Storage"); 2 | 3 | class Store { 4 | constructor(name, inventory = []) { 5 | this.name = name; 6 | 7 | var floor = new Storage("store floor", inventory.floor); 8 | var backroom = new Storage("store backroom", inventory.backroom); 9 | var localStore = new Storage("nearby store", inventory.localStore, 1); 10 | var warehouse = new Storage("warehouse", inventory.warehouse, 5); 11 | 12 | floor.setNext(backroom); 13 | backroom.setNext(localStore); 14 | localStore.setNext(warehouse); 15 | 16 | this.storage = floor; 17 | } 18 | 19 | find(itemName) { 20 | return this.storage.find(itemName); 21 | } 22 | } 23 | 24 | module.exports = Store; 25 | -------------------------------------------------------------------------------- /chain-of-responsibility-pattern/index.js: -------------------------------------------------------------------------------- 1 | var Store = require("./Store"); 2 | var inventory = require("./inventory"); 3 | 4 | var skiShop = new Store("Steep and Deep", inventory); 5 | 6 | var searchItem = "powder skis"; 7 | var results = skiShop.find(searchItem); 8 | 9 | console.log(results); 10 | -------------------------------------------------------------------------------- /chain-of-responsibility-pattern/inventory.json: -------------------------------------------------------------------------------- 1 | { 2 | "floor": [ 3 | { "name": "ski googles", "qty": 5 }, 4 | { "name": "ski hats", "qty": 15 }, 5 | { "name": "all mountain skis", "qty": 2 }, 6 | { "name": "ski boots", "qty": 2 } 7 | ], 8 | "backroom": [ 9 | { "name": "ski googles", "qty": 5 }, 10 | { "name": "ski hats", "qty": 15 }, 11 | { "name": "ski poles", "qty": 2 }, 12 | { "name": "ski rack", "qty": 1 } 13 | ], 14 | "localStore": [ 15 | { "name": "ski boots", "qty": 2 }, 16 | { "name": "ski poles", "qty": 4 }, 17 | { "name": "wax", "qty": 8 } 18 | ], 19 | "warehouse": [ 20 | { "name": "ski googles", "qty": 100 }, 21 | { "name": "ski hats", "qty": 100 }, 22 | { "name": "all mountain skis", "qty": 10 }, 23 | { "name": "ski boots", "qty": 20 }, 24 | { "name": "ski poles", "qty": 20 }, 25 | { "name": "wax", "qty": 100 }, 26 | { "name": "powder skis", "qty": 10 }, 27 | { "name": "ski rack", "qty": 3 } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /command-pattern/commands.js: -------------------------------------------------------------------------------- 1 | var { writeFile, unlink } = require("fs"); 2 | var path = require("path"); 3 | 4 | class ExitCommand { 5 | get name() { 6 | return "exit... bye!"; 7 | } 8 | 9 | execute() { 10 | process.exit(0); 11 | } 12 | } 13 | 14 | class CreateCommand { 15 | constructor(fileName, text) { 16 | this.fileName = fileName; 17 | this.fullPath = path.join(__dirname, fileName); 18 | this.body = text; 19 | } 20 | 21 | get name() { 22 | return `create ${this.fileName}`; 23 | } 24 | 25 | execute() { 26 | writeFile(this.fullPath, this.body, (f) => f); 27 | } 28 | 29 | undo() { 30 | unlink(this.fullPath, (f) => f); 31 | } 32 | } 33 | 34 | module.exports = { ExitCommand, CreateCommand }; 35 | -------------------------------------------------------------------------------- /command-pattern/conductor.js: -------------------------------------------------------------------------------- 1 | class Conductor { 2 | constructor() { 3 | this.history = []; 4 | this.undone = []; 5 | } 6 | 7 | run(command) { 8 | console.log(`Executing command: ${command.name}`); 9 | command.execute(); 10 | this.history.push(command); 11 | } 12 | 13 | printHistory() { 14 | this.history.forEach((command) => console.log(command.name)); 15 | } 16 | 17 | undo() { 18 | var command = this.history.pop(); 19 | console.log(`undo ${command.name}`); 20 | command.undo(); 21 | this.undone.push(command); 22 | } 23 | 24 | redo() { 25 | var command = this.undone.pop(); 26 | console.log(`redo ${command.name}`); 27 | command.execute(); 28 | this.history.push(command); 29 | } 30 | } 31 | 32 | module.exports = new Conductor(); 33 | -------------------------------------------------------------------------------- /command-pattern/index.js: -------------------------------------------------------------------------------- 1 | var { createInterface } = require("readline"); 2 | var conductor = require("./conductor"); 3 | var { ExitCommand, CreateCommand } = require("./commands"); 4 | var rl = createInterface({ 5 | input: process.stdin, 6 | output: process.stdout, 7 | }); 8 | 9 | console.log("create | history | undo | redo | exit"); 10 | rl.prompt(); 11 | 12 | rl.on("line", (input) => { 13 | var [commandText, ...remaining] = input.split(" "); 14 | var [fileName, ...fileText] = remaining; 15 | var text = fileText.join(" "); 16 | 17 | switch (commandText) { 18 | case "history": 19 | conductor.printHistory(); 20 | break; 21 | 22 | case "undo": 23 | conductor.undo(); 24 | break; 25 | 26 | case "redo": 27 | conductor.redo(); 28 | break; 29 | 30 | case "exit": 31 | conductor.run(new ExitCommand()); 32 | break; 33 | 34 | case "create": 35 | conductor.run(new CreateCommand(fileName, text)); 36 | break; 37 | 38 | default: 39 | console.log(`${commandText} command not found!`); 40 | } 41 | 42 | rl.prompt(); 43 | }); 44 | -------------------------------------------------------------------------------- /composite-pattern/CatalogGroup.js: -------------------------------------------------------------------------------- 1 | class CatalogGroup { 2 | constructor(name, composites = []) { 3 | this.name = name; 4 | this.composites = composites; 5 | } 6 | 7 | get total() { 8 | return this.composites.reduce( 9 | (total, nextItem) => total + nextItem.total, 10 | 0 11 | ); 12 | } 13 | 14 | print() { 15 | console.log(`\n${this.name.toUpperCase()}`); 16 | this.composites.forEach((item) => item.print()); 17 | } 18 | } 19 | 20 | module.exports = CatalogGroup; 21 | -------------------------------------------------------------------------------- /composite-pattern/CatalogItem.js: -------------------------------------------------------------------------------- 1 | class CatalogItem { 2 | constructor(name, price) { 3 | this.name = name; 4 | this.price = price; 5 | } 6 | 7 | get total() { 8 | return this.price; 9 | } 10 | 11 | print() { 12 | console.log(`${this.name} $${this.price} `); 13 | } 14 | } 15 | 16 | module.exports = CatalogItem; 17 | -------------------------------------------------------------------------------- /composite-pattern/index.js: -------------------------------------------------------------------------------- 1 | var CatalogItem = require("./CatalogItem"); 2 | var CatalogGroup = require("./CatalogGroup"); 3 | 4 | var boots = new CatalogItem("Leather Boots", 79.99); 5 | var sneakers = new CatalogItem("Kicks", 39.99); 6 | var flipFlops = new CatalogItem("California work boots", 19.99); 7 | 8 | var group_shoes = new CatalogGroup("Shoes and Such", [ 9 | boots, 10 | sneakers, 11 | flipFlops, 12 | ]); 13 | 14 | var group_food = new CatalogGroup("Food for while you try on clothes", [ 15 | new CatalogItem("Milkshake", 5.99), 16 | new CatalogItem("French Fries", 3.99), 17 | ]); 18 | 19 | var keychain = new CatalogItem("Key Chain", 0.99); 20 | 21 | var catalog = new CatalogGroup("Clothes and Food", [ 22 | keychain, 23 | group_shoes, 24 | group_food, 25 | ]); 26 | 27 | console.log(`$${catalog.total}`); 28 | 29 | catalog.print(); 30 | -------------------------------------------------------------------------------- /decorator-pattern/InventoryItem.js: -------------------------------------------------------------------------------- 1 | class InventoryItem { 2 | constructor(name, price) { 3 | this.name = name; 4 | this.price = price; 5 | } 6 | 7 | print() { 8 | console.log(`${item.name} costs ${item.price}`); 9 | } 10 | } 11 | 12 | class GoldenInventoryItem { 13 | constructor(baseItem) { 14 | this.name = `Golden ${baseItem.name}`; 15 | this.price = 1000 + baseItem.price; 16 | } 17 | } 18 | 19 | class DiamondInventoryItem { 20 | constructor(baseItem) { 21 | this.name = `Diamond ${baseItem.name}`; 22 | this.price = 1000 + baseItem.price; 23 | this.cutsGlass = true; 24 | } 25 | 26 | print() { 27 | console.log(`${this.name} costs a lot of money...`); 28 | } 29 | } 30 | 31 | module.exports = { InventoryItem, GoldenInventoryItem, DiamondInventoryItem }; 32 | -------------------------------------------------------------------------------- /decorator-pattern/Shopper.js: -------------------------------------------------------------------------------- 1 | class Shopper { 2 | constructor(name, account = 0) { 3 | this.name = name; 4 | this.account = account; 5 | this.items = []; 6 | } 7 | 8 | purchase(item) { 9 | if (item.price > this.account) { 10 | console.log(`Cannot afford ${item.name}`); 11 | } else { 12 | console.log(`Purchasing item ${item.name}`); 13 | this.account -= item.price; 14 | this.items.push(item); 15 | } 16 | } 17 | 18 | printStatus() { 19 | console.log(`${this.name} has purchased ${this.items.length} items:`); 20 | this.items.forEach((item) => { 21 | console.log(` * ${item.name} - ${item.price}`); 22 | }); 23 | console.log(`${this.name} has $${this.account.toFixed(2)} remaining.`); 24 | } 25 | } 26 | 27 | module.exports = Shopper; 28 | -------------------------------------------------------------------------------- /decorator-pattern/index.js: -------------------------------------------------------------------------------- 1 | var Shopper = require("./Shopper"); 2 | var { 3 | InventoryItem, 4 | GoldenInventoryItem, 5 | DiamondInventoryItem, 6 | } = require("./InventoryItem"); 7 | 8 | var alex = new Shopper("Alex", 4000); 9 | 10 | var walkman = new InventoryItem("Walkman", 29.99); 11 | var necklace = new InventoryItem("Necklace", 9.99); 12 | 13 | var gold_necklace = new GoldenInventoryItem(necklace); 14 | var diamond_gold_necklace = new DiamondInventoryItem(gold_necklace); 15 | 16 | var diamond_walkman = new DiamondInventoryItem(walkman); 17 | 18 | alex.purchase(diamond_gold_necklace); 19 | alex.purchase(diamond_walkman); 20 | 21 | alex.printStatus(); 22 | 23 | diamond_walkman.print(); 24 | -------------------------------------------------------------------------------- /factory-pattern/Employee.js: -------------------------------------------------------------------------------- 1 | var Shopper = require("./Shopper"); 2 | 3 | class Employee extends Shopper { 4 | constructor(name, money = 0, employer = "") { 5 | super(name, money); 6 | this.employer = employer; 7 | this.employed = true; 8 | } 9 | 10 | payDay(money = 0) { 11 | this.money += money; 12 | } 13 | } 14 | 15 | module.exports = Employee; 16 | -------------------------------------------------------------------------------- /factory-pattern/Person.js: -------------------------------------------------------------------------------- 1 | class Person { 2 | constructor(name = "unnamed person") { 3 | this.name = name; 4 | } 5 | 6 | toString() { 7 | return JSON.stringify(this); 8 | } 9 | } 10 | 11 | module.exports = Person; 12 | -------------------------------------------------------------------------------- /factory-pattern/Shopper.js: -------------------------------------------------------------------------------- 1 | var Person = require("./Person"); 2 | 3 | class Shopper extends Person { 4 | constructor(name, money = 0) { 5 | super(name); 6 | this.money = money; 7 | this.employed = false; 8 | } 9 | } 10 | 11 | module.exports = Shopper; 12 | -------------------------------------------------------------------------------- /factory-pattern/index.js: -------------------------------------------------------------------------------- 1 | var userFactory = require("./userFactory"); 2 | 3 | var alex = userFactory("Alex Banks", 100); 4 | var eve = userFactory("Eve Porcello", 100, "employee", "This and That"); 5 | 6 | eve.payDay(100); 7 | 8 | console.log(alex.toString()); 9 | console.log(eve.toString()); 10 | -------------------------------------------------------------------------------- /factory-pattern/userFactory.js: -------------------------------------------------------------------------------- 1 | var Employee = require("./Employee"); 2 | var Shopper = require("./Shopper"); 3 | 4 | var userFactory = (name, money = 0, type, employer) => { 5 | if (type === "employee") { 6 | return new Employee(name, money, employer); 7 | } else { 8 | return new Shopper(name, money); 9 | } 10 | }; 11 | 12 | module.exports = userFactory; 13 | -------------------------------------------------------------------------------- /iterator-pattern/InventoryItem.js: -------------------------------------------------------------------------------- 1 | class InventoryItem { 2 | constructor(name, price) { 3 | this.name = name; 4 | this.price = price; 5 | } 6 | 7 | writeLn() { 8 | process.stdout.write(`${this.name}: $${this.price}`); 9 | } 10 | } 11 | 12 | module.exports = InventoryItem; 13 | -------------------------------------------------------------------------------- /iterator-pattern/Iterator.js: -------------------------------------------------------------------------------- 1 | class Iterator { 2 | constructor(items = []) { 3 | this.index = 0; 4 | this.items = items; 5 | } 6 | 7 | first() { 8 | var [first] = this.items; 9 | return first; 10 | } 11 | 12 | last() { 13 | var [last] = [...this.items].reverse(); 14 | return last; 15 | } 16 | 17 | hasNext() { 18 | return this.index < this.items.length - 1; 19 | } 20 | 21 | current() { 22 | return this.items[this.index]; 23 | } 24 | 25 | next() { 26 | if (this.hasNext()) { 27 | this.index += 1; 28 | } 29 | return this.current(); 30 | } 31 | 32 | previous() { 33 | if (this.index !== 0) { 34 | this.index -= 1; 35 | } 36 | return this.current(); 37 | } 38 | } 39 | 40 | module.exports = Iterator; 41 | -------------------------------------------------------------------------------- /iterator-pattern/index.js: -------------------------------------------------------------------------------- 1 | var InventoryItem = require("./InventoryItem"); 2 | var Iterator = require("./Iterator"); 3 | 4 | require("readline").emitKeypressEvents(process.stdin); 5 | process.stdin.setRawMode(true); 6 | 7 | console.log("Press any direction key..."); 8 | 9 | var inventory = new Iterator([ 10 | new InventoryItem("Poles", 9.99), 11 | new InventoryItem("Skis", 799.99), 12 | new InventoryItem("Boots", 799.99), 13 | new InventoryItem("Burgers", 5.99), 14 | new InventoryItem("Fries", 2.99), 15 | new InventoryItem("Shake", 4.99), 16 | new InventoryItem("Jeans", 59.99), 17 | new InventoryItem("Shoes", 39.99), 18 | ]); 19 | 20 | process.stdin.on("keypress", (str, key) => { 21 | process.stdout.clearLine(); 22 | process.stdout.cursorTo(0); 23 | 24 | switch (key.name) { 25 | case "right": 26 | inventory.next().writeLn(); 27 | break; 28 | 29 | case "left": 30 | inventory.previous().writeLn(); 31 | break; 32 | 33 | case "down": 34 | inventory.last().writeLn(); 35 | break; 36 | 37 | case "up": 38 | inventory.first().writeLn(); 39 | break; 40 | 41 | case "c": 42 | if (key.ctrl) { 43 | process.exit(); 44 | } 45 | } 46 | }); 47 | -------------------------------------------------------------------------------- /observer-pattern/Mall.js: -------------------------------------------------------------------------------- 1 | class Mall { 2 | constructor() { 3 | this.sales = []; 4 | } 5 | 6 | notify(storeName, discount) { 7 | this.sales.push({ storeName, discount }); 8 | } 9 | } 10 | 11 | module.exports = Mall; 12 | -------------------------------------------------------------------------------- /observer-pattern/Shopper.js: -------------------------------------------------------------------------------- 1 | class Shopper { 2 | constructor(name) { 3 | this.name = name; 4 | } 5 | 6 | notify(storeName, discount) { 7 | console.log( 8 | `${this.name}, there is a sale at ${storeName}! ${discount}% off everything!` 9 | ); 10 | } 11 | } 12 | 13 | module.exports = Shopper; 14 | -------------------------------------------------------------------------------- /observer-pattern/Store.js: -------------------------------------------------------------------------------- 1 | class Store { 2 | constructor(name) { 3 | this.name = name; 4 | this.subscribers = []; 5 | } 6 | 7 | subscribe(observer) { 8 | this.subscribers.push(observer); 9 | } 10 | 11 | sale(discount) { 12 | this.subscribers.forEach((observer) => 13 | observer.notify(this.name, discount) 14 | ); 15 | } 16 | } 17 | 18 | module.exports = Store; 19 | -------------------------------------------------------------------------------- /observer-pattern/index.js: -------------------------------------------------------------------------------- 1 | var Store = require("./Store"); 2 | var Shopper = require("./Shopper"); 3 | var Mall = require("./Mall"); 4 | 5 | var catsAndThings = new Store("Cats & Things"); 6 | var insAndOuts = new Store("Ins and Outs"); 7 | 8 | var alex = new Shopper("Alex"); 9 | var eve = new Shopper("Eve"); 10 | var sharon = new Shopper("Sharon"); 11 | var mike = new Shopper("Mike"); 12 | 13 | var valleyMall = new Mall(); 14 | 15 | catsAndThings.subscribe(alex); 16 | catsAndThings.subscribe(eve); 17 | catsAndThings.subscribe(mike); 18 | catsAndThings.subscribe(valleyMall); 19 | 20 | insAndOuts.subscribe(sharon); 21 | insAndOuts.subscribe(valleyMall); 22 | 23 | catsAndThings.sale(20); 24 | insAndOuts.sale(50); 25 | 26 | console.log(valleyMall.sales); 27 | -------------------------------------------------------------------------------- /prototype-pattern/Shopper.js: -------------------------------------------------------------------------------- 1 | class Shopper { 2 | constructor(name = "unnamed person") { 3 | this._name = name; 4 | this._shoppingList = []; 5 | } 6 | 7 | set name(value) { 8 | this._name = value; 9 | } 10 | 11 | get name() { 12 | return this._name; 13 | } 14 | 15 | get shoppingList() { 16 | return this._shoppingList.join(", "); 17 | } 18 | 19 | addItemToList(item) { 20 | this._shoppingList.push(item); 21 | } 22 | 23 | clone() { 24 | var proto = Object.getPrototypeOf(this); 25 | var cloned = Object.create(proto); 26 | 27 | cloned._name = this._name; 28 | cloned._shoppingList = [...this._shoppingList]; 29 | 30 | return cloned; 31 | } 32 | } 33 | 34 | module.exports = Shopper; 35 | -------------------------------------------------------------------------------- /prototype-pattern/index.js: -------------------------------------------------------------------------------- 1 | var scout_prototype = require("./scout_prototype"); 2 | 3 | var alex = scout_prototype.clone(); 4 | alex.name = "Alex Banks"; 5 | alex.addItemToList("slingshot"); 6 | 7 | var eve = scout_prototype.clone(); 8 | eve.name = "Eve Porcello"; 9 | eve.addItemToList("reading light"); 10 | 11 | console.log(`${alex.name}: ${alex.shoppingList}`); 12 | console.log(`${eve.name}: ${eve.shoppingList}`); 13 | -------------------------------------------------------------------------------- /prototype-pattern/scout_prototype.js: -------------------------------------------------------------------------------- 1 | var Shopper = require("./Shopper"); 2 | 3 | var scout = new Shopper(); 4 | scout.addItemToList("camping knife"); 5 | scout.addItemToList("tent"); 6 | scout.addItemToList("backpack"); 7 | scout.addItemToList("map"); 8 | 9 | module.exports = scout; 10 | -------------------------------------------------------------------------------- /proxy-pattern/FS_Proxy.js: -------------------------------------------------------------------------------- 1 | class FS_Proxy { 2 | constructor(fs_subject) { 3 | this.fs = fs_subject; 4 | } 5 | 6 | readFile(path, format, callback) { 7 | var markdownExtensionRegex = /.md$|.MD$/; 8 | 9 | if (!path.match(markdownExtensionRegex)) { 10 | return callback(new Error(`Can only read Markdown files.`)); 11 | } 12 | 13 | this.fs.readFile(path, format, (error, contents) => { 14 | if (error) { 15 | console.error(error); 16 | return callback(error); 17 | } 18 | 19 | return callback(null, contents); 20 | }); 21 | } 22 | } 23 | 24 | module.exports = FS_Proxy; 25 | -------------------------------------------------------------------------------- /proxy-pattern/Readme.md: -------------------------------------------------------------------------------- 1 | Readme Markdown 2 | ================ 3 | This is the markdown version of the readme file. 4 | -------------------------------------------------------------------------------- /proxy-pattern/Readme.txt: -------------------------------------------------------------------------------- 1 | This is a text version of the readme file. 2 | -------------------------------------------------------------------------------- /proxy-pattern/index.js: -------------------------------------------------------------------------------- 1 | var path = require("path"); 2 | 3 | var FS_Proxy = require("./FS_Proxy"); 4 | var fs = new FS_Proxy(require("fs")); 5 | 6 | var txtFile = path.join(__dirname, "Readme.txt"); 7 | var mdFile = path.join(__dirname, "Readme.md"); 8 | 9 | var result = (error, contents) => { 10 | console.log("reading file..."); 11 | 12 | if (error) { 13 | console.log("\x07"); 14 | console.error(error); 15 | } else { 16 | console.log(contents); 17 | } 18 | }; 19 | 20 | fs.readFile(txtFile, "UTF-8", result); 21 | fs.readFile(mdFile, "UTF-8", result); 22 | -------------------------------------------------------------------------------- /singleton-pattern/Logger.js: -------------------------------------------------------------------------------- 1 | class Logger { 2 | constructor() { 3 | this.logs = []; 4 | } 5 | 6 | get count() { 7 | return this.logs.length; 8 | } 9 | 10 | log(message) { 11 | const timestamp = new Date().toISOString(); 12 | this.logs.push({ message, timestamp }); 13 | console.log(`${timestamp} - ${message}`); 14 | } 15 | } 16 | 17 | module.exports = new Logger(); 18 | -------------------------------------------------------------------------------- /singleton-pattern/Shopper.js: -------------------------------------------------------------------------------- 1 | var logger = require("./Logger"); 2 | 3 | class Shopper { 4 | constructor(name, money = 0) { 5 | this.name = name; 6 | this.money = money; 7 | logger.log(`New Shopper: ${name} has ${money} in their account.`); 8 | } 9 | } 10 | 11 | module.exports = Shopper; 12 | -------------------------------------------------------------------------------- /singleton-pattern/Store.js: -------------------------------------------------------------------------------- 1 | var logger = require("./Logger"); 2 | 3 | class Store { 4 | constructor(name, inventory = []) { 5 | this.name = name; 6 | this.inventory = inventory; 7 | logger.log(`New Store: ${name} has ${inventory.length} items in stock.`); 8 | } 9 | } 10 | 11 | module.exports = Store; 12 | -------------------------------------------------------------------------------- /singleton-pattern/index.js: -------------------------------------------------------------------------------- 1 | var logger = require("./Logger"); 2 | var Shopper = require("./Shopper"); 3 | var Store = require("./Store"); 4 | 5 | logger.log("starting app..."); 6 | 7 | var alex = new Shopper("alex", 500); 8 | var ski_shop = new Store("Steep and Deep Supplies", [ 9 | { 10 | item: "Downhill Skis", 11 | qty: 5, 12 | price: 750, 13 | }, 14 | { 15 | item: "Knit Hat", 16 | qty: 20, 17 | price: 5, 18 | }, 19 | ]); 20 | 21 | logger.log("finished config..."); 22 | 23 | console.log(`${logger.count} logs total`); 24 | logger.logs.map((log) => console.log(` ${log.message}`)); 25 | -------------------------------------------------------------------------------- /strategy-pattern/LogStrategy.js: -------------------------------------------------------------------------------- 1 | var path = require("path"); 2 | var { appendFile } = require("fs"); 3 | 4 | class LogStrategy { 5 | static noDate(timestamp, message) { 6 | console.log(message); 7 | } 8 | 9 | static toFile(timestamp, message) { 10 | var fileName = path.join(__dirname, "logs.txt"); 11 | appendFile(fileName, `${timestamp} - ${message} \n`, (error) => { 12 | if (error) { 13 | console.log("Error writing to file"); 14 | console.error(error); 15 | } 16 | }); 17 | } 18 | 19 | static toConsole(timestamp, message) { 20 | console.log(`${timestamp} - ${message}`); 21 | } 22 | 23 | static none() {} 24 | } 25 | 26 | module.exports = LogStrategy; 27 | -------------------------------------------------------------------------------- /strategy-pattern/Logger.js: -------------------------------------------------------------------------------- 1 | var LogStrategy = require("./LogStrategy"); 2 | var config = require("./config"); 3 | 4 | class Logger { 5 | constructor(strategy = "toConsole") { 6 | this.logs = []; 7 | this.strategy = LogStrategy[strategy]; 8 | } 9 | 10 | get count() { 11 | return this.logs.length; 12 | } 13 | 14 | changeStrategy(newStrategy) { 15 | this.strategy = LogStrategy[newStrategy]; 16 | } 17 | 18 | log(message) { 19 | const timestamp = new Date().toISOString(); 20 | this.logs.push({ message, timestamp }); 21 | this.strategy(timestamp, message); 22 | } 23 | } 24 | 25 | module.exports = new Logger(config.logs.strategy); 26 | -------------------------------------------------------------------------------- /strategy-pattern/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "logs": { 3 | "strategy": "noDate" 4 | } 5 | } -------------------------------------------------------------------------------- /strategy-pattern/index.js: -------------------------------------------------------------------------------- 1 | var logger = require("./Logger"); 2 | 3 | logger.log("Hello World"); 4 | logger.log("Hi World"); 5 | logger.log("Yo World"); 6 | 7 | logger.changeStrategy("toConsole"); 8 | 9 | logger.log("Hello World"); 10 | logger.log("Hi World"); 11 | logger.log("Yo World"); 12 | --------------------------------------------------------------------------------