Nguyên lý SOLID cho thanh niên code "cứng"

Trong quá trình học, hầu như các bạn sinh viên đều được học một số khái niệm OOP cơ bản như sau:

Abstraction Encapsulation Inheritance Polymophirsm

Những khái niệm này đã được dạy khá rõ ràng, và hầu như những buổi phỏng vấn nào cũng có những câu hỏi liên quan đến khái niệm này. Vì 4 khái niệm này khá cơ bản, bạn nào chưa vũng có thể google để tìm hiểu thêm.

Những nguyên lý mình giới thiệu hôm nay là những nguyên lý thiết kế trong OOP. Đây là những nguyên lý được đúc kết bởi máu xương vô số developer, rút ra từ hàng ngàn dự án thành công và thất bại. Một project áp dụng những nguyên lý này sẽ có code dễ đọc, dễ test, rõ ràng hơn. Và việc quan trọng nhất là việc maintainace code sẽ dễ hơn rất nhiều (Ai có kinh nghiệm trong ngành IT đều biết thời gian code chỉ chiếm 20-40%, còn lại là thời gian để maintainance: thêm bớt chức năng và sửa lỗi). Nắm vững những nguyên lý này, đồng thời áp dụng chúng trong việc thiết kế + viết code sẽ giúp bạn tiến thêm 1 bước trên con đường thành senior nhé.

Chém gió thế đủ rùi, bây giờ đến phần giới thiệu 😄. Nguyên lý SOLID gồm 5 nguyên tắc sau đây:

Single responsibility principle Open/closed principle Liskov substitution principle Interface segregation principle Dependency inversion principle

Mình sẽ cố gắng tóm tắt và giới thiệu tổng quát về các nguyên lý này.

1. Single responsibility principle (SRP)

a class should have only a single responsibility (i.e. only one potential change in the software's specification should be able to affect the specification of the class)

Tạm hiểu là

Một class chỉ chịu một trách nhiệm duy nhất (mỗi một thay đổi trong tài liệu mô tả sẽ chỉ ảnh hưởng tới một class cụ thể)

Có thể hiểu nôm na qua vd sau:

protocol UserProtocol {
  func getInfo()
  func storeToDb()
  func readFromDb()
}

Class này giữ tới 3 trách nhiệm: lấy tên đầy đủ, lưu vào db và get ra. Mỗi khi thay đổi db hoặc thay đổi cách lấy thông tin của User ta sẽ phải sửa đổi class này, như vây đã vi phạm nguyên tắc SRP. Theo đúng nguyên lý SRP ta phải tách thành 2 class như sau:

protocol UserProtocol {
  func getInfo()
}
protocol UserDAOProtocol {
  func storeToDb()
  func readFromDb()
}

Tuy số lượng class nhiều hơn những việc sửa chữa sẽ đơn giản hơn, class ngắn hơn nên cũng ít bug hơn.

2. Open/closed principle (OCP)

"software entities … should be open for extension, but closed for modification."

Một module có thể mở rộng dẽ dàng như không được thay đổi bên trong module đó.

Theo nguyên lý này những entities (classes, modules, functions, etc.) có sẵn sẽ dễ dàng mở rộng (bằng cách kế thừa) mà ko cần phải sửa đổi source code cũ.

3. Liskov substitution principle (LSP)

"objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program." See also design by contract.

Trong một chương trình, các object của class con có thể thay thế class cha mà không làm thay đổi tính đúng đắn của chương trình.

Khả năng thay thế là một nguyên tắc trong lập trình hướng đối tượng được hiểu như ví dụ sau: trọng một chương trình: nếu S là class con của T, khi đó object kiểu T có thể được thay thế bằng objects kiểu S mà ko làm mất đi tính đúng đắn của chương trình.

4. Interface segregation principle (ISP)

"many client-specific interfaces are better than one general-purpose interface."

Thay vì dùng 1 interface lớn với mục định chung, ta nên tách thành nhiều interface nhỏ, với nhiều mục đích cụ thể.

Giả sử chúng ta có 1 interface lớn, khoảng 100 methods. Việc implements sẽ khá cực khổ, ngoài ra còn có thể dư thừa vì 1 class không cần dùng hết 100 method. Khi tách interface ra thành nhiều interface nhỏ, gồm các method liên quan tới nhau, việc implement và quản lý sẽ dễ hơn.

5. Dependency inversion principle (DIP)

one should “depend upon abstractions, [not] concretions.”

Class chỉ phụ thuộc vào abstractions (interface), ko phải cách cài đặt

Trong hướng đối tượng, DIP được xem như là cách phân chia các module trong chương trình. Bao gồm 2 nguyên lý sau:

A. High-level modules should not depend on low-level modules. Both should depend on abstractions. (Các module cấp cao không nên phụ thuộc vào các modules cấp thấp. Cả 2 nên phụ thuộc vào abstraction.) B. Abstractions should not depend on details. Details should depend on abstractions. ( Interface (abstraction) không nên phụ thuộc vào chi tiết, mà ngược lại. (Các class giao tiếp với nhau thông qua interface, không phải thông qua implementation.)

Tham khảo: https://en.wikipedia.org/wiki/SOLID_(object-oriented_design)


All Rights Reserved