SOLID – 5 nguyên tắc giúp thiết kế code tốt hơn

SOLID là 5 nguyên tắc thiết kế trong lập trình hướng đối tượng (OOP), giúp code dễ đọc, dễ thay đổi, dễ mở rộng và dễ bảo trì.

SOLID không phải framework hay tool mà là tư duy thiết kế code. Nó giúp hạn chế những vấn đề thường gặp khi dự án lớn dần như class quá lớn, dependency quá chặt, sửa một chỗ ảnh hưởng nhiều chỗ hoặc thêm tính năng mới trở nên khó khăn.

SOLID gồm:

  • S – Single Responsibility Principle
  • O – Open/Closed Principle
  • L – Liskov Substitution Principle
  • I – Interface Segregation Principle
  • D – Dependency Inversion Principle

Nguyên tắc thiết kế SOLID là gì? - Viblo

 

1. Single Responsibility Principle – SRP

một class nên có một trách nhiệm chính và một lý do để thay đổi một class không nên ôm quá nhiều loại công việc khác nhau.

Ví dụ, một UserService vừa xử lý logic user, vừa lưu database, vừa gửi email:

UserService
 ├── xử lý User
 ├── lưu Database
 └── gửi Email


Khi database thay đổi, UserService phải thay đổi. Khi cách gửi email thay đổi, nó cũng phải thay đổi.

Có thể tách thành:

UserService
UserRepository
EmailService

Mỗi thành phần tập trung vào một nhóm trách nhiệm liên quan.

SRP không có nghĩa là mỗi class chỉ được có đúng một method. Điều quan trọng là các trách nhiệm bên trong class phải có sự liên quan chặt chẽ với nhau.

tóm lại: khi một class có quá nhiều công việc cũng như có quá nhiều lí do để thay đổi đó là cái nên xem xét lại cái class đó

2. Open/Closed Principle – OCP

code nên viết để có thể mở rộng và tái sử dụng nhưng hạn chế phải sửa code đã ổn định.

ví dụ cho việc trên có một hệ thống thanh toán:

CreditCard
Momo
Paypal

nếu mỗi lần thêm một phương thức mới là phải code một if else lớn cho phương thức đó:

if CreditCard
else if Momo
else if Paypal
else if ...

càng thêm phương thức chức năng code càng khó quản lý.

Thay vào đó, có thể định nghĩa một abstraction chung:

Payment
 ├── CreditCardPayment
 ├── MomoPayment
 ├── PaypalPayment
 └── ZaloPayPayment

Khi muốn thêm ZaloPay, chỉ cần tạo implementation mới thay vì liên tục sửa logic cũ.

OCP không có nghĩa là không bao giờ được sửa code. Ý chính là những phần đã ổn định nên hạn chế bị ảnh hưởng mỗi khi hệ thống cần mở rộng.

tóm lại: khi mình thêm một tính năng mới nào đó hãy nghĩ đến việc phải mở rộng hệ thống thay vì thay đổi code cũ.

3. Liskov Substitution Principle – LSP

Class con phải có thể thay thế class cha mà không làm chương trình hoạt động sai.

ví dụ dễ hiểu:

Bird
 └── fly()

Sau đó tạo:

Penguin extends Bird

Nhưng Penguin lại không thể bay.

Nếu code đang mong đợi một Bird có khả năng fly() mà nhận vào Penguin, hệ thống có thể gặp vấn đề.

Điều này cho thấy abstraction ban đầu đã không phù hợp.

thay vì coi tất cả các loài chim đều có khả năng bay, ta có thể tách chưng năng bay đó thành một abtraction riêng:

Bird
 ├── Eagle
 └── Penguin

Flying
 ├── Eagle
 └── ...

LSP không chỉ nói về inheritance. Cốt lõi của nó là behavior: class con phải tuân thủ những gì abstraction hoặc class cha đã cam kết.

tóm lại: nếu một class con không thể hoạt động một cách hợp lý ở nơi class cha được mong đợi, có thể thiết kế abstraction đang có vấn đề.

4. Interface Segregation Principle – ISP

không nên ép một class phụ thuộc và các method mà nó không cần.

ví dụ để hiểu:

Worker
 ├── work()
 ├── eat()
 ├── sleep()
 └── program()
một human có thể sử dụng tất cả các method. nhưng một con robot thì chỉ có thể sử dụng hợp lý hai method là work
và program.

Nếu bắt Robot implement toàn bộ interface, nó sẽ phải viết những method không có ý nghĩa.

Thay vì một interface quá lớn, có thể chia nhỏ:

Workable
Programmable
Eatable
Sleepable

Mỗi class chỉ cần sử dụng những interface phù hợp với nó.

ISP giúp interface trở nên nhỏ, rõ ràng và đúng nhu cầu sử dụng.

tóm lại: đừng tạo một interface lớn rồi bắt mọi implement phải phụ thuộc vào toàn bộ những gì bên trong nó.

5. Dependency Inversion Principle – DIP

Code cấp cao không nên phụ thuộc trực tiếp vào implementation cụ thể. Cả hai nên phụ thuộc vào abstraction.

Ví dụ:

OrderService
      ↓
    MySQL

OrderService phụ thuộc trực tiếp vào MySQL. Nếu sau này chuyển sang PostgreSQL hoặc một database khác, logic bên trên có thể phải thay đổi.

Một thiết kế tốt hơn:

          OrderRepository
          ↑             ↑
       MySQL        PostgreSQL

             ↑
        OrderService

OrderService chỉ biết đến OrderRepository, còn việc sử dụng MySQL hay PostgreSQL được xử lý ở tầng implementation.

Đây cũng là lý do chúng ta thường thấy Dependency Injection trong các framework hiện đại.

DIP giúp business logic không bị gắn chặt với một công nghệ cụ thể.

tóm lại: logic quan trọng của hệ thống nên phụ thuộc vào abstraction thay vì phụ thuộc trực tiếp vào implement.

Bài viết khác

Series 7 — Web Quality – Engineering :Testing – Unit Testing, Integration Testing, Web Performance – Core Web Vitals, Code Splitting, Lazy Loading

Series 7 — Web Quality – Engineering Bài 3: Testing – Unit Testing, Integration Testing và End-to-End Testing trong ứng dụng web 1. Vì sao ứng dụng web cần Testing? Trong quá trình phát triển Frontend, chúng ta thường kiểm tra ứng dụng bằng cách chạy website trên trình duyệt, nhấn vào các nút, nhập […]

Series 7 — Web Quality – Engineering : Authentication & Authorization

Series 7 — Web Quality – Engineering Bài 1: Authentication & Authorization – Xác thực và phân quyền trong ứng dụng web 1. Mở đầu Khi xây dựng một ứng dụng web có chức năng đăng nhập, chúng ta thường nghĩ rằng chỉ cần tạo biểu mẫu nhập email, mật khẩu và kiểm tra thông […]

Series 6 – Next.js Advanced – Production: Next.js Build & Deployment

Series 6 – Next.js Advanced & Production Bài 5: Next.js Build & Deployment – Từ mã nguồn đến môi trường Production 1. Mở đầu Trong quá trình phát triển một ứng dụng web, việc chạy thành công trên máy tính cá nhân chưa có nghĩa là ứng dụng đã sẵn sàng để đưa đến người […]

Series 6 – Next.js Advanced – Production: Next.js Internationalized Routing Next.js Static Export

Series 6 – Next.js Advanced & Production   Bài 3: Next.js Internationalized Routing – Tổ chức URL cho website đa ngôn ngữ 1. Mở đầu Sau khi xây dựng cơ chế dịch nội dung và định dạng theo locale, ứng dụng cần một cách để người dùng truy cập đúng phiên bản ngôn ngữ. Ví […]

Series 6 – Next.js Advanced & Production: Metadata & SEO, Internationalization (i18n) & Localization (l10n)

Series 6 – Next.js Advanced & Production Bài 1: Metadata & SEO trong Next.js – Tối ưu thông tin và khả năng hiển thị trên công cụ tìm kiếm 1. Mở đầu Khi xây dựng một website, việc hoàn thiện giao diện và chức năng mới chỉ là một phần của quá trình phát triển. […]

Series 5 – Next.js Fundamentals: CSR, SSR, SSG, ISR và cách lựa chọn

Series 5 – Next.js Fundamentals   Bài 7 : CSR, SSR, SSG và ISR – Các chiến lược Rendering trong Next.js 1. Mở đầu Khi xây dựng một website bằng React, chúng ta thường tập trung vào việc tạo component, quản lý state và hiển thị giao diện trên trình duyệt. Tuy nhiên, khi ứng dụng […]

Leave a Reply

Your email address will not be published. Required fields are marked *