0
0
Lập trình
TT

Nguyên Tắc Thiết Kế Phần Mềm: Ví Dụ Thực Tiễn Với Python

Đăng vào 1 tháng trước

• 6 phút đọc

Nguyên Tắc Thiết Kế Phần Mềm: Ví Dụ Thực Tiễn Với Python

Tóm tắt: Trong bài viết này, tôi sẽ giải thích các nguyên tắc thiết kế phần mềm quan trọng (SOLID - nhấn mạnh vào SRP và DIP, DRY, KISS, YAGNI) và trình bày một ví dụ thực tiễn đơn giản trong Python: dịch vụ thông báo (email + SMS) được thiết kế để có thể mở rộng, dễ kiểm tra và dễ hiểu.


Các nguyên tắc đã chọn

  • SOLID (SRP, OCP, LSP, ISP, DIP) - nhấn mạnh vào SRP và DIP.
  • DRY (Don't Repeat Yourself).
  • KISS (Keep It Simple, Stupid).
  • YAGNI (You Aren't Gonna Need It).
  • Phân tách trách nhiệm / Khả năng kiểm tra / Tính mô-đun.

Vấn đề thực tế

Chúng ta cần một thành phần gửi thông báo đến người dùng qua nhiều kênh (ví dụ: email và SMS). Các yêu cầu thực tiễn:

  • Có khả năng thêm các kênh mới (Push, Webhook) mà không thay đổi logic chính.
  • Giúp việc kiểm tra đơn vị dễ dàng mà không cần gọi mạng thực.
  • Giữ cho mã nguồn rõ ràng và phân tách trách nhiệm.

Thiết kế (tóm tắt)

  • Định nghĩa một trừu tượng Notifier đại diện cho hợp đồng gửi thông báo.
  • Các triển khai cụ thể (EmailNotifier, SMSNotifier) thực hiện trừu tượng.
  • NotificationServiceđiều phối các notifier thông qua việc tiêm phụ thuộc (nó không biết về các triển khai cụ thể).
  • Các khách hàng bên ngoài (SMTP, nhà cung cấp SMS) được đóng gói và giả lập trong các bài kiểm tra.

Ví dụ mã (các tệp gợi ý: notifiers.py, test_notifiers.py)

Copy
```python
# notifiers.py
from abc import ABC, abstractmethod
from typing import List, Dict

class Notifier(ABC):
    """Hợp đồng: bất kỳ Notifier nào phải triển khai phương thức send."""
    @abstractmethod
    def send(self, to: str, subject: str, body: str) -> bool:
        pass

class EmailNotifier(Notifier):
    """SRP: lớp này chỉ biết cách gửi email."""
    def __init__(self, smtp_client):
        # smtp_client đóng gói logic gửi thực tế
        self.smtp = smtp_client

    def send(self, to: str, subject: str, body: str) -> bool:
        return self.smtp.send_email(to, subject, body)

class SMSNotifier(Notifier):
    def __init__(self, sms_client):
        self.sms = sms_client

    def send(self, to: str, subject: str, body: str) -> bool:
        # Đơn giản hóa: sử dụng subject làm tiền tố trong SMS
        text = f"{subject}: {body}"
        return self.sms.send_sms(to, text)

class NotificationService:
    """DIP: phụ thuộc vào trừu tượng Notifier, không phải các lớp cụ thể."""
    def __init__(self, notifiers: List[Notifier]):
        self.notifiers = notifiers

    def notify_all(self, to: str, subject: str, body: str) -> Dict[str, bool]:
        results = {}
        for n in self.notifiers:
            key = n.__class__.__name__
            results[key] = n.send(to, subject, body)
        return results

# Các client giả lập cho demo/local
class MockSMTPClient:
    def send_email(self, to, subject, body):
        print(f"[MockSMTP] Gửi email đến {to}: {subject} / {body}")
        return True

class MockSMSClient:
    def send_sms(self, to, text):
        print(f"[MockSMS] Gửi SMS đến {to}: {text}")
        return True

if __name__ == "__main__":
    email_notifier = EmailNotifier(MockSMTPClient())
    sms_notifier = SMSNotifier(MockSMSClient())
    svc = NotificationService([email_notifier, sms_notifier])
    result = svc.notify_all("user@example.com", "Chào mừng", "Xin chào, cảm ơn bạn đã đăng ký.")
    print("Kết quả:", result)
```

Kiểm tra (ví dụ với pytest - test_notifiers.py)

Copy
```python
# test_notifiers.py
from notifiers import Notifier, NotificationService

class DummyNotifier(Notifier):
    def __init__(self):
        self.sent = False
    def send(self, to, subject, body):
        self.sent = True
        return True

def test_notification_service_sends_to_all():
    a = DummyNotifier()
    b = DummyNotifier()
    svc = NotificationService([a, b])
    res = svc.notify_all("u@x.com", "t", "b")
    assert res["DummyNotifier"] is True
    assert a.sent and b.sent
```

Cách các nguyên tắc áp dụng ở đây

  • SRP (Single Responsibility): Mỗi lớp có một trách nhiệm duy nhất: EmailNotifier chỉ gửi email, SMSNotifier chỉ gửi SMS, NotificationService chỉ điều phối.
  • DIP (Dependency Inversion): NotificationService phụ thuộc vào Notifier (trừu tượng), không phải các triển khai cụ thể. Điều này cho phép tiêm các đối tượng giả lập để kiểm tra.
  • OCP (Open/Closed): Để thêm PushNotifier, bạn không cần sửa đổi NotificationService; chỉ cần tạo một triển khai mới của Notifier và đăng ký nó.
  • LSP & ISP: Các triển khai tôn trọng hợp đồng send và không buộc phải có các phương thức không cần thiết.
  • DRY: Logic cụ thể của phương tiện được đóng gói, tránh sự trùng lặp.
  • KISS / YAGNI: Thiết kế đơn giản đáp ứng các yêu cầu hiện tại; không có việc thử lại, nhóm lại, hoặc thêm độ phức tạp cho đến khi cần thiết.
  • Khả năng kiểm tra / Tính mô-đun: Bằng cách tiêm các phụ thuộc và sử dụng các client giả lập, các bài kiểm tra có tính xác định và nhanh chóng.

Cách chạy cục bộ

  1. Tạo một môi trường ảo (khuyến nghị):

    Copy
    python -m venv .venv
    source .venv/bin/activate   # Linux/macOS
    .venv\Scripts\activate      # Windows
    pip install pytest
  2. Chạy demo:

    Copy
    python notifiers.py
  3. Chạy các bài kiểm tra:

    Copy
    pytest -q

Kết luận

Ví dụ đơn giản này chứng minh cách áp dụng các nguyên tắc thiết kế để xây dựng một thành phần thông báo có khả năng mở rộng, dễ kiểm tradễ bảo trì. Bằng cách ưu tiên các trừu tượng, phân tách trách nhiệm và sự đơn giản, mã nguồn đã sẵn sàng để phát triển (thêm các kênh, công cụ đo lường, thử lại) mà không trở nên mong manh.


Gợi ý câu hỏi phỏng vấn
Không có dữ liệu

Không có dữ liệu

Bài viết được đề xuất
Bài viết cùng tác giả

Bình luận

Chưa có bình luận nào

Chưa có bình luận nào