Nguyên lý SOLID
SOLID là gì?
Phần tiêu đề “SOLID là gì?”SOLID là 5 nguyên lý thiết kế hướng đối tượng cơ bản, giúp code dễ hiểu, linh hoạt và dễ bảo trì. SOLID là viết tắt của:
- S - Single Responsibility Principle (Nguyên lý đơn trách nhiệm)
- O - Open/Closed Principle (Nguyên lý đóng/mở)
- L - Liskov Substitution Principle (Nguyên lý thay thế Liskov)
- I - Interface Segregation Principle (Nguyên lý phân tách interface)
- D - Dependency Inversion Principle (Nguyên lý đảo ngược phụ thuộc)
Các nguyên lý này được Robert C. Martin (Uncle Bob) đưa ra và đã trở thành nền tảng của thiết kế phần mềm hiện đại.
1. Single Responsibility Principle (SRP)
Phần tiêu đề “1. Single Responsibility Principle (SRP)”Định nghĩa
Phần tiêu đề “Định nghĩa”Một class chỉ nên có một lý do để thay đổi. Một class chỉ nên có một trách nhiệm duy nhất.
❌ Vi phạm SRP:
Phần tiêu đề “❌ Vi phạm SRP:”class User: def __init__(self, name: str, email: str): self.name = name self.email = email
def get_name(self) -> str: return self.name
def save_to_database(self) -> None: """Lưu user vào database.""" import sqlite3 conn = sqlite3.connect('users.db') cursor = conn.cursor() cursor.execute( "INSERT INTO users VALUES (?, ?)", (self.name, self.email) ) conn.commit()
def send_welcome_email(self) -> None: """Gửi email chào mừng.""" import smtplib server = smtplib.SMTP('smtp.gmail.com', 587) server.send_message(...)
def generate_report(self) -> str: """Tạo báo cáo về user.""" return f"User Report: {self.name}, {self.email}"Vấn đề: Class User làm quá nhiều việc:
- Quản lý dữ liệu user
- Lưu vào database
- Gửi email
- Tạo báo cáo
Nếu cách gửi email thay đổi, ta phải sửa class User. Nếu database thay đổi, ta cũng phải sửa class User.
✅ Tuân thủ SRP:
Phần tiêu đề “✅ Tuân thủ SRP:”# 1. User class - Chỉ quản lý dữ liệu userclass User: def __init__(self, name: str, email: str): self.name = name self.email = email
def get_name(self) -> str: return self.name
def get_email(self) -> str: return self.email
# 2. UserRepository - Chịu trách nhiệm lưu trữclass UserRepository: def __init__(self, database_path: str): self.database_path = database_path
def save(self, user: User) -> None: """Lưu user vào database.""" import sqlite3 with sqlite3.connect(self.database_path) as conn: cursor = conn.cursor() cursor.execute( "INSERT INTO users VALUES (?, ?)", (user.name, user.email) ) conn.commit()
# 3. EmailService - Chịu trách nhiệm gửi emailclass EmailService: def __init__(self, smtp_host: str, smtp_port: int): self.smtp_host = smtp_host self.smtp_port = smtp_port
def send_welcome_email(self, user: User) -> None: """Gửi email chào mừng.""" import smtplib with smtplib.SMTP(self.smtp_host, self.smtp_port) as server: # Gửi email logic pass
# 4. UserReportGenerator - Chịu trách nhiệm tạo báo cáoclass UserReportGenerator: def generate(self, user: User) -> str: """Tạo báo cáo về user.""" return f"User Report: {user.name}, {user.email}"
# Sử dụnguser_repo = UserRepository("users.db")email_service = EmailService("smtp.gmail.com", 587)report_generator = UserReportGenerator()
user_repo.save(user)email_service.send_welcome_email(user)report = report_generator.generate(user)Lợi ích:
- Mỗi class có một trách nhiệm rõ ràng
- Dễ kiểm thử từng phần
- Thay đổi một phần không ảnh hưởng phần khác
2. Open/Closed Principle (OCP)
Phần tiêu đề “2. Open/Closed Principle (OCP)”Định nghĩa
Phần tiêu đề “Định nghĩa”Class nên mở cho việc mở rộng (extension) nhưng đóng cho việc sửa đổi (modification).
❌ Vi phạm OCP:
Phần tiêu đề “❌ Vi phạm OCP:”class DiscountCalculator: def calculate(self, customer_type: str, amount: float) -> float: """Tính discount dựa trên loại khách hàng.""" if customer_type == "regular": return amount * 0.05 elif customer_type == "premium": return amount * 0.10 elif customer_type == "vip": return amount * 0.20 return 0
# Vấn đề: Mỗi khi thêm loại khách hàng mới,# phải sửa class DiscountCalculatorVấn đề: Mỗi khi thêm loại khách hàng mới (ví dụ: “gold”), ta phải mở và sửa code của class DiscountCalculator.
✅ Tuân thủ OCP:
Phần tiêu đề “✅ Tuân thủ OCP:”from abc import ABC, abstractmethod
# 1. Interface cho discount strategyclass DiscountStrategy(ABC): @abstractmethod def calculate(self, amount: float) -> float: pass
# 2. Các implementation cụ thểclass RegularDiscount(DiscountStrategy): def calculate(self, amount: float) -> float: return amount * 0.05
class PremiumDiscount(DiscountStrategy): def calculate(self, amount: float) -> float: return amount * 0.10
class VIPDiscount(DiscountStrategy): def calculate(self, amount: float) -> float: return amount * 0.20
# 3. Dễ dàng thêm discount mới mà không sửa code cũclass GoldDiscount(DiscountStrategy): def calculate(self, amount: float) -> float: return amount * 0.15
# 4. Calculator sử dụng strategyclass DiscountCalculator: def __init__(self, strategy: DiscountStrategy): self.strategy = strategy
def calculate_discount(self, amount: float) -> float: return self.strategy.calculate(amount)
# Sử dụngregular_calculator = DiscountCalculator(RegularDiscount())print(regular_calculator.calculate_discount(1000)) # 50
premium_calculator = DiscountCalculator(PremiumDiscount())print(premium_calculator.calculate_discount(1000)) # 100
# Thêm mới mà không sửa code cũgold_calculator = DiscountCalculator(GoldDiscount())print(gold_calculator.calculate_discount(1000)) # 150Lợi ích:
- Thêm tính năng mới mà không sửa code cũ
- Giảm rủi ro phá vỡ code đang hoạt động
- Dễ kiểm thử
3. Liskov Substitution Principle (LSP)
Phần tiêu đề “3. Liskov Substitution Principle (LSP)”Định nghĩa
Phần tiêu đề “Định nghĩa”Các đối tượng của class con phải có thể thay thế được cho đối tượng của class cha mà không làm thay đổi tính đúng đắn của chương trình.
❌ Vi phạm LSP:
Phần tiêu đề “❌ Vi phạm LSP:”class Bird: def fly(self): print("Bird is flying")
class Sparrow(Bird): def fly(self): print("Sparrow is flying")
class Ostrich(Bird): def fly(self): # Đà điểu không bay được! raise Exception("Ostrich cannot fly")
def make_bird_fly(bird: Bird): bird.fly()
# Sử dụngsparrow = Sparrow()make_bird_fly(sparrow) # OK
ostrich = Ostrich()make_bird_fly(ostrich) # Exception! Vi phạm LSPVấn đề: Ostrich không thể thay thế được Bird vì nó không bay được.
✅ Tuân thủ LSP:
Phần tiêu đề “✅ Tuân thủ LSP:”from abc import ABC, abstractmethod
# 1. Tách interfaceclass Bird(ABC): @abstractmethod def eat(self): pass
class FlyingBird(Bird): @abstractmethod def fly(self): pass
class WalkingBird(Bird): @abstractmethod def walk(self): pass
# 2. Implementationclass Sparrow(FlyingBird): def eat(self): print("Sparrow is eating")
def fly(self): print("Sparrow is flying")
class Ostrich(WalkingBird): def eat(self): print("Ostrich is eating")
def walk(self): print("Ostrich is walking")
# 3. Sử dụng đúngdef make_bird_fly(bird: FlyingBird): bird.fly()
def make_bird_walk(bird: WalkingBird): bird.walk()
# Sử dụngsparrow = Sparrow()make_bird_fly(sparrow) # OK
ostrich = Ostrich()make_bird_walk(ostrich) # OK# make_bird_fly(ostrich) # Type error - không compileLợi ích:
- Tránh các hành vi bất ngờ
- Code an toàn hơn
- Dễ reasoning về code
4. Interface Segregation Principle (ISP)
Phần tiêu đề “4. Interface Segregation Principle (ISP)”Định nghĩa
Phần tiêu đề “Định nghĩa”Không nên ép client phụ thuộc vào các interface mà chúng không sử dụng.
❌ Vi phạm ISP:
Phần tiêu đề “❌ Vi phạm ISP:”from abc import ABC, abstractmethod
class Worker(ABC): @abstractmethod def work(self): pass
@abstractmethod def eat(self): pass
@abstractmethod def sleep(self): pass
class Human(Worker): def work(self): print("Human working")
def eat(self): print("Human eating")
def sleep(self): print("Human sleeping")
class Robot(Worker): def work(self): print("Robot working")
def eat(self): # Robot không ăn! pass
def sleep(self): # Robot không ngủ! passVấn đề: Robot bị ép phải implement eat() và sleep() mà nó không cần.
✅ Tuân thủ ISP:
Phần tiêu đề “✅ Tuân thủ ISP:”from abc import ABC, abstractmethod
# 1. Tách thành nhiều interface nhỏclass Workable(ABC): @abstractmethod def work(self): pass
class Eatable(ABC): @abstractmethod def eat(self): pass
class Sleepable(ABC): @abstractmethod def sleep(self): pass
# 2. Class implement những gì cầnclass Human(Workable, Eatable, Sleepable): def work(self): print("Human working")
def eat(self): print("Human eating")
def sleep(self): print("Human sleeping")
class Robot(Workable): def work(self): print("Robot working")
# Robot chỉ implement Workable, không cần Eatable và SleepableLợi ích:
- Class chỉ implement những gì nó thực sự cần
- Giảm coupling
- Dễ bảo trì
5. Dependency Inversion Principle (DIP)
Phần tiêu đề “5. Dependency Inversion Principle (DIP)”Định nghĩa
Phần tiêu đề “Định nghĩa”
- Các module cấp cao không nên phụ thuộc vào các module cấp thấp. Cả hai nên phụ thuộc vào abstraction.
- Abstraction không nên phụ thuộc vào details. Details nên phụ thuộc vào abstraction.
❌ Vi phạm DIP:
Phần tiêu đề “❌ Vi phạm DIP:”# Low-level moduleclass MySQLDatabase: def connect(self): print("Connecting to MySQL")
def save_data(self, data): print(f"Saving {data} to MySQL")
# High-level module phụ thuộc trực tiếp vào low-level moduleclass UserService: def __init__(self): self.database = MySQLDatabase() # Phụ thuộc cứng vào MySQL
def save_user(self, user_data): self.database.connect() self.database.save_data(user_data)
# Vấn đề: Nếu muốn đổi sang PostgreSQL, phải sửa UserServiceVấn đề: UserService (high-level) phụ thuộc trực tiếp vào MySQLDatabase (low-level). Khó thay đổi database.
✅ Tuân thủ DIP:
Phần tiêu đề “✅ Tuân thủ DIP:”from abc import ABC, abstractmethod
# 1. Abstraction (Interface)class Database(ABC): @abstractmethod def connect(self): pass
@abstractmethod def save_data(self, data): pass
# 2. Low-level modules implement abstractionclass MySQLDatabase(Database): def connect(self): print("Connecting to MySQL")
def save_data(self, data): print(f"Saving {data} to MySQL")
class PostgreSQLDatabase(Database): def connect(self): print("Connecting to PostgreSQL")
def save_data(self, data): print(f"Saving {data} to PostgreSQL")
class MongoDBDatabase(Database): def connect(self): print("Connecting to MongoDB")
def save_data(self, data): print(f"Saving {data} to MongoDB")
# 3. High-level module phụ thuộc vào abstractionclass UserService: def __init__(self, database: Database): # Dependency Injection self.database = database
def save_user(self, user_data): self.database.connect() self.database.save_data(user_data)
# Sử dụng - Dễ dàng thay đổi databasemysql_service = UserService(MySQLDatabase())mysql_service.save_user({"name": "An"})
postgres_service = UserService(PostgreSQLDatabase())postgres_service.save_user({"name": "Bình"})
mongo_service = UserService(MongoDBDatabase())mongo_service.save_user({"name": "Chi"})Lợi ích:
- Dễ thay đổi implementation
- Dễ kiểm thử (có thể inject mock database)
- Giảm coupling giữa các module
Tổng kết
Phần tiêu đề “Tổng kết”So sánh các nguyên lý SOLID:
Phần tiêu đề “So sánh các nguyên lý SOLID:”| Nguyên lý | Mục đích | Khi nào áp dụng |
|---|---|---|
| SRP | Một class một trách nhiệm | Class làm quá nhiều việc |
| OCP | Mở cho mở rộng, đóng cho sửa đổi | Thường xuyên thêm tính năng mới |
| LSP | Class con thay thế được class cha | Thiết kế kế thừa |
| ISP | Interface nhỏ, tập trung | Interface quá lớn, nhiều method |
| DIP | Phụ thuộc vào abstraction | Cần linh hoạt thay đổi implementation |
Ví dụ tổng hợp SOLID:
Phần tiêu đề “Ví dụ tổng hợp SOLID:”from abc import ABC, abstractmethodfrom typing import List
# DIP - Abstraction cho paymentclass PaymentProcessor(ABC): @abstractmethod def process_payment(self, amount: float) -> bool: pass
# DIP - Concrete implementationsclass StripePayment(PaymentProcessor): def process_payment(self, amount: float) -> bool: print(f"Processing ${amount} via Stripe") return True
class PayPalPayment(PaymentProcessor): def process_payment(self, amount: float) -> bool: print(f"Processing ${amount} via PayPal") return True
# OCP - Discount strategyclass DiscountStrategy(ABC): @abstractmethod def calculate(self, amount: float) -> float: pass
class NoDiscount(DiscountStrategy): def calculate(self, amount: float) -> float: return 0
class PercentageDiscount(DiscountStrategy): def __init__(self, percentage: float): self.percentage = percentage
def calculate(self, amount: float) -> float: return amount * self.percentage
# SRP - Order chỉ quản lý dữ liệu đơn hàngclass Order: def __init__(self, items: List[dict], discount: DiscountStrategy): self.items = items self.discount = discount self.total = self._calculate_total()
def _calculate_total(self) -> float: subtotal = sum(item['price'] * item['quantity'] for item in self.items) discount_amount = self.discount.calculate(subtotal) return subtotal - discount_amount
# SRP - OrderProcessor xử lý paymentclass OrderProcessor: def __init__(self, payment_processor: PaymentProcessor): self.payment_processor = payment_processor
def process(self, order: Order) -> bool: return self.payment_processor.process_payment(order.total)
# Sử dụngitems = [ {'name': 'Book', 'price': 100, 'quantity': 2}, {'name': 'Pen', 'price': 20, 'quantity': 5}]
order = Order(items, PercentageDiscount(0.1)) # 10% discountprocessor = OrderProcessor(StripePayment())processor.process(order)Lợi ích khi áp dụng SOLID:
Phần tiêu đề “Lợi ích khi áp dụng SOLID:”✅ Dễ bảo trì: Code rõ ràng, dễ hiểu ✅ Dễ mở rộng: Thêm tính năng mới mà không sửa code cũ ✅ Dễ kiểm thử: Có thể inject mock dependencies ✅ Giảm coupling: Các module độc lập với nhau ✅ Tăng reusability: Code có thể tái sử dụng
SOLID là nền tảng của thiết kế phần mềm tốt. Hãy thực hành thường xuyên để thành thạo!