Nguyên lý SOLID

OOAD & SOLID

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 (Nghĩa là chỉ có thể sửa đổi lớp đó với 1 lý do 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();
   publicvirtual 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.

Đọc thêm: https://blogs.msdn.microsoft.com


Hãy tham gia nhóm Học lập trình để thảo luận thêm về các vấn đề cùng quan tâm.

Leave a Reply

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