SOLID là gì?
Trong môi trường làm việc thực tế, chúng
ta sẽ nhận thấy rằng sản phẩm chúng ta làm ra luôn luôn có sự thay đổi và mở rộng
chức năng theo thời gian. Không có phần mềm nào có thể đứng vững theo thời gian
mà không thay đổi. Và chúng ta phải luôn đáp ứng được sự thay đổi đó. Chính vì
lẽ đó nên trong quy trình phát triển phần mềm, khâu phân tích và thiết kế là cực
kỳ quan trọng. Người thiết kế phải làm thế nào để kiến trúc phần mềm có thể dễ
dàng đáp ứng với thay đổi nhất. Và để làm được điều đó thì cần phải có kiến thức
rất sâu rộng trong hướng đối tượng, vận dụng linh hoạt các đặc trưng của OOP. Để
thiết kế một phần mềm có độ linh hoạt cao thì cần phải áp dụng thuần thục các
kiến thức về Design Pattern (Mẫu
thiết kế), các nguyên tắc trong thiết kế và lập trình. SOLID là một trong những
bộ nguyên tắc đó.
SOLID là những nguyên tắc được đúc kết từ
nhiều nhà phát triển, rút ra từ các thành công và thất bại của hàng nghìn dự án
phát triển phần mềm. Một dự án áp dụng tốt những nguyên lý này sẽ có mã dễ đọc,
dễ bảo trì, dễ sửa lỗi và mở rộng. Và việc quan trọng nhất là bạn sẽ dễ hơn rất
nhiều trong việc bảo trì mã.
"SOLID" là tập hợp 5 nguyên lý
sau:
- Single
responsibility principle (nguyên lý Trách nhiệm Duy nhất)
- Open/closed
principle (nguyên lý Đóng/mở)
- Liskov
substitution principle (nguyên lý Thay thế Liskov)
- Interface
segregation principle (nguyên lý Phân tách Interface)
- Dependency
inversion principle (nguyên lý Đảo ngược Phụ thuộc)
S - Single
responsibility principle
Nguyên lý đầu tiên, tương ứng với chữ S.
Có nội dung như sau:
Một lớp chỉ nên đảm nhiệm
một trách nhiệm duy nhất
Để hiểu nguyên lý này, ta hãy lấy ví dụ với
một lớp vi phạm nguyên lý:
class Customer {
public void Add() {
try {
// Database code goes here
} catch (Exception ex) {
System.IO.File.WriteAllText(@"C:\log.txt", ex.toString());
}
}
}
Lớp Customer ngoài việc thực hiện các xử
lý đến đối tượng Customer còn thực hiện cả việc ghi log nữa. Ghi log thì tất
nhiên là rất quan trọng rồi. Nhưng việc thực hiện nó như trên thì không tốt. Rõ
ràng lớp Customer chỉ nên làm những việc như là kiểm tra tính hợp lệ dữ liệu, xử
lý logic liên quan tới các dữ liệu của Customer. Việc thực hiện ghi log tại
Customer sẽ gây ra khó khăn khi chúng ta muốn thay đổi việc ghi log. Và để đảm
bảo nguyên lý này thì chúng ta sẽ chuyển đoạn mã ghi log sang một lớp khác, lớp
đó chỉ làm việc với Log mà thôi.
class FileLogger {
public void Handle(string message) {
System.IO.File.WriteAllText(@"c:\log.txt", message);
}
}
class Customer {
private FileLogger logger = new FileLogger();
public void Add() {
try {
// Database code goes here
} catch (Exception ex) {
logger.Handle(ex.ToString());
}
}
}
Mọi thứ đã trở nên rõ ràng hơn trước. Lớp
Customer chỉ làm việc với đối tượng Customer và lớp FileLogger sẽ chuyên tâm
làm nhiệm vụ ghi log. Ở đây có thể dễ dàng thấy được lợi ích của việc tách
thành hai lớp.
O - Open/closed
principle
Nguyên lý thứ hai, tương ứng với chữ O
trong SOLID. Có nội dung như sau:
Có thể thoái mái mở rộng
một lớp, nhưng không được sửa đổi bên trong lớp đó.
Theo nguyên lý này, mỗi khi ta muốn thêm
chức năng cho chương trình, chúng ta nên viết lớp mới mở rộng từ lớp cũ (bằng
cách kế thừa hoặc sở hữu lớp đó) chứ không nên sửa đổi nó. Việc này dẫn đến
tình trạng phát sinh nhiều lớp, nhưng chúng ta sẽ không cần phải kiểm thử lại các lớp
cũ nữa, mà chỉ tập trung vào kiểm thử lớp mới.
L - Liskov
substitution principle
Nguyên lý thứ ba, tương ứng với chữ L
trong SOLID. Có nội dung như sau:
Trong một chương trình,
các đối tượng của lớp con có thể thay thế đố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.
Bắt đầu có sự khó hiểu ở nguyên lý này.
Không sao, hãy tưởng tượng bạn có một lớp cha tên là Vịt. Các lớp con của nó là
VịtBầu, VịtXiêm, chương trình chạy bình thường. Tuy nhiên nếu ta bổ sung ớp VịtChạyPin,
cần đến pin mới chạy được. Khi lớp này kế thừa lớp Vịt, vì không có pin không
chạy được, sẽ gây lỗi. Đó là 1 trường hợp vi phạm nguyên lý này.
I - Interface
segregation principle
Nguyên lý thứ tư, tương ứng với chữ I
trong SOLID. Có nội dung như sau:
Thay vì dùng một
interface lớn, ta nên tách thành nhiều interface nhỏ, với nhiều mục đích cụ thể.
Nguyên lý này rất dễ hiểu. Hãy tưởng tượng
chúng ta có một interface lớn, khoảng 100 phương thức. Việc implements sẽ khá vất
vả, ngoài ra còn có thể dư thừa vì một lớp không cần dùng hết 100 phương thức
đó. Khi tách interface ra thành nhiều interface nhỏ, gồm các phương thức liên
quan tới nhau, việc implement và quản lý sẽ dễ hơn.
D - Dependency
inversion principle
Nguyên lý cuối cùng, tương ứng với chữ D
trong SOLID. Có nội dung như sau:
Các module cấp cao không
nên phụ thuộc vào các modules cấp thấp. Cả hai nên phụ thuộc vào abstraction.
Abstraction không nên
phụ thuộc vào chi tiết, mà ngược lại (Các lớp giao tiếp với nhau thông qua
interface, không phải thông qua triển khai.)
Nguyên lý này khá lắt léo. Hãy xem xét ví
dụ với hai loại đèn: đèn sợi đốt đuôi tròn và đèn huỳnh quang đuôi tròn. Chúng
cùng có đuôi tròn, do đó ta có thể thay thế đèn sợi đốt đuôi tròn bằng đèn huỳnh
quanh đuôi tròn cho nhau một cách dễ dàng. Ở đây, interface chính là đuôi tròn,
hai triển khai (implementation) là bóng đèn sợi đốt đuôi tròn và bóng đèn huỳnh
quang đuôi tròn. Ta có thể thay đổi dễ dàng giữa hai loại bóng vì ổ điện chỉ
quan tâm tới interface (đuôi tròn), không quan tâm tới implementation.
Trong mã cũng vậy, khi áp dụng nguyên lý
Dependency Inversion, ta chỉ cần quan tâm tới interface. Để kết nối tới CSDL,
ta chỉ cần gọi hàm Get, Save,…của Interface IDataAccess. Khi thay CSDL khác, ta
chỉ cần thay implementation của interface này.
إرسال تعليق