12/08/2018, 13:46

Nguyên lý SOLID trong lập trình hướng đối tượng

Có lẽ mọi sinh viên IT và lập trình viên đều không thể không biết đến khái niệm lập trình hướng đối tượng. Ngay trong những năm đầu tiên của thời sinh viên, chúng ta đã được học về OOP. Và các câu hỏi về OOP xuất hiện trong mọi cuộc phỏng vấn đối với lập trình viên. Luôn luôn là như vậy. Trong quá ...

Có lẽ mọi sinh viên IT và lập trình viên đều không thể không biết đến khái niệm lập trình hướng đối tượng. Ngay trong những năm đầu tiên của thời sinh viên, chúng ta đã được học về OOP. Và các câu hỏi về OOP xuất hiện trong mọi cuộc phỏng vấn đối với lập trình viên. Luôn luôn là như vậy. Trong quá trình học, các bạn sinh viên đều được học một số khái niệm OOP cơ bản đó là: Abstraction, Encapsulation, Inheritance và Polymophirsm. Nhưng đối với OOP, đa phần các bạn sẽ dừng lại ở mức: define class và khởi tạo object mà ít khi sử dụng đến các concept khác như abstract class, interface, inheritance…

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 các nguyên tắc trong thiết kế và lập trình. Và SOLID là một tập hợp trong các nguyên tắc đó.

Đâ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 tốt những nguyên lý này sẽ có code dễ đọc, dễ bảo trì và mở rộng. Và việc quan trọng nhất là việc maintainace code sẽ dễ hơn rất nhiều. SOLID nghĩa là "cứng", áp dụng nguyên lý này nhiều thì bạn sẽ trở thành một tay code "cứng". Nói vui là vậy nhưng cũng không hẳn là sai. "SOLID" là tập hợp 5 nguyên tắc sau:

  • Single responsibility principle

  • Open/closed principle

  • Liskov substitution principle

  • Interface segregation principle

  • Dependency inversion principle

  • Single responsibility principle

Nguyên lý đầu tiên, tương ứng với chữ S. Có nội dung như sau:

Một class chỉ nên giữ 1 trách nhiệm duy nhất (Nghĩa là chỉ có thể sửa đổi class đó với 1 lý do duy nhất).

Để hiểu nguyên lý này, ta hãy lấy ví dụ với 1 class vi phạm nguyên lý. Ta có 1 class như sau

class Customer {
   public void Add() {
      try {
         // Database code goes here
      } catch (Exception ex) {
         System.IO.File.WriteAllText(@"C:log.txt", ex.ToString());
      }
   }
}

Class 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à validations dữ liệu, xử lý logic liên quan tới các dữ liệu của Customer thôi. Điều này sẽ gây ra khó khăn khi chúng ta muốn thay đổi việc ghi log này. Mỗi lần muốn thay đổi cách thức ghi log file chúng ta lại vào lớp Customer để sửa. Không tốt chút nào phải không. Và để đảm bảo nguyên lý này thì chúng ta sẽ chuyển đoạn code ghi log sang một class 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. Class Customer chỉ làm việc với đối tượng Customer và class FileLogger sẽ chuyên tâm làm việc với nhiệm vụ ghi log. Ở đây có thể dễ dàng thấy được lợi ích của việc tách 2 lớp này ra.

  • 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 1 class, nhưng không được sửa đổi bên trong class đó (open for extension but closed for modification).

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 class mới mở rộng class cũ (bằng cách kế thừa hoặc sở hữu class cũ) chứ không nên sửa đổi class cũ. Việc này dẫn đến tình trạng phát sinh nhiều class, nhưng chúng ta sẽ không cần phải test lại các class cũ nữa, mà chỉ tập trung vào test các class mới, nơi chứa các chức năng mới.

  • 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 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.

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ó 1 class cha tên Vịt. Các class VịtBầu, VịtXiêm có thể kế thừa class này, chương trình chạy bình thường. Tuy nhiên nếu ta viết class VịtChạyPin, cần đến pin mới chạy được. Khi class này kế thừa class 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.

  • 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 1 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ó 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.

  • 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ả 2 nên phụ thuộc vào abstraction.

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.)

Nguyên lý này khá lắt léo. Hãy xem xét ví dụ với 2 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 1 cách dễ dàng. Ở đây, interface chính là đuôi tròn, 2 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 2 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 code cũng vậy, khi áp dụng Dependency Inverse, ta chỉ cần quan tâm tới interface. Để kết nối tới database, ta chỉ cần gọi hàm Get, Save,…của Interface IDataAccess. Khi thay database, ta chỉ cần thay implementation của interface này.

Tham khảo: http://blogs.msdn.com/b/cdndevs/archive/2009/07/15/the-solid-principles-explained-with-motivational-posters.aspx

0