Skip to Content

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 DiscountCalculator

Vấ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)) # 150

Lợ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 LSP

Vấ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 compile

Lợ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ủ! pass

Vấn đề: Robot bị ép phải implement eat()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à Sleepable

Lợ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

  1. 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.
  2. 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 UserService

Vấ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 đíchKhi nào áp dụng
SRPMột class một trách nhiệmClass làm quá nhiều việc
OCPMở cho mở rộng, đóng cho sửa đổiThường xuyên thêm tính năng mới
LSPClass con thay thế được class chaThiết kế kế thừa
ISPInterface nhỏ, tập trungInterface quá lớn, nhiều method
DIPPhụ thuộc vào abstractionCầ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!

Last updated on