This guide provides a quick reference for all design patterns used across the case studies in this project.
Purpose: Ensure a class has only one instance and provide a global point of access to it.
Used In:
- Parking Lot System:
ParkingLotclass ensures only one parking lot instance exists
Example:
class ParkingLot:
instance = None
class __OnlyOne:
def __init__(self, name, address):
self.__name = name
self.__address = address
def __init__(self, name, address):
if not ParkingLot.instance:
ParkingLot.instance = ParkingLot.__OnlyOne(name, address)When to Use:
- When exactly one instance of a class is needed
- When the instance needs to be accessible from multiple points
- When the instance should be extensible by subclassing
Purpose: Define an interface for creating objects, but let subclasses decide which class to instantiate.
Used In:
- Library Management System: Creating different types of books
- Online Shopping System: Creating different product types
- Hotel Management System: Creating different room types
- Airline Management System: Creating flights and reservations
- Movie Ticket Booking System: Creating shows and bookings
- Chess Game: Creating different chess pieces
Example:
class VehicleFactory:
@staticmethod
def create_vehicle(vehicle_type, license_number):
if vehicle_type == VehicleType.CAR:
return Car(license_number)
elif vehicle_type == VehicleType.TRUCK:
return Truck(license_number)
elif vehicle_type == VehicleType.VAN:
return Van(license_number)When to Use:
- When a class can't anticipate the type of objects it needs to create
- When you want to localize the knowledge of which class gets created
- When you want to provide a library of products without exposing implementation
Purpose: Compose objects into tree structures to represent part-whole hierarchies.
Used In:
- Online Shopping System: Shopping cart with multiple items
- Airline Management System: Itinerary with multiple flight reservations
- Hotel Management System: Hotel with multiple locations and rooms
Example:
class ShoppingCart:
def __init__(self):
self.__items = [] # Composite of items
def add_item(self, item):
self.__items.append(item)
def get_total_price(self):
return sum(item.get_price() for item in self.__items)When to Use:
- When you want to represent part-whole hierarchies
- When you want clients to treat individual objects and compositions uniformly
Purpose: Attach additional responsibilities to an object dynamically.
Used In:
- Hotel Management System: Adding amenities and services to rooms
- Online Shopping System: Adding features to products
Example:
class Room:
def __init__(self):
self.__amenities = []
def add_amenity(self, amenity):
self.__amenities.append(amenity)
def get_total_price(self):
base_price = self.__booking_price
amenity_price = sum(a.get_price() for a in self.__amenities)
return base_price + amenity_priceWhen to Use:
- When you want to add responsibilities to objects dynamically
- When extension by subclassing is impractical
Purpose: Define a family of algorithms, encapsulate each one, and make them interchangeable.
Used In:
- Chess Game: Different movement strategies for different pieces
- Parking Lot System: Different parking spot allocation strategies
Example:
class Piece(ABC):
@abstractmethod
def can_move(self, board, start, end):
pass
class King(Piece):
def can_move(self, board, start, end):
# King-specific movement logic
pass
class Queen(Piece):
def can_move(self, board, start, end):
# Queen-specific movement logic
passWhen to Use:
- When you have multiple algorithms for a specific task
- When you want to switch between algorithms at runtime
- When you want to isolate algorithm implementation from the code that uses it
Purpose: Encapsulate a request as an object, thereby letting you parameterize clients with different requests.
Used In:
- ATM System: Different transaction types (Withdraw, Deposit, Transfer, BalanceInquiry)
- Chess Game: Move commands
Example:
class Transaction(ABC):
@abstractmethod
def make_transaction(self):
pass
class Withdraw(Transaction):
def __init__(self, amount):
self.__amount = amount
def make_transaction(self):
# Execute withdrawal logic
pass
class Deposit(Transaction):
def __init__(self, amount):
self.__amount = amount
def make_transaction(self):
# Execute deposit logic
passWhen to Use:
- When you want to parameterize objects with operations
- When you want to queue, log, or support undo operations
- When you want to structure a system around high-level operations
Purpose: Allow an object to alter its behavior when its internal state changes.
Used In:
- Online Shopping System: Order status management
- Airline Management System: Flight status management
- Movie Ticket Booking System: Booking and seat status management
- ATM System: Transaction status management
Example:
class Order:
def __init__(self):
self.__status = OrderStatus.PENDING
def send_for_shipment(self):
if self.__status == OrderStatus.PENDING:
self.__status = OrderStatus.SHIPPED
# Trigger shipment process
else:
raise Exception("Order cannot be shipped in current state")When to Use:
- When an object's behavior depends on its state
- When operations have large conditional statements that depend on object state
- When state-specific behavior should be defined independently
Purpose: Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified.
Used In:
- Online Shopping System: Order status notifications
- Hotel Management System: Booking notifications
- Airline Management System: Flight status updates
- Movie Ticket Booking System: Booking confirmations
- Stack Overflow: Question/answer notifications
- LinkedIn: Connection and post notifications
- Facebook: Friend activity notifications
Example:
class BookReservation:
def __init__(self):
self.__observers = []
def add_observer(self, observer):
self.__observers.append(observer)
def notify_observers(self):
for observer in self.__observers:
observer.update(self)
def send_book_available_notification(self):
self.notify_observers()When to Use:
- When a change to one object requires changing others
- When an object should notify other objects without knowing who they are
- When you want loose coupling between objects
Purpose: Define the skeleton of an algorithm in a method, deferring some steps to subclasses.
Used In:
- Library Management System: Account class with common operations
- ATM System: Transaction processing flow
- Chess Game: Piece movement validation
Example:
class Account(ABC):
def __init__(self, id, password, person, status):
self.__id = id
self.__password = password
self.__status = status
self.__person = person
@abstractmethod
def reset_password(self):
pass
class Librarian(Account):
def reset_password(self):
# Librarian-specific password reset logic
pass
class Member(Account):
def reset_password(self):
# Member-specific password reset logic
passWhen to Use:
- When you want to let subclasses redefine certain steps of an algorithm
- When you want to avoid code duplication
- When you want to control subclass extensions
| System | Singleton | Factory | Composite | Decorator | Strategy | Command | State | Observer | Template |
|---|---|---|---|---|---|---|---|---|---|
| Library Management | ✓ | ✓ | |||||||
| Parking Lot | ✓ | ✓ | ✓ | ||||||
| Online Shopping | ✓ | ✓ | ✓ | ✓ | ✓ | ||||
| Chess Game | ✓ | ✓ | ✓ | ✓ | |||||
| ATM System | ✓ | ✓ | ✓ | ✓ | |||||
| Hotel Management | ✓ | ✓ | ✓ | ✓ | ✓ | ||||
| Airline Management | ✓ | ✓ | ✓ | ✓ | |||||
| Movie Ticket Booking | ✓ | ✓ | ✓ |
All designs follow SOLID principles:
Each class has one reason to change. For example:
BookLendingonly handles lending operationsPaymentonly handles payment processingFlightReservationonly handles flight reservations
Classes are open for extension but closed for modification:
- Adding new vehicle types doesn't require modifying existing
Vehiclecode - Adding new chess pieces doesn't require modifying
Piecebase class - Adding new transaction types doesn't require modifying
Transactionbase class
Derived classes can substitute base classes:
- Any
Vehiclesubclass can be used whereVehicleis expected - Any
Piecesubclass can be used wherePieceis expected - Any
Transactionsubclass can be used whereTransactionis expected
Clients aren't forced to depend on interfaces they don't use:
LibrarianandMemberhave different methods despite sharingAccountbase- Different room types have specific methods beyond base
Roominterface
Depend on abstractions, not concretions:
- Code depends on abstract
Account,Vehicle,Piececlasses - Concrete implementations are injected or created through factories
Problem: One class does too much Solution: Break into smaller, focused classes with single responsibilities
Problem: Classes are too dependent on each other Solution: Use interfaces, dependency injection, and design patterns
Problem: Optimizing before understanding requirements Solution: Focus on clean design first, optimize later if needed
Problem: Hard-coded values without explanation
Solution: Use constants and enums (e.g., AccountStatus, VehicleType)
When discussing design patterns in interviews:
- Identify the Problem First: Understand what problem the pattern solves
- Explain Trade-offs: Every pattern has pros and cons
- Show Alternatives: Discuss why you chose one pattern over another
- Consider Scale: How does the pattern handle growth?
- Real-world Examples: Relate to systems you've built or used
Q: Why use Singleton for ParkingLot? A: Ensures consistent state across all entrance/exit panels. Only one parking lot should exist in the system, managing all floors and spots centrally.
Q: Why Strategy Pattern for chess pieces? A: Each piece has unique movement rules. Strategy pattern allows us to encapsulate these rules and make them interchangeable without modifying the game logic.
Q: Why Command Pattern for ATM transactions? A: Encapsulates each transaction type as an object, making it easy to queue, log, undo, or audit transactions. Also follows Open/Closed principle.
Q: When would you NOT use a Singleton? A: When you need multiple instances, when testing requires different instances, or when the singleton creates tight coupling. Consider dependency injection instead.
- Design Patterns: Elements of Reusable Object-Oriented Software (Gang of Four)
- Head First Design Patterns by Freeman & Freeman
- Refactoring: Improving the Design of Existing Code by Martin Fowler
- Clean Code by Robert C. Martin
Last Updated: 2025-11-03 Version: 1.0