Trong quá trình phát triển phần mềm, việc tạo ra một hệ thống mã nguồn dễ đọc, dễ hiểu và dễ bảo trì là một trong những mục tiêu quan trọng nhất. SOLID, một tập hợp các nguyên tắc thiết kế phần mềm, ra đời để giúp các nhà phát triển đạt được mục tiêu này. Vậy cụ thể SOLID là gì? Cùng TinoHost tìm hiểu qua bài viết dưới đây nhé!
SOLID là gì?
Trong khi xây dựng một ứng dụng phần mềm, bạn sẽ luôn phải sẵn sàng đối mặt với việc thay đổi code hoặc xây dựng thêm một vài tính năng mới và cả việc bảo trì phần mềm theo một cách định kỳ.
Để đảm bảo được điều này, code của bạn cần phải gọn gàng – ngăn nắp, khi bạn sửa chữa hoặc nâng cấp phần mềm, bạn sẽ không phải “đau khổ” nhớ lại mình đã làm gì, hay loay hoay kiểm tra class nào là của class nào…
Nếu bạn thiết kế code của bạn xấu, bạn sẽ nhận lấy “khổ đau” về sau, vì vậy bộ 5 nguyên tắc SOLID ra đời nhằm giúp bạn có thể thiết kế được một phần mềm có cấu trúc tốt hơn.
5 nguyên tắc của SOLID
SOLID là viết tắt của 5 thành phần như sau:
- S – Single-responsibility Principle – Nguyên tắc trách nhiệm đơn lẻ
- O – Open-closed Principle – Nguyên tắc đóng mở
- L – Liskov Substitution Principle – Nguyên tắc phân vùng Liskov
- I – Interface Segregation Principle – Nguyên tắc phân tách giao diện
- D – Dependency Inversion Principle – Nguyên tắc đảo ngược phụ thuộc
Được Robert C Martin (hay còn gọi với cái tên thân thương là “Uncle Bob”) đề xuất vào năm 2000, SOLID trở nên phổ biến khi phát hành quyển sách Agile Software Development, Principles, Patterns, and Practices vào năm 2003.
Tại sao SOLID giúp lập trình trở nên hiệu quả?
Ngay khi bạn đọc 5 nguyên tắc trên, bạn đã có thể hiểu được mình nên làm gì rồi đúng không? Và đây là những lý do tại sao SOLID giúp bạn lập trình trở nên hiệu quả hơn.
Sự rõ ràng và dễ hiểu
Tất nhiên, một phần mềm rất hiếm khi được phát triển độc lập bởi một cá nhân duy nhất, mà đó là kết quả làm việc của cả một nhóm nhiều người. Vì vậy, áp dụng SOLID để khiến code của bạn trở nên gọn gàng, ngăn nắp sẽ tạo được cảm tình với đồng nghiệp, cũng như khiến teamwork trở nên hiệu quả hơn.
Dễ dàng sửa đổi và nâng cấp
Khi nhìn vào code và nhân ra ngay vai trò của từng class hoặc vị trí của từng hàm, bạn sẽ dễ dàng sửa đổi hoặc nâng cấp chương trình phần mềm mình đã viết. Bạn cũng sẽ đỡ tốn thời gian để tìm hiểu lại chính code của mình và giúp những người khác có được bổ sung để chữa cháy, họ cũng có thể hiểu code của bạn có ý nghĩa như thế nào và giúp bạn.
Khả năng tái sử dụng
Nếu chọn đi theo con đường lập trình, chắc chắn trong suốt cuộc đời bạn sẽ tham gia không biết bao nhiêu dự án. Vì thế, bạn làm code của bạn trở nên ngăn nắp và có ghi chú rõ ràng thành từng module tách biệt, bạn có thể mở dự án cũ lên copy và sử dụng lại lần nữa. Điều này sẽ khiến thời gian của bạn được tiết kiệm rất nhiều đấy!
Giải thích chi tiết 5 nguyên tắc SOLID
S – Single-responsibility Principle – Nguyên tắc trách nhiệm đơn lẻ
Giải thích một cách đơn giản hơn, Single-responsibility Principle là “mỗi người 1 việc”.
Khi thiết kế phần mềm, bạn chỉ nên để mỗi lớp – class chỉ có một trách nhiệm/ một chức năng nhằm để:
- Khả năng kiểm thử: trong trường hợp bạn kiểm thử, bạn sẽ phải suy nghĩ ít hơn để test class, vì class đó chỉ có 1 chức năng xác định duy nhất.
- Tạo ít mối quan hệ phụ thuộc: chỉ một chức năng cho một class sẽ giảm thiểu các phụ thuộc qua lại lẫn nhau giữa các chức năng.
- Tính lớp lang: bạn làm một lớp nhỏ, sau đó bạn có thể tìm đến lớp lớn hơn. Việc này sẽ giúp bạn sắp xếp phần mềm của mình hiệu quả hơn.
Ví dụ: Bạn tạo ra một nơi để lưu trữ sách và các thuộc tính bao gồm: tên, sách, tên tác giả, nội dung và chức năng in sách ra như sau:
public class Book {
private String name;
private String author;
private String text;
public String replaceWordInText(String word){
return text.replaceAll(word, text);
}
public boolean isWordInText(String word){
return text.contains(word);
}
public class Book {
void printTextToConsole(){
}
}
Điều này vi phạm nguyên tắc trách nhiệm đơn lẻ. Vì thế, bạn sẽ phải tách chức năng lưu trữ và chức năng in sách ra thành 2 class khác nhau.
Lớp xử lý sách bạn có thể tạo ra class mới như sau:
public class BookPrinter {
void printTextToConsole(String text){
}
void printTextToAnotherMedium(String text){
}
}
Để tránh việc sau này bạn sẽ phải “mệt” với việc kiểm thử và sắp xếp sửa đổi chức năng.
O – Open-closed Principle – Nguyên tắc đóng mở
Thay vì bạn sửa đổi trực tiếp những dòng code hiện có và tạo ra nguy cơ tiềm ẩn cho lỗi hỏng cả phần mềm. Bạn chỉ cần sử dụng phương thức mở rộng và kế thừa class trước đó là ổn.
Ví dụ bạn code ra một cây đàn Guitar có luôn 1 nút vặn âm lượng như sau:
public class Guitar {
private String make;
private String model;
private int volume;
}
Giờ đây, bạn thấy cây đàn Guitar của mình hơi bình thường quá, bạn muốn đốt cháy cây đàn để khán giả của bạn phấn khích hơn. Nếu bạn thêm thẳng thuộc tính: private String flameColor; vào bên trong class Guitar, khả năng cao cây đàn của bạn sẽ bị hỏng thật sự.
Thay vì như thế, bạn chỉ cần mở rộng thêm một class GuitarCoolNgauBungLua kế thừa từ Guitar như sau để cây đàn của bạn không bị hỏng:
public class GuitarCoolNgauBungLua extends Guitar {
private String flameColor;
}
L – Liskov Substitution Principle – Nguyên tắc phân vùng Liskov
Một trong những nguyên tắc khá phức tạp và khó để hiểu… Nguyên tắc phân vùng Liskov: nếu class A là con/class dẫn xuất của class B, class A phải có thể thay thế vị trí của class B mà không làm chương trình bị lỗi.
TinoHost sẽ giải thích dễ hiểu nhất như sau:
Một con vịt giả có bề ngoài giống hệt con vịt thật. Tuy nhiên, con vịt giả cần pin để kêu “cạp cạp” => bạn không có một con vịt thật.
Hoặc một ví dụ khác như sau:
Ta sẽ đặt điều kiện như sau: nhân viên chính thức sẽ được điểm danh vào buổi sáng, bạn thêm class checkAttendance(). Tuy nhiên, nhân viên lao công tạp vụ tạm thời không được điểm danh nhưng lại trỏ vào class Employee trở nên sai nguyên tắc phân vùng Liskov.
Cách giải quyết: bạn có thể tách checkAttendance() ra một lớp khác và trỏ các nhân viên ở các bộ phận khác nằm dưới lớp checkAttendance() và trỏ ngược lên lớp Employee.
I – Interface Segregation Principle – Nguyên tắc phân tách giao diện
Nguyên tắc phân tách giao diện dễ hiểu hơn nguyên tắc phân vùng Liskov rất nhiều. Với nguyên tắc phân tách giao diện, bạn chỉ cần phân nhỏ giao diện lớn thành nhiều giao diện nhỏ hơn phù hợp hơn.
Ví dụ:
Bạn là một người một người nuôi gấu trong sở thú. Bạn sẽ được lập trình với nhiệm vụ như sau:
public interface NguoinuoiGau{
void washTheBear();
void feedTheBear();
void petTheBear();
}
Tuy nhiên, việc chăm sóc 1 con gấu cho chúng ăn và vuốt ve chúng là 2 công việc tách biệt nhau, chưa kể, việc vuốt ve gấu là rất nguy hiểm. Vì thế, bạn sẽ cần phải tách việc nuôi gấu thành 3 thành phần tách biệt như sau để giao đúng người hơn.
public interface BearCleaner {
void washTheBear();
}
public interface BearFeeder {
void feedTheBear();
}
public interface BearPetter {
void petTheBear();
}
Đối với nhân viên thông thường, bạn có thể giao nhiệm vụ tắm rửa và cho gấu ăn. Bằng dòng code sau:
public class BearCarer implements BearCleaner, BearFeeder {
public void washTheBear() {
}
public void feedTheBear() {
}
}
Còn công việc nguy hiểm hơn, “điên hơn” bạn có thể gửi gắm cho một người có bản lĩnh hơn như sau:
public class NguoiBanLinh implements BearPetter {
public void petTheBear() {
}
}
D – Dependency Inversion Principle – Nguyên tắc đảo ngược phụ thuộc
Bạn có thể hiểu nguyên tắc này như sau: 1 thành phần trong một chương trình chỉ nên phụ thuộc vào một giá trị trừu tượng – abstraction chứ không nên phụ thuộc vào một giá trị nào cụ thể cả.
Ví dụ: Khi mua một laptop đời mới, nhà phát hành bo mạch chủ không nhất thiết phải biết bạn muốn gắn ổ cứng SSD hay HDD, họ chỉ cần phát triển ra một bo mạch chủ có thể gắn cả 2 loại ổ dựa trên của giao tiếp SATA để bạn có thể gắn vào.
Kết luận
Qua bài viết, TinoHost hi vọng bạn đã hiểu hơn về SOLID là gì cũng như 5 nguyên tắc SOLID. Chúc bạn áp dụng nguyên tắc SOLID vào công việc lập trình của mình một cách hiệu quả nhất!
Những câu hỏi thường gặp
Nguyên tắc nào trong SOLID quan trọng nhất?
Tất cả các nguyên tắc SOLID đều quan trọng và bổ trợ cho nhau. Tuy nhiên, nguyên tắc SRP (Single Responsibility Principle) thường được coi là cơ bản nhất vì nếu một lớp không được thiết kế với trách nhiệm đơn, các nguyên tắc khác cũng sẽ khó áp dụng hiệu quả.
SOLID có áp dụng được cho tất cả các dự án không?
SOLID phù hợp nhất với các dự án có quy mô trung bình đến lớn, đặc biệt là khi làm việc nhóm hoặc các dự án có yêu cầu mở rộng trong tương lai. Đối với các dự án nhỏ hoặc đơn giản, việc áp dụng toàn bộ SOLID có thể khiến mã trở nên phức tạp không cần thiết.
Áp dụng SOLID trong thực tế có khó không?
Việc áp dụng SOLID yêu cầu người phát triển có tư duy thiết kế tốt và kinh nghiệm với lập trình hướng đối tượng. Ban đầu có thể khó khăn, nhưng khi đã quen, việc tuân theo các nguyên tắc này sẽ giúp tiết kiệm thời gian trong dài hạn và giảm rủi ro phát sinh lỗi.
Khi nào cần ưu tiên áp dụng nguyên tắc OCP (Open/Closed Principle)?
OCP đặc biệt quan trọng khi xây dựng các hệ thống cần mở rộng tính năng trong tương lai. Khi dự án cần thêm các module mới mà không muốn làm gián đoạn hệ thống hiện tại, việc áp dụng OCP sẽ giúp dễ dàng thêm chức năng mới mà không phải chỉnh sửa mã cũ.
Áp dụng SOLID có làm tăng độ phức tạp của mã không?
Ban đầu có thể khiến mã trở nên phức tạp hơn do phải tách các lớp và giao diện, nhưng về lâu dài, mã sẽ dễ bảo trì và mở rộng hơn. Khi áp dụng SOLID đúng cách, bạn sẽ tránh được nhiều lỗi tiềm ẩn và giúp dự án phát triển ổn định.