Lesson 5
Applying Creational Patterns in a Banking System Using JavaScript
Applying Creational Patterns in Banking System

Welcome back! You've learned so much about creational patterns, and it's time to apply what you know to a real-world project: a banking system. In this unit, we'll focus on using creational patterns to manage and simplify the creation of banking system components.

Quick Pattern Refresher

Before we dive in, let's quickly recap the creational patterns we'll use:

  1. Singleton Pattern: Ensures a class has only one instance and provides a global point of access to it.
  2. Factory Method Pattern: Defines an interface for creating an object but lets subclasses alter the type of objects that will be created.
  3. Abstract Factory Pattern: Provides an interface for creating families of related or dependent objects without specifying their concrete classes.
  4. Builder Pattern: Separates the construction of a complex object from its representation, allowing the same construction process to create various representations.

We will implement these patterns to create a logger, accounts, and account factories for our banking system.

What You'll Build

Let's see what you'll build in this unit. You will create:

  1. Logger with the Singleton Pattern: A logging mechanism that ensures only one instance of the logger exists throughout the application.
  2. Accounts using the Factory Method Pattern: Different types of accounts (savings and current) created via a factory method.
  3. Account Factories using the Abstract Factory Pattern: Factories that will instantiate different types of accounts.
  4. Code Integration: Comprehensive integration of these patterns into a cohesive system.
Singleton Pattern for Logger

First, let's create a logger using the Singleton Pattern. This will ensure that there is only one instance of the logger throughout the application.

JavaScript
1const Logger = (function () { 2 let instance; 3 4 function createInstance() { 5 return { 6 log: function (message) { 7 console.log(message); 8 } 9 }; 10 } 11 12 return { 13 getInstance: function () { 14 if (!instance) { 15 instance = createInstance(); 16 } 17 return instance; 18 } 19 }; 20})();

Here, the Logger function ensures that only one instance of the logger exists. Whenever a message needs to be logged, the single instance is used.

Accounts Using Factory Method Pattern

Next, we will create different types of accounts using the Factory Method Pattern. This allows us to create specific types of accounts while adhering to a common interface.

JavaScript
1class Account { 2 accountType() { 3 throw new Error("This method should be overridden!"); 4 } 5} 6 7class SavingsAccount extends Account { 8 accountType() { 9 return "Savings Account"; 10 } 11} 12 13class CurrentAccount extends Account { 14 accountType() { 15 return "Current Account"; 16 } 17}

In this example, Account is a base class with an accountType method that should be overridden by subclass implementations. SavingsAccount and CurrentAccount provide specific implementations returning messages to indicate their type.

Abstract Factory Pattern for Creating Accounts and Debit Cards

Now, let's move on to the Abstract Factory Pattern to create account factories that will instantiate different types of accounts and associated debit cards.

JavaScript
1// Abstract products (in JavaScript, we just define these as normal classes) 2class DebitCard { 3 cardType() { 4 throw new Error("This method should be overridden!"); 5 } 6} 7 8class SavingsDebitCard extends DebitCard { 9 cardType() { 10 return "Savings Debit Card"; 11 } 12} 13 14class CurrentDebitCard extends DebitCard { 15 cardType() { 16 return "Current Debit Card"; 17 } 18} 19 20// Abstract Factory concept through regular functions 21class SavingsAccountFactory { 22 createAccount() { 23 return new SavingsAccount(); 24 } 25 26 createDebitCard() { 27 return new SavingsDebitCard(); 28 } 29} 30 31class CurrentAccountFactory { 32 createAccount() { 33 return new CurrentAccount(); 34 } 35 36 createDebitCard() { 37 return new CurrentDebitCard(); 38 } 39}

SavingsAccountFactory and CurrentAccountFactory provide factory functions to create instances of SavingsAccount, SavingsDebitCard, CurrentAccount, and CurrentDebitCard, respectively.

Code Integration

To integrate these patterns into a cohesive system, let's see how the different components work together.

JavaScript
1const savingsFactory = new SavingsAccountFactory(); 2const savingsAccount = savingsFactory.createAccount(); 3const savingsDebitCard = savingsFactory.createDebitCard(); 4Logger.getInstance().log(savingsAccount.accountType()); 5Logger.getInstance().log(savingsDebitCard.cardType()); 6// Output: Savings Account 7// Output: Savings Debit Card 8 9const currentFactory = new CurrentAccountFactory(); 10const currentAccount = currentFactory.createAccount(); 11const currentDebitCard = currentFactory.createDebitCard(); 12Logger.getInstance().log(currentAccount.accountType()); 13Logger.getInstance().log(currentDebitCard.cardType()); 14// Output: Current Account 15// Output: Current Debit Card

In this code, we create instances of SavingsAccountFactory and CurrentAccountFactory. We use these factories to create savingsAccount, savingsDebitCard, currentAccount, and currentDebitCard, respectively. Each component's type is logged using the logger.

Complete Code

Here's the complete code putting everything together:

JavaScript
1const Logger = (function () { 2 let instance; 3 4 function createInstance() { 5 return { 6 log: function (message) { 7 console.log(message); 8 } 9 }; 10 } 11 12 return { 13 getInstance: function () { 14 if (!instance) { 15 instance = createInstance(); 16 } 17 return instance; 18 } 19 }; 20})(); 21 22class Account { 23 accountType() { 24 throw new Error("This method should be overridden!"); 25 } 26} 27 28class SavingsAccount extends Account { 29 accountType() { 30 return "Savings Account"; 31 } 32} 33 34class CurrentAccount extends Account { 35 accountType() { 36 return "Current Account"; 37 } 38} 39 40class DebitCard { 41 cardType() { 42 throw new Error("This method should be overridden!"); 43 } 44} 45 46class SavingsDebitCard extends DebitCard { 47 cardType() { 48 return "Savings Debit Card"; 49 } 50} 51 52class CurrentDebitCard extends DebitCard { 53 cardType() { 54 return "Current Debit Card"; 55 } 56} 57 58class SavingsAccountFactory { 59 createAccount() { 60 return new SavingsAccount(); 61 } 62 63 createDebitCard() { 64 return new SavingsDebitCard(); 65 } 66} 67 68class CurrentAccountFactory { 69 createAccount() { 70 return new CurrentAccount(); 71 } 72 73 createDebitCard() { 74 return new CurrentDebitCard(); 75 } 76} 77 78const savingsFactory = new SavingsAccountFactory(); 79const savingsAccount = savingsFactory.createAccount(); 80const savingsDebitCard = savingsFactory.createDebitCard(); 81Logger.getInstance().log(savingsAccount.accountType()); 82Logger.getInstance().log(savingsDebitCard.cardType()); 83// Output: Savings Account 84// Output: Savings Debit Card 85 86const currentFactory = new CurrentAccountFactory(); 87const currentAccount = currentFactory.createAccount(); 88const currentDebitCard = currentFactory.createDebitCard(); 89Logger.getInstance().log(currentAccount.accountType()); 90Logger.getInstance().log(currentDebitCard.cardType()); 91// Output: Current Account 92// Output: Current Debit Card
Conclusion

Understanding creational patterns in the context of a banking system not only strengthens your coding skills but also demonstrates their utility in real-world applications. These patterns help you maintain code quality by ensuring your code is clean, modular, and easy to understand. They encourage reusability, allowing you to reuse common components across different parts of the system, and enhance flexibility by enabling changes and extensions with minimal impact on existing code. By mastering these patterns, you'll be equipped to build robust and scalable systems efficiently.

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.