Design patterndesign principles là hai khái niệm quan trọng trong thiết kế phần mềm, nhưng chúng có vai trò và ý nghĩa khác nhau. Để phân biệt rõ ràng, chúng ta cần xem xét mục đích, bản chất, và cách áp dụng của từng khái niệm:

1. Mục đích

  • Design principles: Là những nguyên tắc cơ bản trong việc thiết kế phần mềm, nhằm đảm bảo mã nguồn có cấu trúc tốt, dễ bảo trì, dễ mở rộng và ít lỗi. Chúng đóng vai trò định hướng, giúp lập trình viên phát triển hệ thống có tính ổn định cao, dễ thay đổi mà không làm ảnh hưởng đến phần còn lại của hệ thống. Một ví dụ tiêu biểu là các nguyên tắc SOLID (Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation, và Dependency Inversion).
  • Design patterns: Là các giải pháp có cấu trúc cụ thể, được chứng minh là hiệu quả cho các vấn đề phổ biến trong thiết kế phần mềm. Design patterns cung cấp các khuôn mẫu để giải quyết những vấn đề lặp lại, giúp tiết kiệm thời gian suy nghĩ về cách thiết kế và đảm bảo rằng các vấn đề được giải quyết theo cách tốt nhất đã được kiểm nghiệm. Một số design patterns phổ biến là Singleton, Factory, Observer, và Command.

2. Bản chất

  • Design principles: Là các hướng dẫn hoặc triết lý trừu tượng giúp định hình cách lập trình viên suy nghĩ về kiến trúc phần mềm. Chúng không cụ thể về cách cài đặt, mà chủ yếu hướng đến việc tư duy đúng cách trong quá trình thiết kế. Các nguyên tắc này giúp đảm bảo rằng hệ thống được thiết kế một cách rõ ràng, không phụ thuộc quá mức giữa các thành phần và dễ dàng bảo trì.
  • Design patterns: Là các giải pháp cụ thể và được trình bày dưới dạng sơ đồ, mã nguồn hoặc ví dụ về cách giải quyết một vấn đề nào đó trong thiết kế. Design patterns là các mẫu thiết kế đã được kiểm chứng qua nhiều lần sử dụng thực tế. Mỗi design pattern thường có các lớp, đối tượng cụ thể, mô tả về cách chúng tương tác với nhau để giải quyết một bài toán cụ thể trong lập trình.

3. Cách áp dụng

  • Design principles: Được áp dụng xuyên suốt quá trình thiết kế phần mềm và thường được xem xét ngay từ đầu khi lập kế hoạch kiến trúc của hệ thống. Chúng giúp định hình hệ thống tổng thể, đảm bảo mã nguồn không bị rối và dễ thay đổi khi có yêu cầu mới. Các nguyên tắc này được áp dụng ở mọi cấp độ của phần mềm, từ thiết kế nhỏ nhất như hàm hoặc lớp, cho đến toàn bộ hệ thống.

Ví dụ:

  • Nguyên tắc Single Responsibility Principle (SRP) hướng dẫn rằng mỗi lớp chỉ nên có một lý do duy nhất để thay đổi, tức là nó chỉ nên thực hiện một nhiệm vụ cụ thể.
  • Nguyên tắc Dependency Inversion Principle (DIP) đề xuất rằng các module cấp cao không nên phụ thuộc trực tiếp vào các module cấp thấp mà nên dựa vào abstraction.
  • Design patterns: Được sử dụng khi lập trình viên nhận ra rằng một vấn đề họ đang gặp phải đã có sẵn một giải pháp được ghi nhận dưới dạng pattern. Khi đó, họ sẽ áp dụng pattern cụ thể để giải quyết bài toán. Không phải tất cả mọi tình huống đều cần sử dụng design pattern, chỉ khi lập trình viên gặp phải một vấn đề phù hợp thì họ mới áp dụng pattern đã có.

Ví dụ:

  • Khi cần quản lý trạng thái của hệ thống theo một mô hình nhiều lớp và cần đảm bảo rằng chỉ có một phiên bản của một đối tượng được sử dụng, bạn có thể sử dụng Singleton Pattern.
  • Khi cần thiết kế hệ thống với khả năng khởi tạo các đối tượng mà không phụ thuộc vào lớp cụ thể của chúng, bạn có thể áp dụng Factory Pattern.

4. Mức độ trừu tượng

  • Design principles: Là những nguyên tắc mang tính trừu tượng cao, chúng không đưa ra các phương pháp cụ thể để giải quyết vấn đề, mà chỉ định hướng suy nghĩ để lập trình viên thiết kế phần mềm tốt hơn. Bạn không thể viết mã dựa trên các nguyên tắc này mà cần áp dụng tư duy từ nguyên tắc vào thiết kế.

Ví dụ:

  • Nguyên tắc Open/Closed Principle nói rằng các lớp nên mở rộng (open for extension) nhưng đóng kín để sửa đổi (closed for modification). Điều này không cụ thể hóa cách viết mã, nhưng gợi ý cách tổ chức hệ thống sao cho không phải chỉnh sửa mã hiện có mỗi khi cần thêm chức năng mới.
  • Design patterns: Là các giải pháp đã được định nghĩa rõ ràng với cách cài đặt cụ thể. Chúng có tính cụ thể hơn nhiều so với principles. Các lập trình viên có thể dễ dàng dựa vào các mẫu này để hiện thực hóa giải pháp cho vấn đề mà họ gặp phải trong quá trình lập trình.

Ví dụ:

  • Observer Pattern cung cấp một sơ đồ chi tiết về cách các đối tượng “quan sát” (observer) có thể được thông báo khi có sự thay đổi trạng thái của đối tượng “chủ” (subject), và cách thức cài đặt nó trong mã nguồn.

5. Ví dụ minh họa

Design principles (SOLID)

  • Single Responsibility Principle (SRP): Một lớp chỉ nên có một lý do để thay đổi.
  • Open/Closed Principle (OCP): Lớp nên mở rộng cho việc thêm tính năng, nhưng đóng kín để sửa đổi.
  • Liskov Substitution Principle (LSP): Các đối tượng của lớp con có thể thay thế cho lớp cha mà không làm thay đổi tính đúng đắn của chương trình.
  • Interface Segregation Principle (ISP): Nên tách biệt các interface lớn thành nhiều interface nhỏ, chuyên biệt.
  • Dependency Inversion Principle (DIP): Các lớp cấp cao không nên phụ thuộc vào các lớp cấp thấp, cả hai nên phụ thuộc vào abstraction.

Design patterns

  • Factory Pattern: Cung cấp một interface để tạo các đối tượng mà không cần xác định lớp cụ thể của chúng trong mã nguồn.
  • Observer Pattern: Giúp tạo ra một cơ chế thông báo giữa các đối tượng, khi một đối tượng thay đổi thì các đối tượng phụ thuộc sẽ được thông báo.
  • Singleton Pattern: Đảm bảo rằng chỉ có một phiên bản duy nhất của một lớp được tạo ra và cung cấp một điểm truy cập toàn cục tới nó.
  • Command Pattern: Đóng gói một yêu cầu vào một đối tượng, cho phép tách biệt giữa người phát lệnh và người thực thi.

Kết luận

Design principles là những nguyên tắc chung hướng dẫn cách lập trình viên thiết kế phần mềm sao cho dễ bảo trì, dễ mở rộng và dễ thay đổi, trong khi design patterns là những giải pháp cụ thể giúp giải quyết các vấn đề lặp đi lặp lại trong thiết kế phần mềm. Mặc dù cả hai đều nhằm mục đích giúp mã nguồn tốt hơn, nhưng principles thiên về tư duy và triết lý thiết kế, còn patterns là các công cụ thực tiễn để giải quyết các bài toán cụ thể trong quá trình phát triển phần mềm.