├── .gitignore ├── .vscode └── settings.json ├── exercises ├── 02_exercise.js ├── 01_exercise.js ├── 06_exercise.js ├── 05_exercise.js ├── 07_exercise.js ├── 03_exercise.js ├── 09_exercise.js ├── 04_exercise.js └── 08_exercise.js ├── 01_factory ├── problem.js ├── funct-solution.js ├── README.md └── oop-solution.js ├── 02_singleton ├── problem.js ├── solution.js └── README.md ├── 06_strategy ├── README.md ├── solution.js └── problem.js ├── 04_adapter ├── README.md ├── solution.js └── problem.js ├── 05_composite ├── README.md ├── solution.js └── problem.js ├── 03_decorator ├── README.md ├── problem.js └── solution.js └── 07_command ├── README.md ├── problem.js └── solution.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.wordWrap": "on" 3 | } 4 | -------------------------------------------------------------------------------- /exercises/02_exercise.js: -------------------------------------------------------------------------------- 1 | // You are developing a logging system for a web application. Implement a logger class to ensure that only one instance of the logger exists throughout the application. 2 | -------------------------------------------------------------------------------- /exercises/01_exercise.js: -------------------------------------------------------------------------------- 1 | // You are building a game where different types of enemies need to be created dynamically. Implement a system to create different types of enemies such as 'Goblin', 'Orc', and 'Dragon'. 2 | -------------------------------------------------------------------------------- /exercises/06_exercise.js: -------------------------------------------------------------------------------- 1 | // You are building an image loading library for a web application. Implement a system to create image proxies that will lazily load images from the server only when they are requested. 2 | -------------------------------------------------------------------------------- /exercises/05_exercise.js: -------------------------------------------------------------------------------- 1 | // You are developing a file system management module where both individual files and directories need to be treated uniformly. Implement a system to represent files and directories as a single interface. 2 | -------------------------------------------------------------------------------- /exercises/07_exercise.js: -------------------------------------------------------------------------------- 1 | // You are developing a weather monitoring application that needs to notify various components whenever the weather conditions change. Implement a system to allow components to subscribe to weather updates and react accordingly. 2 | -------------------------------------------------------------------------------- /exercises/03_exercise.js: -------------------------------------------------------------------------------- 1 | // You are working on an e-commerce website where users can customize their orders with optional add-ons such as gift wrapping, expedited shipping, and personal messages. Implement a system to add these optional features to the base order. 2 | -------------------------------------------------------------------------------- /exercises/09_exercise.js: -------------------------------------------------------------------------------- 1 | // You are developing a remote control application for a smart home system. Implement a system to represent various actions such as turning lights on/off, adjusting thermostat settings, and locking doors, as command objects that can be executed and undone. 2 | -------------------------------------------------------------------------------- /exercises/04_exercise.js: -------------------------------------------------------------------------------- 1 | // You have an existing class that fetches data from a REST API in a specific format. However, you need to integrate it with a new component that expects the data in a different format. Implement a system to adapt the existing class to work with the new component. 2 | -------------------------------------------------------------------------------- /exercises/08_exercise.js: -------------------------------------------------------------------------------- 1 | // You are building a sorting algorithm visualization tool that allows users to choose between different sorting algorithms such as bubble sort, merge sort, and quick sort. Implement a system to encapsulate each sorting algorithm and allow users to switch between them dynamically. 2 | -------------------------------------------------------------------------------- /01_factory/problem.js: -------------------------------------------------------------------------------- 1 | function processOrder(paymentType, amount) { 2 | switch (paymentType) { 3 | case "paypal": 4 | console.log(`Processing ${amount} through PayPal`); 5 | break; 6 | case "stripe": 7 | console.log(`Processing ${amount} through Stripe`); 8 | break; 9 | default: 10 | console.error("Unsupported payment gateway type"); 11 | } 12 | } 13 | 14 | processOrder("paypal", 100); 15 | processOrder("stripe", 200); 16 | processOrder("unsupported", 300); 17 | -------------------------------------------------------------------------------- /02_singleton/problem.js: -------------------------------------------------------------------------------- 1 | let counter = 0; 2 | 3 | class Counter { 4 | getInstance() { 5 | return this; 6 | } 7 | 8 | getCount() { 9 | return counter; 10 | } 11 | 12 | increment() { 13 | return ++counter; 14 | } 15 | 16 | decrement() { 17 | return --counter; 18 | } 19 | } 20 | 21 | const counter1 = new Counter(); 22 | const counter2 = new Counter(); 23 | 24 | // Not a Singleton - A Singleton should only be able to get instantiated once! 25 | console.log(counter1.getInstance() === counter2.getInstance()); // false -------------------------------------------------------------------------------- /02_singleton/solution.js: -------------------------------------------------------------------------------- 1 | let instance; 2 | let counter = 0; 3 | 4 | class Counter { 5 | constructor() { 6 | if (instance) { 7 | throw new Error("You can only create one instance!"); 8 | } 9 | instance = this; 10 | } 11 | 12 | getInstance() { 13 | return this; 14 | } 15 | 16 | getCount() { 17 | return counter; 18 | } 19 | 20 | increment() { 21 | return ++counter; 22 | } 23 | 24 | decrement() { 25 | return --counter; 26 | } 27 | } 28 | 29 | const counter1 = new Counter(); 30 | const counter2 = new Counter(); 31 | // Error: You can only create one instance! 32 | // Which means that this is a Singleton as it can only get instantiated once! -------------------------------------------------------------------------------- /01_factory/funct-solution.js: -------------------------------------------------------------------------------- 1 | const processPaymentWithPayPal = (amount) => { 2 | console.log(`Processing ${amount} through PayPal`); 3 | }; 4 | 5 | const processPaymentWithStripe = (amount) => { 6 | console.log(`Processing ${amount} through Stripe`); 7 | }; 8 | 9 | const processPaymentUnsupported = () => { 10 | console.log(`Unsupported`); 11 | }; 12 | 13 | const getPaymentProcessor = (type) => { 14 | const processors = { 15 | paypal: processPaymentWithPayPal, 16 | stripe: processPaymentWithStripe, 17 | }; 18 | 19 | return processors[type] || processPaymentUnsupported; 20 | }; 21 | 22 | const processOrder = (paymentType, amount) => { 23 | const processPayment = getPaymentProcessor(paymentType); 24 | 25 | processPayment(amount); 26 | }; 27 | 28 | processOrder("paypal", 100); 29 | processOrder("stripe", 200); 30 | processOrder("unsupported", 300); 31 | -------------------------------------------------------------------------------- /06_strategy/README.md: -------------------------------------------------------------------------------- 1 | ## With & Without the Strategy Pattern 2 | 3 | ### Dynamic Strategy Assignment 4 | - **Problem.js:** Uses conditional statements to select and apply algorithms, which is less flexible. 5 | - **Solution.js:** Uses `compressionContext` function with `setStrategy` method for dynamic assignment, enabling runtime changes without modifying client code. 6 | 7 | ### Separation of Concerns 8 | - **Problem.js:** Mixes logic for selecting and applying algorithms with their implementation. 9 | - **Solution.js:** Separates context from strategy implementation, with `compressionContext` handling strategy application and strategy functions focused on compression specifics. 10 | 11 | ### Improved Maintainability and Extensibility 12 | - **Problem.js:** Adding new algorithms requires modifying existing conditional logic. 13 | - **Solution.js:** New algorithms can be added as new strategy functions without altering existing code, adhering to the open/closed principle. -------------------------------------------------------------------------------- /06_strategy/solution.js: -------------------------------------------------------------------------------- 1 | const zipCompressionStrategy = (data) => { 2 | console.log("Compressing using ZIP algorithm"); 3 | 4 | return "Compressed using ZIP"; 5 | }; 6 | 7 | const gzipCompressionStrategy = (data) => { 8 | console.log("Compressing using GZIP algorithm"); 9 | 10 | return "Compressed using GZIP"; 11 | }; 12 | 13 | // Context function that sets strategy and compresses data 14 | const compressionContext = (strategy) => { 15 | const setStrategy = (newStrategy) => { 16 | strategy = newStrategy; 17 | }; 18 | 19 | const compress = (data) => { 20 | return strategy(data); 21 | }; 22 | 23 | return { setStrategy, compress }; 24 | }; 25 | 26 | // Example usage 27 | const data = "Lorem ipsum dolor sit amet"; 28 | 29 | const context = compressionContext(zipCompressionStrategy); 30 | console.log(context.compress(data)); // Output: Compressed using ZIP 31 | 32 | context.setStrategy(gzipCompressionStrategy); 33 | console.log(context.compress(data)); // Output: Compressed using GZIP 34 | -------------------------------------------------------------------------------- /02_singleton/README.md: -------------------------------------------------------------------------------- 1 | ### 1. Approach without a Singleton 2 | 3 | **Pros:** 4 | - **Simplicity**: The implementation is straightforward and easy to understand. 5 | - **Flexibility**: Allows for the creation of multiple instances, 6 | 7 | **Cons**: 8 | - **Shared State Vulnerability**: The global counter can lead to issues with state management in a multi-instance scenario. 9 | 10 | ### 2. Approach with a Singleton 11 | 12 | **Pros**: 13 | - **Encapsulated State**: The state is encapsulated within the Singleton instance, preventing external modification and ensuring consistent access. 14 | 15 | ### State management in React 16 | In React, we often rely on a global state through state management tools such as **Redux** or **React Context** instead of using Singletons. 17 | 18 | Although their global state behavior might seem similar to that of a Singleton, these tools provide a **read-only** state rather than the mutable state of the Singleton. When using Redux, only pure function reducers can update the state, after a component has sent an action through a dispatcher. -------------------------------------------------------------------------------- /04_adapter/README.md: -------------------------------------------------------------------------------- 1 | ### Without using the Adapter Pattern 2 | - **Tight Coupling:** Directly depends on services like Twilio and Nodemailer, making it less flexible. 3 | - **Violates Single Responsibility Principle (SRP):** Handles logic for multiple services, leading to multiple reasons for changes. 4 | - **Testing Difficulty:** Hard to test without actual services, leading to complex and flaky tests. 5 | 6 | ### With the Adapter Pattern 7 | - **Decoupling:** Uses `NotificationAdapter` and subclasses (`EmailAdapter`, `SMSAdapter`, `SlackAdapter`) to separate logic from services, making the code more flexible. 8 | - **Adheres to Single Responsibility Principle (SRP) Adherence:** Each adapter handles its own service logic, improving maintainability. 9 | - **Easier Testing:** Adapters can be mocked, making tests more reliable and faster. 10 | - **Flexibility and Scalability:** Adding a new service is simple with a new adapter class, without modifying existing code. The system can easily support more notification services. 11 | 12 | ### Summary 13 | The adapter pattern leads to a more flexible, maintainable, and testable design. -------------------------------------------------------------------------------- /05_composite/README.md: -------------------------------------------------------------------------------- 1 | ### 1. Approach using Object Literal 2 | 3 | **Pros:** 4 | 5 | 1. **Simplicity:** Object literals are straightforward and easy to understand. 6 | 2. **Declarative:** Role hierarchy is defined directly in the object structure. 7 | 3. **Minimalist:** It requires less boilerplate code. 8 | 9 | **Cons:** 10 | 11 | 1. **Limited Abstraction:** It lacks clear separation of concerns. Logic and data are mixed, making it harder to maintain and extend. 12 | 2. **Mutability:** The role structure is mutable, which might lead to unintended changes. 13 | 3. **Recursive Functions:** The `getPermissions` and `displayRoleHierarchy` functions are recursive, potentially leading to stack overflow errors for deeply nested role hierarchies. 14 | 15 | ### 2. Approach using Composition 16 | 17 | **Pros:** 18 | 19 | 1. **Encapsulation:** Roles are encapsulated within objects, providing a clear separation of concerns. 20 | 2. **Inheritance:** Utilizes object-oriented principles like inheritance to model role hierarchies. 21 | 3. **Readability:** Class methods like `getPermissions` and `display` provide clear and readable code. -------------------------------------------------------------------------------- /03_decorator/README.md: -------------------------------------------------------------------------------- 1 | ### 1. Pros of using a Decorator pattern 2 | 3 | - **Modularity and Flexibility:** 4 | - Defines separate functions for each topping. 5 | - Easy to add or remove toppings without modifying core logic. 6 | - Allows flexible and dynamic pizza creation. 7 | 8 | - **Extensibility:** 9 | - Adding a new topping is simple with a new function. 10 | - Minimal changes needed for new toppings. 11 | 12 | - **Use of Functional Programming:** 13 | - Leverages principles like higher-order functions and reduce method. 14 | - More declarative and concise. 15 | - Easier to understand and maintain. 16 | 17 | - **Adherence to the Open/Closed Principle:** 18 | - Open for extension (easy to add new toppings). 19 | - Closed for modification (existing code doesn't change). 20 | 21 | - **Improved Readability:** 22 | - More readable and easier to follow. 23 | - Separation of concerns and descriptive function names. 24 | 25 | - **Better Abstraction:** 26 | - Abstracts topping application into a higher-order function. 27 | - Hides complexity, making high-level logic clearer. 28 | 29 | - **Summary:** 30 | - Offers a flexible, extensible, and maintainable approach. 31 | - Adheres to design principles and patterns. -------------------------------------------------------------------------------- /07_command/README.md: -------------------------------------------------------------------------------- 1 | ## With & Without the Command Pattern 2 | 3 | ### Structured Command Implementation 4 | - **Problem.js:** Uses simple functions without a unified interface, leading to inconsistent command implementations. 5 | - **Solution.js:** Introduces a Command interface with `execute()` and `undo()` methods, ensuring consistent and formalized command structure. 6 | 7 | ### Encapsulation and Modularity 8 | - **Problem.js:** Handles commands through standalone functions, passing data directly as arguments. 9 | - **Solution.js:** Uses concrete command classes (e.g., `DeductFundsCommand`, `CreditFundsCommand`) that encapsulate command data and logic, enhancing modularity and data integrity. 10 | 11 | ### Undo Functionality 12 | - **Problem.js:** Attempts to handle undo with a separate function, which is less integrated and flexible. 13 | - **Solution.js:** Implements an `undo()` method in command classes, allowing easy and integrated command reversal. 14 | 15 | ### Transaction Management 16 | - **Problem.js:** Lacks a clear mechanism for managing complex transactions. 17 | - **Solution.js:** Demonstrates structured transaction execution and rollback using `executeTransaction` and `rollbackTransaction` functions to handle arrays of command objects. -------------------------------------------------------------------------------- /04_adapter/solution.js: -------------------------------------------------------------------------------- 1 | class NotificationAdapter { 2 | async send(message, recipient) { 3 | throw new Error("Method 'send' must be implemented"); 4 | } 5 | } 6 | 7 | class EmailAdapter extends NotificationAdapter { 8 | async send(message, recipient) { 9 | // Email sending logic here 10 | } 11 | } 12 | 13 | class SMSAdapter extends NotificationAdapter { 14 | async send(message, recipient) { 15 | // SMS sending logic here 16 | } 17 | } 18 | 19 | class SlackAdapter extends NotificationAdapter { 20 | async send(message, recipient) { 21 | // Slack sending logic here 22 | } 23 | } 24 | 25 | async function sendNotification(adapter, message, recipient) { 26 | try { 27 | await adapter.send(message, recipient); 28 | console.log("Notification sent successfully"); 29 | } catch (error) { 30 | console.error("Error sending notification:", error.message); 31 | } 32 | } 33 | 34 | const emailAdapter = new EmailAdapter("your-email@gmail.com", "your-password"); 35 | const smsAdapter = new SMSAdapter("your-twilio-account-sid", "your-twilio-auth-token"); 36 | const slackAdapter = new SlackAdapter("your-slack-token"); 37 | 38 | sendNotification(emailAdapter, "Hello via Email!", "recipient@example.com"); 39 | sendNotification(smsAdapter, "Hello via SMS!", "+1234567890"); 40 | sendNotification(slackAdapter, "Hello via Slack!"); -------------------------------------------------------------------------------- /06_strategy/problem.js: -------------------------------------------------------------------------------- 1 | // Compression functions 2 | function compressWithZIP(data) { 3 | console.log("Compressing using ZIP algorithm"); 4 | // Implementation logic for ZIP compression 5 | return "Compressed using ZIP"; 6 | } 7 | 8 | function compressWithGZIP(data) { 9 | console.log("Compressing using GZIP algorithm"); 10 | // Implementation logic for GZIP compression 11 | return "Compressed using GZIP"; 12 | } 13 | 14 | // Decompression functions 15 | function decompressWithZIP(data) { 16 | console.log("Decompressing using ZIP algorithm"); 17 | // Implementation logic for ZIP decompression 18 | return "Decompressed using ZIP"; 19 | } 20 | 21 | function decompressWithGZIP(data) { 22 | console.log("Decompressing using GZIP algorithm"); 23 | // Implementation logic for GZIP decompression 24 | return "Decompressed using GZIP"; 25 | } 26 | 27 | // Example usage 28 | const data = "Lorem ipsum dolor sit amet"; 29 | let compressedData; 30 | let decompressedData; 31 | 32 | // Select compression algorithm 33 | if (false) { 34 | compressedData = compressWithZIP(data); 35 | } else { 36 | compressedData = compressWithGZIP(data); 37 | } 38 | 39 | // Select decompression algorithm 40 | if (true) { 41 | decompressedData = decompressWithZIP(compressedData); 42 | } else { 43 | decompressedData = decompressWithGZIP(compressedData); 44 | } 45 | 46 | console.log(decompressedData); 47 | -------------------------------------------------------------------------------- /03_decorator/problem.js: -------------------------------------------------------------------------------- 1 | const BASIC_PIZZA_PRICE = 10; 2 | const CHEESE_TOPPING_PRICE = 2; 3 | const PEPPERONI_TOPPING_PRICE = 3; 4 | const HOT_SAUCE_TOPPING_PRICE = 1; 5 | 6 | function createPizza(toppings) { 7 | let price = BASIC_PIZZA_PRICE; 8 | 9 | switch (toppings) { 10 | case "cheese": 11 | price += CHEESE_TOPPING_PRICE; 12 | break; 13 | case "pepperoni": 14 | price += PEPPERONI_TOPPING_PRICE; 15 | break; 16 | case "cheese_and_pepperoni": 17 | price += CHEESE_TOPPING_PRICE + PEPPERONI_TOPPING_PRICE; 18 | break; 19 | case "hot_sauce": 20 | price += HOT_SAUCE_TOPPING_PRICE; 21 | break; 22 | default: 23 | // Basic pizza with no toppings 24 | break; 25 | } 26 | 27 | return { 28 | price 29 | }; 30 | } 31 | 32 | // Example Usage 33 | const basicPizza = createPizza(); // Basic pizza with no toppings 34 | const pizzaWithCheese = createPizza("cheese"); 35 | const pizzaWithPepperoni = createPizza("pepperoni"); 36 | const pizzaWithBoth = createPizza("cheese_and_pepperoni"); 37 | 38 | console.log("Basic pizza price:", basicPizza.price); // Output: 10 39 | console.log("Pizza with cheese price:", pizzaWithCheese.price); // Output: 12 40 | console.log("Pizza with pepperoni price:", pizzaWithPepperoni.price); // Output: 13 41 | console.log("Pizza with cheese and pepperoni price:", pizzaWithBoth.price); // Output: 15 42 | -------------------------------------------------------------------------------- /07_command/problem.js: -------------------------------------------------------------------------------- 1 | function deductFunds(command) { 2 | console.log(`Deducting ${command.amount} from account ${command.account}`); 3 | } 4 | 5 | function creditFunds(command) { 6 | console.log(`Crediting ${command.amount} to account ${command.account}`); 7 | } 8 | 9 | function executeTransaction(commands) { 10 | console.log("Starting transaction"); 11 | try { 12 | commands.forEach((command) => command.action(command)); 13 | console.log("Transaction committed successfully"); 14 | } catch (error) { 15 | console.log("Transaction failed, rolling back"); 16 | rollbackTransaction(commands); 17 | throw error; 18 | } 19 | } 20 | 21 | function rollbackTransaction(commands) { 22 | commands.reverse().forEach((command) => undoCommand(command)); 23 | console.log("Transaction rolled back"); 24 | } 25 | 26 | function undoCommand(command) { 27 | if (command.action === deductFunds) { 28 | console.log( 29 | `Undoing deduction of ${command.amount} from account ${command.account}` 30 | ); 31 | } else if (command.action === creditFunds) { 32 | console.log( 33 | `Undoing credit of ${command.amount} to account ${command.account}` 34 | ); 35 | } 36 | } 37 | 38 | // Example usage 39 | const commands = [ 40 | { action: deductFunds, account: "12345", amount: 100 }, 41 | { action: creditFunds, account: "67890", amount: 100 }, 42 | ]; 43 | 44 | executeTransaction(commands); 45 | -------------------------------------------------------------------------------- /03_decorator/solution.js: -------------------------------------------------------------------------------- 1 | const BASIC_PIZZA_PRICE = 10; 2 | const CHEESE_TOPPING_PRICE = 2; 3 | const PEPPERONI_TOPPING_PRICE = 3; 4 | const HOT_SAUCE_TOPPING_PRICE = 1; 5 | 6 | // Basic pizza price function 7 | const basicPizzaPrice = () => BASIC_PIZZA_PRICE; 8 | 9 | // Topping decorators as functions 10 | const addCheese = (price) => price + CHEESE_TOPPING_PRICE; 11 | const addPepperoni = (price) => price + PEPPERONI_TOPPING_PRICE; 12 | const addHotSauce = (price) => price + HOT_SAUCE_TOPPING_PRICE; 13 | 14 | // Function to apply toppings 15 | const applyTopping = (price, topping) => { 16 | switch (topping) { 17 | case "cheese": 18 | return addCheese(price); 19 | case "pepperoni": 20 | return addPepperoni(price); 21 | case "hot_sauce": 22 | return addHotSauce(price); 23 | default: 24 | return price; 25 | } 26 | }; 27 | 28 | const createPizza = (toppings = []) => { 29 | return toppings.reduce(applyTopping, basicPizzaPrice()); 30 | }; 31 | 32 | const basicPizza = createPizza(); // Basic pizza with no toppings 33 | const pizzaWithCheese = createPizza(["cheese"]); 34 | const pizzaWithPepperoni = createPizza(["pepperoni"]); 35 | const pizzaWithBoth = createPizza(["cheese", "pepperoni"]); 36 | 37 | console.log("Basic pizza price:", basicPizza); // Output: 10 38 | console.log("Pizza with cheese price:", pizzaWithCheese); // Output: 12 39 | console.log("Pizza with pepperoni price:", pizzaWithPepperoni); // Output: 13 40 | console.log("Pizza with cheese and pepperoni price:", pizzaWithBoth); // Output: 15 -------------------------------------------------------------------------------- /04_adapter/problem.js: -------------------------------------------------------------------------------- 1 | const twilio = require("twilio"); 2 | const nodemailer = require("nodemailer"); 3 | const { WebClient } = require("@slack/web-api"); 4 | 5 | async function sendNotification(service, message, recipient) { 6 | try { 7 | if (service === "email") { 8 | const transporter = nodemailer.createTransport({ 9 | service: "gmail", 10 | auth: { user: "your-email@gmail.com", pass: "your-password" }, 11 | }); 12 | 13 | await transporter.sendMail({ 14 | from: "no-reply@example.com", 15 | to: recipient, 16 | subject: "Notification", 17 | text: message, 18 | }); 19 | } else if (service === "sms") { 20 | const client = twilio("your-twilio-account-sid", "your-twilio-auth-token"); 21 | 22 | await client.messages.create({ 23 | body: message, 24 | from: "+1234567890", 25 | to: recipient, 26 | }); 27 | } else if (service === "slack") { 28 | const client = new WebClient("your-slack-token"); 29 | 30 | await client.chat.postMessage({ 31 | channel: "#notifications", 32 | text: message, 33 | }); 34 | } else { 35 | throw new Error("Unsupported notification service"); 36 | } 37 | 38 | console.log("Notification sent successfully"); 39 | } catch (error) { 40 | console.error("Error sending notification:", error.message); 41 | } 42 | } 43 | 44 | // Send notifications using different services 45 | sendNotification("email", "Hello via Email!", "recipient@example.com"); 46 | sendNotification("sms", "Hello via SMS!", "+1234567890"); 47 | sendNotification("slack", "Hello via Slack!"); 48 | -------------------------------------------------------------------------------- /05_composite/solution.js: -------------------------------------------------------------------------------- 1 | class Role { 2 | constructor(name) { 3 | this.name = name; 4 | this.permissions = []; 5 | this.subRoles = []; 6 | } 7 | 8 | addPermission(permission) { 9 | this.permissions.push(permission); 10 | } 11 | 12 | addRole(role) { 13 | this.subRoles.push(role); 14 | } 15 | 16 | getPermissions() { 17 | let allPermissions = new Set(this.permissions); 18 | 19 | for (let role of this.subRoles) { 20 | for (let permission of role.getPermissions()) { 21 | allPermissions.add(permission); 22 | } 23 | } 24 | 25 | return Array.from(allPermissions); 26 | } 27 | 28 | display(indent = "") { 29 | console.log(`${indent}- Role: ${this.name}`); 30 | console.log(`${indent} Permissions: ${this.permissions.join(", ")}`); 31 | this.subRoles.forEach((subRole) => subRole.display(indent + " ")); 32 | } 33 | } 34 | 35 | // Define roles 36 | const employeeRole = new Role("Employee"); 37 | employeeRole.addPermission("read_documents"); 38 | 39 | const managerRole = new Role("Manager"); 40 | managerRole.addPermission("approve_documents"); 41 | managerRole.addRole(employeeRole); // Manager has all permissions of Employee 42 | 43 | const adminRole = new Role("Administrator"); 44 | adminRole.addPermission("delete_documents"); 45 | adminRole.addRole(managerRole); // Administrator has all permissions of Manager 46 | 47 | // Display role hierarchy and permissions 48 | adminRole.display(); 49 | 50 | console.log("\nAdministrator Permissions:", adminRole.getPermissions()); 51 | console.log("Manager Permissions:", managerRole.getPermissions()); 52 | console.log("Employee Permissions:", employeeRole.getPermissions()); 53 | -------------------------------------------------------------------------------- /05_composite/problem.js: -------------------------------------------------------------------------------- 1 | const roles = { 2 | Employee: { 3 | permissions: ["read_documents"], 4 | }, 5 | Manager: { 6 | permissions: ["approve_documents"], 7 | inherits: ["Employee"], 8 | }, 9 | Administrator: { 10 | permissions: ["delete_documents"], 11 | inherits: ["Manager"], 12 | }, 13 | }; 14 | 15 | function getPermissions(role) { 16 | let allPermissions = new Set(); 17 | 18 | function collectPermissions(roleName) { 19 | if (roles[roleName]) { 20 | roles[roleName].permissions.forEach((permission) => 21 | allPermissions.add(permission) 22 | ); 23 | 24 | if (roles[roleName].inherits) { 25 | roles[roleName].inherits.forEach((inheritedRole) => 26 | collectPermissions(inheritedRole) 27 | ); 28 | } 29 | } 30 | } 31 | 32 | collectPermissions(role); 33 | 34 | return Array.from(allPermissions); 35 | } 36 | 37 | function displayRoleHierarchy(roleName, indent = "") { 38 | if (roles[roleName]) { 39 | console.log(`${indent}- Role: ${roleName}`); 40 | console.log( 41 | `${indent} Permissions: ${roles[roleName].permissions.join(", ")}` 42 | ); 43 | 44 | if (roles[roleName].inherits) { 45 | roles[roleName].inherits.forEach((inheritedRole) => 46 | displayRoleHierarchy(inheritedRole, indent + " ") 47 | ); 48 | } 49 | } 50 | } 51 | 52 | console.log("Administrator Role Hierarchy:"); 53 | displayRoleHierarchy("Administrator"); 54 | 55 | console.log("\nAdministrator Permissions:", getPermissions("Administrator")); 56 | console.log("Manager Permissions:", getPermissions("Manager")); 57 | console.log("Employee Permissions:", getPermissions("Employee")); 58 | -------------------------------------------------------------------------------- /07_command/solution.js: -------------------------------------------------------------------------------- 1 | // Command interface 2 | class Command { 3 | execute() {} 4 | undo() {} 5 | } 6 | 7 | // Concrete command for deducting funds 8 | class DeductFundsCommand extends Command { 9 | constructor(account, amount) { 10 | super(); 11 | this.account = account; 12 | this.amount = amount; 13 | } 14 | 15 | execute() { 16 | console.log(`Deducting ${this.amount} from account ${this.account}`); 17 | } 18 | 19 | undo() { 20 | console.log(`Undoing deduction of ${this.amount} from account ${this.account}`); 21 | } 22 | } 23 | 24 | // Concrete command for crediting funds 25 | class CreditFundsCommand extends Command { 26 | constructor(account, amount) { 27 | super(); 28 | this.account = account; 29 | this.amount = amount; 30 | } 31 | 32 | execute() { 33 | console.log(`Crediting ${this.amount} to account ${this.account}`); 34 | } 35 | 36 | undo() { 37 | console.log(`Undoing credit of ${this.amount} to account ${this.account}`); 38 | } 39 | } 40 | 41 | function executeTransaction(commands) { 42 | console.log("Starting transaction"); 43 | try { 44 | commands.forEach((command) => command.execute()); 45 | console.log("Transaction committed successfully"); 46 | } catch (error) { 47 | console.log("Transaction failed, rolling back"); 48 | rollbackTransaction(commands); 49 | throw error; 50 | } 51 | } 52 | 53 | function rollbackTransaction(commands) { 54 | commands.slice().reverse().forEach((command) => command.undo()); 55 | console.log("Transaction rolled back"); 56 | } 57 | 58 | // Example usage 59 | const commands = [ 60 | new DeductFundsCommand("12345", 100), 61 | new CreditFundsCommand("67890", 100), 62 | ]; 63 | 64 | executeTransaction(commands); -------------------------------------------------------------------------------- /01_factory/README.md: -------------------------------------------------------------------------------- 1 | ### 1. Approach using normal functions 2 | 3 | **Pros:** 4 | 5 | - **Simple and Direct**: This approach uses straightforward functions to handle payments, making it easy to understand and implement. 6 | - **No Overhead**: It does not involve class definitions or factory methods, keeping the codebase lightweight. 7 | - **Immediate Invocation**: Payment processing functions are invoked directly, reducing potential overhead or complexity introduced by class instantiation. 8 | 9 | **Cons:** 10 | 11 | - **Limited Scalability**: Adding new payment gateways would require modifying the `processOrder` function, leading to code duplication or increased complexity. 12 | - **Hardcoded Logic**: Payment processing logic is tightly coupled with the `processOrder` function, making it less flexible for future changes or extensions. 13 | - **Less Reusable**: The payment processing logic is not encapsulated within objects, limiting reusability and maintainability. 14 | 15 | ### 2. Approach using Factory Pattern 16 | 17 | **Pros:** 18 | 19 | - **Modular and Extensible**: Utilizes classes and a factory method pattern, making it easy to add new payment gateways without modifying existing code. 20 | - **Encapsulation**: Payment gateway implementations are encapsulated within their respective classes, promoting cleaner code and separation of concerns. 21 | - **Error Handling**: Incorporates error handling using try-catch blocks, providing more robustness and resilience to unexpected scenarios. 22 | 23 | **Cons:** 24 | 25 | - **Increased Complexity**: Introduction of classes, factory methods, and error handling adds complexity compared to the simpler approach. 26 | - **Potential Overhead**: Class instantiation and method calls may introduce slight overhead compared to direct function invocation. 27 | - **Learning Curve**: Developers unfamiliar with object-oriented programming concepts might find this approach more challenging to grasp initially. -------------------------------------------------------------------------------- /01_factory/oop-solution.js: -------------------------------------------------------------------------------- 1 | class PayPalGateway { 2 | // constructor is a special method that is called when a new instance of the class is created. 3 | constructor() { 4 | this.name = "PayPal"; 5 | } 6 | 7 | // A method that logs a message indicating processing the given amount through PayPal. 8 | processPayment(amount) { 9 | console.log(`Processing ${amount} through ${this.name}`); 10 | } 11 | } 12 | 13 | class StripeGateway { 14 | constructor() { 15 | this.name = "Stripe"; 16 | } 17 | 18 | processPayment(amount) { 19 | console.log(`Processing ${amount} through ${this.name}`); 20 | } 21 | } 22 | 23 | class PaymentFactory { 24 | /** 25 | * 26 | * @param {string} type 27 | * @returns 28 | * 29 | * @note By using the static keyword, the createPaymentGateway() method is defined as a static method of the PaymentFactory class. 30 | * @note This means it belongs to the class itself rather than any specific instance of the class. 31 | * @note Defined to allow it to be accessed directly on the class itself without requiring an instance of the class 32 | */ 33 | static createPaymentGateway(type) { 34 | switch (type) { 35 | case "paypal": 36 | return new PayPalGateway(); 37 | case "stripe": 38 | return new StripeGateway(); 39 | default: 40 | throw new Error("Unsupported payment gateway type"); 41 | } 42 | } 43 | } 44 | 45 | function processOrder(paymentType, amount) { 46 | try { 47 | // Since we defined the createPaymentGateway method as a static method, we can call it directly on the PaymentFactory class. 48 | const paymentGateway = PaymentFactory.createPaymentGateway(paymentType); 49 | // Call the processPayment method of the created payment gateway with the given amount. 50 | paymentGateway.processPayment(amount); 51 | } catch (error) { 52 | console.error(error.message); 53 | } 54 | } 55 | 56 | processOrder("paypal", 100); 57 | processOrder("stripe", 200); 58 | processOrder("unsupported", 300); 59 | --------------------------------------------------------------------------------