Nguyên lý SOLID
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)
Đị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:
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:
# 1. User class - Chỉ quản lý dữ liệu user
class 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 email
class 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áo
class 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ụng
user = User("An", "an@example.com")
user_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)
Đị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:
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:
from abc import ABC, abstractmethod
# 1. Interface cho discount strategy
class 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 strategy
class DiscountCalculator:
def __init__(self, strategy: DiscountStrategy):
self.strategy = strategy
def calculate_discount(self, amount: float) -> float:
return self.strategy.calculate(amount)
# Sử dụng
regular_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)
Đị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:
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ụng
sparrow = 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:
from abc import ABC, abstractmethod
# 1. Tách interface
class Bird(ABC):
@abstractmethod
def eat(self):
pass
class FlyingBird(Bird):
@abstractmethod
def fly(self):
pass
class WalkingBird(Bird):
@abstractmethod
def walk(self):
pass
# 2. Implementation
class 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 đúng
def make_bird_fly(bird: FlyingBird):
bird.fly()
def make_bird_walk(bird: WalkingBird):
bird.walk()
# Sử dụng
sparrow = 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)
Đị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:
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:
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ần
class 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)
Đị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:
# Low-level module
class 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 module
class 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:
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 abstraction
class 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 abstraction
class 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 database
mysql_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
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:
from abc import ABC, abstractmethod
from typing import List
# DIP - Abstraction cho payment
class PaymentProcessor(ABC):
@abstractmethod
def process_payment(self, amount: float) -> bool:
pass
# DIP - Concrete implementations
class 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 strategy
class 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àng
class 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ý payment
class 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ụng
items = [
{'name': 'Book', 'price': 100, 'quantity': 2},
{'name': 'Pen', 'price': 20, 'quantity': 5}
]
order = Order(items, PercentageDiscount(0.1)) # 10% discount
processor = OrderProcessor(StripePayment())
processor.process(order)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!
Python