Summary
Design Patterns | Description | Use Case |
Singleton Pattern | Ensures that there is only one instance of a class in the entire system. | - Have only one of something in your system. - Share a common resource or configuration. -Control access to a global resource. |
Factory Pattern | Creates objects without showing the complicated creation process to the user. | - Create different objects without knowing their specific classes. - Common way to create objects of a related family. - Hide the complex creation logic from the client code. |
Observer Pattern | Establishes a connection between objects so that when one object changes, all other connected objects get automatically notified and updated. | - Stay updated about changes in a system. -Keep multiple objects in sync. - Establish dynamic relationships between objects. - Support event-driven systems. |
Strategy Pattern | Defines different strategies (ways of doing things) and allows you to switch between them easily. | - Have different ways to solve a problem or perform a task. - Switch between different strategies dynamically. - Keep the code flexible and extensible. |
Decorator Pattern | Adds extra features to an object without changing its original structure. | -Add new functionalities to an object dynamically. - Enhance an object's behavior without modifying its original code. -Avoid having too many subclasses for different combinations of features. |
Understanding Object-Oriented Programming (OOP)
CheckOut!
The Power of Design Patterns
1. Singleton Pattern: Ensuring One Instance
Example
class StockPriceProvider:
__instance = None
latest_price = None
@staticmethod
def get_instance():
if StockPriceProvider.__instance is None:
StockPriceProvider.__instance = StockPriceProvider()
return StockPriceProvider.__instance
def fetch_latest_price(self, symbol):
# Simulated logic to fetch the latest market price for the given stock symbol
# In a real implementation, this would involve querying an API or a database
self.latest_price = self._simulate_fetching_latest_price(symbol)
def _simulate_fetching_latest_price(self, symbol):
# Simulated method to fetch the latest price
# Replace this with your actual logic to fetch the price
price = 0.0
if symbol == "AAPL":
price = 150.25
elif symbol == "GOOGL":
price = 2750.50
elif symbol == "AMZN":
price = 3500.75
return price
def get_latest_price(self):
return self.latest_price
price_provider = StockPriceProvider.get_instance()
price_provider.fetch_latest_price("AAPL")
# In another part of the trading application...
latest_price = StockPriceProvider.get_instance().get_latest_price()
print(f"The latest price for AAPL is: ${latest_price}")
class MusicPlayer:
instance = None
def __init__(self):
if MusicPlayer.instance is not None:
raise Exception("Only one instance of MusicPlayer allowed!")
MusicPlayer.instance = self
self.current_song = None
self.is_playing = False
@staticmethod
def get_instance():
if MusicPlayer.instance is None:
MusicPlayer()
return MusicPlayer.instance
def play_song(self, song):
self.current_song = song
self.is_playing = True
print("Now playing:", self.current_song)
def stop_song(self):
self.current_song = None
self.is_playing = False
print("Playback stopped.")
player = MusicPlayer.get_instance()
player.play_song("Song A") # Output: Now playing: Song A
another_player = MusicPlayer.get_instance()
another_player.play_song("Song B") # Output: Now playing: Song B
player.stop_song() # Output: Playback stopped.
2. Factory Pattern: Creating Objects Dynamically
Example
class Instrument:
def __init__(self, symbol):
self.symbol = symbol
def execute_trade(self):
pass
class Stock(Instrument):
def execute_trade(self):
print(f"Executing stock trade for {self.symbol}")
class Bond(Instrument):
def execute_trade(self):
print(f"Executing bond trade for {self.symbol}")
class Cryptocurrency(Instrument):
def execute_trade(self):
print(f"Executing cryptocurrency trade for {self.symbol}")
class InstrumentFactory:
@staticmethod
def create_instrument(instrument_type, symbol):
if instrument_type == "stock":
return Stock(symbol)
elif instrument_type == "bond":
return Bond(symbol)
elif instrument_type == "cryptocurrency":
return Cryptocurrency(symbol)
# Usage:
instrument_factory = InstrumentFactory()
instrument_type = input("Enter instrument type (stock/bond/cryptocurrency): ")
symbol = input("Enter instrument symbol: ")
instrument = instrument_factory.create_instrument(instrument_type, symbol)
instrument.execute_trade()
class Character:
def __init__(self, name):
self.name = name
def attack(self):
pass
class Warrior(Character):
def attack(self):
print(f"{self.name} swings their mighty sword!")
class Mage(Character):
def attack(self):
print(f"{self.name} casts a powerful spell!")
class Archer(Character):
def attack(self):
print(f"{self.name} shoots arrows with precision!")
class CharacterFactory:
@staticmethod
def create_character(character_type, name):
if character_type == "warrior":
return Warrior(name)
elif character_type == "mage":
return Mage(name)
elif character_type == "archer":
return Archer(name)
# Usage:
character_factory = CharacterFactory()
character_type = input("Enter character type (warrior/mage/archer): ")
character_name = input("Enter character name: ")
character = character_factory.create_character(character_type, character_name)
character.attack()
3. Observer Pattern: Notifying State Changes
Example
class Celebrity:
def __init__(self, name):
self.name = name
self.followers = []
def add_follower(self, follower):
self.followers.append(follower)
def remove_follower(self, follower):
self.followers.remove(follower)
def notify_followers(self, post):
for follower in self.followers:
follower.update(post)
class Follower:
def __init__(self, name):
self.name = name
def update(self, post):
print(f"{self.name} received a new post: {post}")
# Usage:
celebrity = Celebrity("SuperStar")
follower1 = Follower("John")
follower2 = Follower("Sarah")
celebrity.add_follower(follower1)
celebrity.add_follower(follower2)
celebrity.notify_followers("Check out my new photo!")
celebrity.remove_follower(follower2)
celebrity.notify_followers("I just posted a new video!")
class Scoreboard:
def __init__(self):
self.score = 0
self.observers = []
def add_observer(self, observer):
self.observers.append(observer)
def remove_observer(self, observer):
self.observers.remove(observer)
def update_score(self, new_score):
self.score = new_score
self.notify_observers()
def notify_observers(self):
for observer in self.observers:
observer.update(self.score)
class Audience:
def update(self, score):
print(f"The score is now {score}")
# Usage:
scoreboard = Scoreboard()
audience1 = Audience()
audience2 = Audience()
scoreboard.add_observer(audience1)
scoreboard.add_observer(audience2)
scoreboard.update_score(10)
scoreboard.update_score(20)
class Stock:
def __init__(self, symbol):
self.symbol = symbol
self.price = 0.0
self.observers = []
def add_observer(self, observer):
self.observers.append(observer)
def remove_observer(self, observer):
self.observers.remove(observer)
def update_price(self, new_price):
self.price = new_price
self.notify_observers()
def notify_observers(self):
for observer in self.observers:
observer.update(self.symbol, self.price)
class Investor:
def __init__(self, name):
self.name = name
def update(self, symbol, price):
print(f"{self.name}: {symbol} price is now {price}")
# Usage:
stock = Stock("AAPL")
investor1 = Investor("John")
investor2 = Investor("Sarah")
stock.add_observer(investor1)
stock.add_observer(investor2)
stock.update_price(150.25)
stock.update_price(151.50)
4. Strategy Pattern: Flexible Behavior
Example
class PaymentStrategy:
def pay(self, amount):
raise NotImplementedError()
class CreditCardStrategy(PaymentStrategy):
def pay(self, amount):
# Implement credit card payment logic
class PayPalStrategy(PaymentStrategy):
def pay(self, amount):
# Implement PayPal payment logic
class ShoppingCart:
def __init__(self, payment_strategy):
self.payment_strategy = payment_strategy
def process_payment(self, amount):
self.payment_strategy.pay(amount)
class Character:
def __init__(self, name, fighting_strategy):
self.name = name
self.fighting_strategy = fighting_strategy
def attack(self):
self.fighting_strategy.attack()
class FightingStrategy:
def attack(self):
pass
class WarriorStrategy(FightingStrategy):
def attack(self):
print("Swinging a mighty sword!")
class MageStrategy(FightingStrategy):
def attack(self):
print("Casting a powerful spell!")
class ArcherStrategy(FightingStrategy):
def attack(self):
print("Shooting arrows with precision!")
# Usage:
warrior = Character("Brave Warrior", WarriorStrategy())
mage = Character("Mystic Mage", MageStrategy())
archer = Character("Stealthy Archer", ArcherStrategy())
warrior.attack() # Outputs: Swinging a mighty sword!
mage.attack() # Outputs: Casting a powerful spell!
archer.attack() # Outputs: Shooting arrows with precision!
5. Decorator Pattern: Enhancing Objects Dynamically
Example
coffee = SimpleCoffee()
print(coffee.get_description()) # Output: Simple coffee
print(coffee.get_cost()) # Output: 1.0
coffee_with_milk = MilkDecorator(coffee)
print(coffee_with_milk.get_description()) # Output: Simple coffee, milk
print(coffee_with_milk.get_cost()) # Output: 1.5
coffee_with_milk_and_caramel = CaramelDecorator(coffee_with_milk)
print(coffee_with_milk_and_caramel.get_description()) # Output: Simple coffee, milk, caramel
print(coffee_with_milk_and_caramel.get_cost()) # Output: 2.25
class Trade:
def execute(self):
return "Executing trade"
class TradeDecorator:
def __init__(self, trade):
self.trade = trade
def execute(self):
return self.trade.execute()
class StopLossDecorator(TradeDecorator):
def execute(self):
return self.trade.execute() + " with stop loss"
class TakeProfitDecorator(TradeDecorator):
def execute(self):
return self.trade.execute() + " with take profit"
# Usage
basic_trade = Trade() # Create a basic trade
trade_with_stop_loss = StopLossDecorator(basic_trade) # Add stop loss functionality to the trade
trade_with_take_profit = TakeProfitDecorator(basic_trade) # Add take profit functionality to the trade
trade_with_both_features = TakeProfitDecorator(StopLossDecorator(basic_trade)) # Add both stop loss and take profit features
print(basic_trade.execute()) # Output: Executing trade
print(trade_with_stop_loss.execute()) # Output: Executing trade with stop loss
print(trade_with_take_profit.execute()) # Output: Executing trade with take profit
print(trade_with_both_features.execute()) # Output: Executing trade with stop loss with take profit
Conclusion
Go Further!
- freecodecamp: The 3 Types of Design Patterns All Developers Should Know
- LearningDaily.dev: The 7 Most Important Software Design Patterns