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.
Before we dive in, let's quickly recap the creational patterns we'll use:
- Singleton Pattern: Ensures a class has only one instance and provides a global point of access to it.
- Factory Method Pattern: Defines an interface for creating an object but allows subclasses to alter the type of objects that will be created.
- Abstract Factory Pattern: Provides an interface for creating families of related or dependent objects without specifying their concrete classes.
- 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.
Let's see what you'll build in this unit. You will create:
- Logger with the Singleton Pattern: A logging mechanism that ensures only one instance of the logger exists throughout the application.
- Accounts using the Factory Method Pattern: Different types of accounts (
savings
andcurrent
) created via a factory method. - Account Factories using the Abstract Factory Pattern: Factories that will instantiate different types of accounts.
- Code Integration: Comprehensive integration of these patterns into a cohesive system.
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.
Python1class Logger: 2 __instance = None 3 4 @staticmethod 5 def getInstance(): 6 if Logger.__instance == None: 7 Logger.__instance = Logger() 8 return Logger.__instance 9 10 def log(self, message): 11 print(message)
Here, the Logger
class ensures that only one instance of the logger exists. Whenever a message needs to be logged, the single instance is used.
Next, we will create different types of accounts using the Factory Method Pattern. This will allow us to create specific types of accounts while adhering to a common interface.
Python1from abc import ABC, abstractmethod 2 3class Account(ABC): 4 @abstractmethod 5 def account_type(self): 6 pass 7 8class SavingsAccount(Account): 9 def account_type(self): 10 return "Savings Account" 11 12class CurrentAccount(Account): 13 def account_type(self): 14 return "Current Account"
In this example, Account
is an abstract class with an account_type
method that needs to be implemented by its subclasses. SavingsAccount
and CurrentAccount
provide specific implementations of the account_type
method, returning messages to indicate their type.
Now, let's move on to the Abstract Factory Pattern to create account factories, which will instantiate different types of accounts and associated debit cards.
Python1from abc import ABC, abstractmethod 2 3# Abstract products 4class Account(ABC): 5 @abstractmethod 6 def account_type(self): 7 pass 8 9class DebitCard(ABC): 10 @abstractmethod 11 def card_type(self): 12 pass 13 14# Concrete products for Savings Account 15class SavingsAccount(Account): 16 def account_type(self): 17 return "Savings Account" 18 19class SavingsDebitCard(DebitCard): 20 def card_type(self): 21 return "Savings Debit Card" 22 23# Concrete products for Current Account 24class CurrentAccount(Account): 25 def account_type(self): 26 return "Current Account" 27 28class CurrentDebitCard(DebitCard): 29 def card_type(self): 30 return "Current Debit Card" 31 32# Abstract Factory 33class AccountFactory(ABC): 34 @abstractmethod 35 def create_account(self): 36 pass 37 38 @abstractmethod 39 def create_debit_card(self): 40 pass 41 42# Concrete Factory for Savings Account 43class SavingsAccountFactory(AccountFactory): 44 def create_account(self): 45 return SavingsAccount() 46 47 def create_debit_card(self): 48 return SavingsDebitCard() 49 50# Concrete Factory for Current Account 51class CurrentAccountFactory(AccountFactory): 52 def create_account(self): 53 return CurrentAccount() 54 55 def create_debit_card(self): 56 return CurrentDebitCard()
Here, AccountFactory
is an abstract factory that defines the methods create_account
and create_debit_card
. SavingsAccountFactory
and CurrentAccountFactory
are concrete factories that override these methods to return instances of SavingsAccount
and SavingsDebitCard
, and CurrentAccount
and CurrentDebitCard
respectively.
To integrate these patterns into a cohesive system, let's see how the different components work together.
Python1if __name__ == "__main__": 2 savings_factory = SavingsAccountFactory() 3 savings_account = savings_factory.create_account() 4 savings_debit_card = savings_factory.create_debit_card() 5 Logger.getInstance().log(savings_account.account_type()) 6 Logger.getInstance().log(savings_debit_card.card_type()) 7 # Output: Savings Account 8 # Output: Savings Debit Card 9 10 current_factory = CurrentAccountFactory() 11 current_account = current_factory.create_account() 12 current_debit_card = current_factory.create_debit_card() 13 Logger.getInstance().log(current_account.account_type()) 14 Logger.getInstance().log(current_debit_card.card_type()) 15 # Output: Current Account 16 # Output: Current Debit Card
In this code, we create instances of SavingsAccountFactory
and CurrentAccountFactory
. We use these factories to create savings_account
, savings_debit_card
, current_account
, and current_debit_card
, respectively. Each component's type is logged using the logger.
Here's the complete code putting everything together:
Python1from abc import ABC, abstractmethod 2 3class Logger: 4 __instance = None 5 6 @staticmethod 7 def getInstance(): 8 if Logger.__instance == None: 9 Logger.__instance = Logger() 10 return Logger.__instance 11 12 def log(self, message): 13 print(message) 14 15# Abstract products 16class Account(ABC): 17 @abstractmethod 18 def account_type(self): 19 pass 20 21class DebitCard(ABC): 22 @abstractmethod 23 def card_type(self): 24 pass 25 26# Concrete products for Savings Account 27class SavingsAccount(Account): 28 def account_type(self): 29 return "Savings Account" 30 31class SavingsDebitCard(DebitCard): 32 def card_type(self): 33 return "Savings Debit Card" 34 35# Concrete products for Current Account 36class CurrentAccount(Account): 37 def account_type(self): 38 return "Current Account" 39 40class CurrentDebitCard(DebitCard): 41 def card_type(self): 42 return "Current Debit Card" 43 44# Abstract Factory 45class AccountFactory(ABC): 46 @abstractmethod 47 def create_account(self): 48 pass 49 50 @abstractmethod 51 def create_debit_card(self): 52 pass 53 54# Concrete Factory for Savings Account 55class SavingsAccountFactory(AccountFactory): 56 def create_account(self): 57 return SavingsAccount() 58 59 def create_debit_card(self): 60 return SavingsDebitCard() 61 62# Concrete Factory for Current Account 63class CurrentAccountFactory(AccountFactory): 64 def create_account(self): 65 return CurrentAccount() 66 67 def create_debit_card(self): 68 return CurrentDebitCard() 69 70if __name__ == "__main__": 71 savings_factory = SavingsAccountFactory() 72 savings_account = savings_factory.create_account() 73 savings_debit_card = savings_factory.create_debit_card() 74 Logger.getInstance().log(savings_account.account_type()) 75 Logger.getInstance().log(savings_debit_card.card_type()) 76 # Output: Savings Account 77 # Output: Savings Debit Card 78 79 current_factory = CurrentAccountFactory() 80 current_account = current_factory.create_account() 81 current_debit_card = current_factory.create_debit_card() 82 Logger.getInstance().log(current_account.account_type()) 83 Logger.getInstance().log(current_debit_card.card_type()) 84 # Output: Current Account 85 # Output: Current Debit Card
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.