--------------- <> -----------------
--- KHOA HỌC - CÔNG NGHỆ - GIÁO DỤC - VIỆC LÀM ---
--- Học để đi cùng bà con trên thế giới ---

Tìm kiếm trong Blog

CNPM (6) - Thiết kế chi tiết

Bài trước: CNPM (5) - Thiết kế kiến trúc phần mềm
-----

Bài 6: Thiết kế chi tiết

Sau “Bài 5. Thiết kế kiến trúc phần mềm”, bạn đã chọn được kiến trúc phù hợp với phần mềm.

Trong quy trình phát triển phần mềm, thiết kế chi tiết là giai đoạn trung gian, nằm giữa thiết kế kiến trúc và giai đoạn lập trình.

Nếu thiết kế kiến trúc là việc vẽ ra "bản quy hoạch" cho cả tòa nhà, thì thiết kế chi tiết chính là "bản vẽ kỹ thuật" mô tả cách lắp đặt từng đường điện, ống nước và vị trí nội thất trong từng căn phòng.

6.1. Mục tiêu của thiết kế chi tiết

Mục tiêu là làm rõ cấu trúc bên trong của phần mềm, để người lập trình có thể viết mã mà không cần phải đặt câu hỏi về logic nghiệp vụ hay cấu trúc dữ liệu. 

Bản thiết kế chi tiết giúp:

- Giảm sai sót và sự mơ hồ khi lập trình

- Tăng khả năng bảo trì và mở rộng hệ thống

- Ước tính chính xác hơn thời gian và nguồn lực cần thiết

6.2. Nội dung của thiết kế chi tiết

Thiết kế chi tiết tập trung vào 4 nội dung sau, nhằm chuyển các ý tưởng của bản Thiết kế kiến trúc thành các chỉ dẫn kỹ thuật cụ thể:

[1] Thiết kế dữ liệu chi tiết

Đây là bước cụ thể hóa sơ đồ thực thể quan hệ (ERD) từ giai đoạn kiến trúc thành cấu trúc lưu trữ thực tế.

- Cơ sở dữ liệu: Xác định chính xác tên bảng, kiểu dữ liệu, độ dài trường dữ liệu, các ràng buộc (Not Null, Unique) và các khóa (Primary Key, Foreign Key)

- Cấu trúc dữ liệu trong bộ nhớ: Thiết kế các mảng (Array), danh sách (List), hay các cấu trúc dữ liệu phức tạp để xử lý dữ liệu tạm thời trong quá trình phần mềm chạy

[2] Thiết kế logic xử lý

Mô tả cách thức hoạt động bên trong của từng chức năng. Thay vì viết mã ngay, kiến trúc sư sẽ sử dụng công cụ trung gian để mô tả logic.

- Công cụ: Sử dụng lưu đồ (flowchart) hoặc mã giả (pseudo-code)

- Nội dung: Xác định các bước rẽ nhánh (if-else), vòng lặp (loop) và các thuật toán đặc thù (ví dụ: cách tính thuế, cách lọc dữ liệu)

[3] Thiết kế giao diện lập trình

Đây là việc thiết kế các "hợp đồng" kết nối giữa các thành phần phần mềm (không phải giao diện người dùng).

- Định nghĩa API/Phương thức: Xác định tên hàm, danh sách tham số đầu vào (input) và kiểu dữ liệu trả về (output)

- Mục tiêu: Giúp các nhóm lập trình viên có thể làm việc song song. Nhóm A chỉ cần biết nhóm B cung cấp hàm calculateTotal(items) là có thể gọi để dùng, không cần quan tâm bên trong nhóm B viết gì

[4] Thiết kế phân cấp lớp

Dành cho các ngôn ngữ lập trình hướng đối tượng (OOP).

- Cấu trúc: Xác định các thuộc tính và phương thức cho từng lớp

- Mối quan hệ: Mô tả các mối quan hệ kế thừa (Inheritance), bao đóng (Encapsulation) và đa hình (Polymorphism) để tối ưu hóa việc tái sử dụng mã nguồn

6.3. Mẫu thiết kế

Trong thiết kế chi tiết, thay vì "phát minh lại bánh xe", các nhà phát triển sử dụng các Mẫu thiết kế (Design Patterns). Đây là các giải pháp mẫu cho các vấn đề thiết kế phổ biến.

- Mẫu khởi tạo (Creational): Ví dụ như Singleton (đảm bảo một lớp chỉ có một đối tượng duy nhất, dùng cho kết nối Database)

- Mẫu cấu trúc (Structural): Ví dụ như Adapter (giúp các thành phần không tương thích có thể làm việc với nhau)

- Mẫu hành vi (Behavioral): Ví dụ như Observer (tự động cập nhật giao diện khi dữ liệu thay đổi)

6.4. Kết quả đầu ra của thiết kế chi tiết

Kết thúc giai đoạn này, bạn phải có tài liệu thiết kế chi tiết (DDD: Detailed Design Document). Đây là "kim chỉ nam" cho lập trình viên. Một tài liệu DDD tốt là khi lập trình viên chỉ cần nhìn vào đó và viết mã, gần như không phải đưa ra quyết định về mặt logic hay cấu trúc nữa.

6.5. Ví dụ minh họa

Để dễ hiểu hơn, chúng ta cùng thực hiện một ví dụ minh họa.

Thiết kế chi tiết chức năng Đăng nhập, gồm 4 nội dung:

[1] Thiết kế dữ liệu chi tiết

Chúng ta xác định cấu trúc bảng Users trong Cơ sở dữ liệu để lưu trữ thông tin tài khoản.

- Bảng: Users

- Các trường dữ liệu:

    + id: kiểu INT, tự động tăng, khóa chính

    + username: kiểu VARCHAR(50), duy nhất (Unique), không để trống

    + password_hash: kiểu VARCHAR(255), lưu mật khẩu đã được mã hóa (không lưu mật khẩu thô để bảo mật)

    + last_login: kiểu DATETIME, thời gian đăng nhập cuối cùng

[2] Thiết kế logic xử lý

Sử dụng mã giả (pseudo-code) để mô tả các bước kiểm tra khi người dùng nhấn nút "Đăng nhập".

BẮT ĐẦU

  NHẬN username, password từ giao diện

  KIỂM TRA username có tồn tại trong bảng Users không?

    NẾU Không tồn tại:

      TRẢ VỀ LỖI "Tài khoản không chính xác"

    NẾU Có tồn tại:

      LẤY password_hash từ cơ sở dữ liệu

      KIỂM TRA password (người dùng nhập) có khớp với password_hash không?

        NẾU Khớp:

          TẠO Session/Token đăng nhập

          CẬP NHẬT last_login thành thời gian hiện tại

          CHUYỂN HƯỚNG người dùng vào Trang chủ

        NẾU Không khớp:

          TRẢ VỀ LỖI "Mật khẩu không chính xác"

KẾT THÚC

[3] Thiết kế giao diện lập trình

Định nghĩa "hợp đồng" giữa phía Giao diện (Frontend) và phía Xử lý (Backend).

- Tên phương thức/API: POST /api/v1/auth/login

- Tham số đầu vào (Input): 

    + username: chuỗi ký tự

    + password: chuỗi ký tự

- Dữ liệu trả về (Output):

    + Thành công: 200 OK kèm theo token

    + Thất bại: 401 Unauthorized kèm theo thông báo lỗi

[4] Thiết kế phân cấp lớp

Trong các ngôn ngữ lập trình hướng đối tượng (như Java, C#, hay Python), chúng ta không viết mã nguồn rời rạc mà tổ chức chúng thành các lớp để dễ quản lý và tái sử dụng.

- Sơ đồ lớp (Class Diagram): Thiết kế này thường bao gồm các lớp chính tương tác với nhau: User, AuthService, và UserRepository\

- Chi tiết các lớp:

[1] Lớp User (Entity Class)

- Thuộc tính (Attributes): username (String), passwordHash (String), email (String)

- Phương thức (Methods): validatePassword(inputPassword) – dùng để kiểm tra mật khẩu người dùng nhập vào có khớp với mã băm lưu trong hệ thống hay không

- Tính bao đóng (Encapsulation): Các thuộc tính được để ở dạng private, chỉ truy cập thông qua các hàm getter/setter để bảo vệ dữ liệu

[2] Lớp AuthService (Logic Class)

- Mối quan hệ: Lớp này thực hiện (implement) một Interface tên là IAuthentication

- Phương thức: login(username, password) – chứa logic điều phối: gọi lớp tìm kiếm người dùng, kiểm tra mật khẩu và tạo phiên làm việc (session)

- Tính đa hình (Polymorphism): Nếu sau này hệ thống cần đăng nhập bằng Google hoặc Facebook, ta chỉ cần tạo các lớp GoogleAuthService kế thừa hoặc cùng thực hiện Interface IAuthentication mà không cần sửa đổi mã nguồn ở các phần khác

[3] Lớp UserRepository (Data Access Class):

- Phương thức: findByUsername(username) – chịu trách nhiệm kết nối trực tiếp với Cơ sở dữ liệu để tìm kiếm thông tin người dùng

- Mục đích: Tách biệt hoàn toàn việc truy xuất dữ liệu ra khỏi logic nghiệp vụ (áp dụng nguyên lý Single Responsibility)

6.6. Nguyên lý SOLID trong thiết kế.

Đọc thêm.

6.7. Các mẫu thiết kế cơ bản

Đọc thêm.

6.8 Bài tập và câu hỏi ôn tập



-----
Bài sau: