[Công nghệ Thông tin] -- [Web] -- [Công nghệ phần mềm] -- [PhoThong] -- [TỪ ĐIỂN] -- [Game] -- [Học viên cũ] -- [10.000 giờ]
--------------- <> -----------------
---  KHOA HỌC - CÔNG NGHỆ - GIÁO DỤC - VIỆC LÀM ---
---  Nhận làm website, web app, chạy quảng cáo, digital marketing --->>>  LIÊN HỆ...

Tìm kiếm trong Blog

Phân tích và thiết kế PM (7) - Nguyên tắc thiết kế

Bài trước: Phân tích và thiết kế PM (6) - Kiến trúc phần mềm (bài tập)
-----

6. Nguyên tắc thiết kế

6.1 Nguyên tắc SOLID

Nguyên tắc SOLID là một bộ năm nguyên tắc thiết kế hướng đối tượng (OOP) do Robert C. Martin (Bob Martin) và Michael Feathers giới thiệu, nhằm giúp các lập trình viên xây dựng hệ thống phần mềm dễ đọc, dễ hiểu, dễ kiểm tra, dễ bảo trì và mở rộng.


SOLID là từ viết tắt của 5 nguyên tắc sau:

Chữ cái

Nguyên tắc (tiếng Anh)

Nguyên tắc (tiếng Việt)

Nội dung tóm tắt

S

Single Responsibility Principle (SRP)

Nguyên tắc Trách nhiệm duy nhất

Một lớp (class) hoặc mô-đun (module) chỉ nên có một lý do duy nhất để thay đổi. (Tức là chỉ chịu trách nhiệm cho một công việc cụ thể)

O

Open/Closed Principle (OCP)

Nguyên tắc Mở/Đóng

Một thực thể (class, module, function) nên mở để mở rộng, nhưng đóng để sửa đổi. (Thêm chức năng mới mà không cần thay đổi mã nguồn hiện có)

L

Liskov Substitution Principle (LSP)

Nguyên tắc Thay thế Liskov

Các đối tượng của lớp con có thể thay thế cho các đối tượng của lớp cha mà không làm thay đổi tính đúng đắn của chương trình

I

Interface Segregation Principle (ISP)

Nguyên tắc Phân tách giao diện

Thay vì sử dụng một giao diện (interface) lớn, cồng kềnh, nên tách nó thành nhiều giao diện nhỏ hơn, cụ thể hơn

D

Dependency Inversion Principle (DIP)

Nguyên tắc Đảo ngược phụ thuộc

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 (trừu tượng hóa - Interface/Abstract Class)

6.2 Áp dụng nguyên tắc SOLID

Để dễ hiểu và có thêm trải nghiệm với nguyên tắc SOLID, chúng ta cùng áp dụng nguyên tắc này vào việc thiết kế Hệ thống bán sách trực tuyến.

Thiết kế theo nguyên tắc SOLID sẽ giúp Hệ thống bán sách trực tuyến có mã nguồn sạch (clean code), dễ bảo trì; đặc biệt để đáp ứng các Yêu cầu phi chức năng (NFR) như khả năng mở rộng (NFR-5.2) và độ tin cậy (NFR-4.1).

Dưới đây là ví dụ về cách áp dụng từng nguyên tắc của SOLID vào Hệ thống bán sách trực tuyến.

[1] S: Single Responsibility Principle (SRP) - Nguyên tắc trách nhiệm duy nhất

Nội dung: Một lớp (class) hoặc module chỉ nên có một lý do để thay đổi, nghĩa là nó chỉ nên có một trách nhiệm duy nhất.

Minh họa trong Hệ thống bán sách trực tuyến:

- Không nên: Có một lớp OrderProcessor vừa xử lý logic đặt hàng, vừa gửi email xác nhận, và vừa cập nhật tồn kho. Nếu logic gửi email thay đổi, lớp này cũng phải thay đổi, vi phạm SRP

- Nên áp dụng: Chia thành các dịch vụ/lớp riêng biệt:

  + OrderService (Dịch vụ Đặt hàng): Chỉ xử lý logic tạo, lưu trữ và theo dõi trạng thái đơn hàng (FR-3.5)

  + InventoryService (Dịch vụ Tồn kho): Chỉ xử lý logic kiểm tra và trừ/cộng số lượng tồn kho (Yêu cầu nghiệp vụ)

  + NotificationService (Dịch vụ Thông báo): Chỉ chịu trách nhiệm gửi email xác nhận hoặc các thông báo khác (FR-3.5)

[2] O: Open/Closed Principle (OCP) - Nguyên tắc mở/đóng

Nội dung: Một thực thể (class, module, function) nên mở để mở rộng nhưng đóng để sửa đổi

Minh họa trong Hệ thống bán sách trực tuyến:

- Bài toán: Hệ thống cần hỗ trợ nhiều phương thức thanh toán (FR-3.4: Thanh toán trực tuyến). Ban đầu chỉ có thanh toán bằng Thẻ tín dụng, sau này cần thêm Ví điện tử

- Không nên: Sửa đổi lớp PaymentProcessor hiện tại, mỗi khi thêm một phương thức thanh toán mới

- Nên áp dụng:

  + Tạo một Interface chung là IPaymentGateway với phương thức processPayment()

  + Mỗi phương thức thanh toán mới (như CreditCardGateway và EWalletGateway) sẽ kế thừa (mở rộng) Interface này

  + Lớp OrderService sử dụng Interface IPaymentGateway mà không cần biết triển khai cụ thể. Khi thêm Ví điện tử, ta chỉ cần thêm lớp mới mà không cần sửa đổi OrderService

[3] L: Liskov Substitution Principle (LSP) - Nguyên tắc thay thế Liskov

Nội dung: Các đối tượng của một lớp con (subclass) có thể thay thế cho các đối tượng của lớp cha (parent class) mà không làm thay đổi tính đúng đắn của chương trình

Minh họa trong Hệ thống bán sách trực tuyến:

- Bài toán: Xử lý các loại sách khác nhau

- Nên áp dụng:

  + Tạo lớp cha trừu tượng (Abstract Class) Book với các thuộc tính chung (title, author, price - FR-2.3)

  + Tạo các lớp con như EBook và PaperbackBook

  + Lớp EBook không được làm những việc mà PaperbackBook làm (ví dụ: tính phí vận chuyển - do ebook không vận chuyển). Nếu hệ thống gọi hàm calculateShippingCost() trên một đối tượng EBook và nó trả về lỗi hoặc một giá trị không hợp lệ, thì đó là vi phạm LSP

  + Để tuân thủ, nên tách logic đặc thù (như vận chuyển) ra khỏi lớp Book chung, hoặc đảm bảo rằng tất cả các sách đều có thể được xử lý (ví dụ, EBook trả về phí vận chuyển bằng 0)

[4] I: Interface Segregation Principle (ISP) - Nguyên tắc phân tách giao diện

Nội dung: Không nên bắt người dùng (lớp) phải phụ thuộc vào các giao diện (interface) mà họ không sử dụng. Thay vì một Interface lớn, nên có nhiều Interface nhỏ, chuyên biệt

Minh họa trong Hệ thống bán sách trực tuyến:

- Không nên: Tạo một Interface IUserRepository chứa mọi phương thức: createUser(), updateProfile(), getHistory(), manageInventory(), sendEmail()

- Nên áp dụng: Phân tách Interface theo trách nhiệm:

  + ICustomerProfileManager: Chỉ có updateProfile(), getHistory() (FR-1.4, FR-1.5)

  + IAdminInventoryManager: Chỉ có manageInventory()

  + Dịch vụ User & Auth Service (quản lý thông tin người dùng) chỉ cần triển khai ICustomerProfileManager, trong khi dịch vụ quản lý backend cần triển khai IAdminInventoryManager

[5] D: Dependency Inversion Principle (DIP) - Nguyên tắc đảo ngược sự phụ thuộc

Nội dung:

- Các module cấp cao (high-level) không nên phụ thuộc vào các module cấp thấp (low-level). Cả hai nên phụ thuộc vào các abstraction (trừu tượng hóa, ví dụ: Interface)

- Abstraction không nên phụ thuộc vào chi tiết (detail); chi tiết nên phụ thuộc vào abstraction

Minh họa trong Hệ thống bán sách trực tuyến:

- Không nên: Lớp OrderService tạo trực tiếp một đối tượng cụ thể là PostgreSQLDatabase để lưu đơn hàng. (Sự phụ thuộc trực tiếp vào chi tiết cấp thấp).

- Nên áp dụng:

  + Tạo một Interface (Abstraction) là IOrderRepository

  + Lớp cấp thấp PostgreSQLOrderRepository sẽ triển khai IOrderRepository

  + OrderService (module cấp cao) sẽ nhận IOrderRepository thông qua Dependency Injection (DI) trong constructor

  + Điều này giúp OrderService không quan tâm đến loại cơ sở dữ liệu đang dùng, có thể dễ dàng chuyển sang dùng MongoDbOrderRepository mà không cần thay đổi mã nguồn của OrderService, đảm bảo tính linh hoạt và khả năng mở rộng (NFR-5.2)

6.3 Bài tập

Bài tập 6a. Hãy trình bày minh họa việc áp dụng nguyên tắc SOLID trong dự án của bạn (nhóm).

-----
Bài sau: