These notes cover all essential concepts, principles, and patterns required for Low-Level Design (LLD). It includes explanations, examples, and diagrams to make the understanding and implementation process seamless.
1. Object-Oriented Programming (OOP) Principles
1.1 Encapsulation
Encapsulation hides the internal details of a class and exposes only what is necessary.
Example:
class BankAccount:
def __init__(self, balance):
self.__balance = balance # Private variable
def deposit(self, amount):
self.__balance += amount
def withdraw(self, amount):
if amount > self.__balance:
raise ValueError("Insufficient balance")
self.__balance -= amount
def get_balance(self):
return self.__balance
1.2 Abstraction
Abstraction simplifies complex systems by modeling classes relevant to the context while hiding unnecessary details.
Example:
from abc import ABC, abstractmethod
class Payment(ABC):
@abstractmethod
def make_payment(self, amount):
pass
class CreditCardPayment(Payment):
def make_payment(self, amount):
print(f"Paid {amount} using Credit Card")
class PayPalPayment(Payment):
def make_payment(self, amount):
print(f"Paid {amount} using PayPal")
1.3 Inheritance
Inheritance allows one class to acquire the properties and methods of another.
Example:
class Animal:
def eat(self):
print("This animal eats food.")
class Dog(Animal):
def bark(self):
print("The dog barks.")
# Usage
dog = Dog()
dog.eat() # Inherited method
dog.bark()
1.4 Polymorphism
Polymorphism allows different classes to be treated as instances of the same base class.
Example:
class Bird:
def sound(self):
pass
class Sparrow(Bird):
def sound(self):
print("Chirp chirp")
class Crow(Bird):
def sound(self):
print("Caw caw")
# Polymorphism in action
def make_sound(bird):
bird.sound()
make_sound(Sparrow())
make_sound(Crow())
2. SOLID Principles
2.1 Single Responsibility Principle (SRP)
A class should have only one responsibility.
Example:
class Invoice:
def calculate_total(self):
# Logic for calculating total
pass
class InvoicePrinter:
def print_invoice(self, invoice):
# Logic for printing the invoice
pass
2.2 Open/Closed Principle (OCP)
A class should be open for extension but closed for modification.
Example:
class Discount:
def calculate(self, amount):
return amount
class SeasonalDiscount(Discount):
def calculate(self, amount):
return amount * 0.9 # 10% off
class ClearanceDiscount(Discount):
def calculate(self, amount):
return amount * 0.7 # 30% off
# Extendable without modifying the base class
2.3 Liskov Substitution Principle (LSP)
Subtypes must be substitutable for their base types.
Example:
class Bird:
def fly(self):
print("This bird can fly")
class Sparrow(Bird):
pass
class Ostrich(Bird):
def fly(self):
raise Exception("Ostriches can't fly")
# Violation: Ostrich does not conform to Bird's behavior
2.4 Interface Segregation Principle (ISP)
A class should not implement methods it does not use.
Example:
class Printer:
def print_document(self):
pass
class Scanner:
def scan_document(self):
pass
class MultiFunctionDevice(Printer, Scanner):
def print_document(self):
print("Printing document")
def scan_document(self):
print("Scanning document")
2.5 Dependency Inversion Principle (DIP)
High-level modules should not depend on low-level modules. Both should depend on abstractions.
Example:
class Keyboard:
def input(self):
return "User input from keyboard"
class Monitor:
def display(self, content):
print(content)
class Computer:
def __init__(self, keyboard: Keyboard, monitor: Monitor):
self.keyboard = keyboard
self.monitor = monitor
def compute(self):
data = self.keyboard.input()
self.monitor.display(data)
3. Design Patterns
3.1 Creational Patterns
- Singleton: Ensures a class has only one instance.
Example:
class Singleton:
_instance = None
@staticmethod
def get_instance():
if Singleton._instance is None:
Singleton._instance = Singleton()
return Singleton._instance
- Factory: Provides a way to create objects without specifying the exact class.
Example:
class ShapeFactory:
@staticmethod
def get_shape(type):
if type == "circle":
return Circle()
elif type == "square":
return Square()
3.2 Structural Patterns
- Adapter: Allows incompatible interfaces to work together.
Example:
class EuropeanSocket:
def voltage(self):
return 220
class AmericanSocket:
def voltage(self):
return 110
class Adapter:
def __init__(self, socket):
self.socket = socket
def get_voltage(self):
return self.socket.voltage()
- Decorator: Adds responsibilities to objects dynamically.
Example:
class Coffee:
def cost(self):
return 5
class MilkDecorator:
def __init__(self, coffee):
self.coffee = coffee
def cost(self):
return self.coffee.cost() + 2
coffee = MilkDecorator(Coffee())
print(coffee.cost())
3.3 Behavioral Patterns
- Observer: Defines a dependency between objects so that when one changes, others are notified.
Example:
class Subject:
def __init__(self):
self._observers = []
def attach(self, observer):
self._observers.append(observer)
def notify(self):
for observer in self._observers:
observer.update()
class Observer:
def update(self):
pass
- Strategy: Encapsulates algorithms and allows them to be interchangeable.
Example:
class Strategy:
def execute(self):
pass
class StrategyA(Strategy):
def execute(self):
print("Strategy A")
class StrategyB(Strategy):
def execute(self):
print("Strategy B")
# Usage
strategy = StrategyA()
strategy.execute()
4. UML Diagrams
- Class Diagram: Represents the structure of a system (classes, attributes, methods, and relationships).
- Sequence Diagram: Illustrates the interaction between objects over time.
- State Diagram: Describes state transitions of a system.
This comprehensive guide provides a complete understanding of Low-Level Design (LLD) with principles, patterns, and examples. Use this as a one-stop reference for interviews and implementation.