10/10/2018, 22:51

Series Solid Cho Thanh Nien Code Cứng: Liskov subsitution Principle

Giới thiệu Đây là đây là bài viết thứ 3 trong series “SOLID cho thanh niên code cứng”. Ở bài viết này, mình sẽ nói về Liskov Substitution Principle – Nguyên lý Thay Thế Lít Kốp (LSP). S ingle Responsibility Principle O pen/Closed Principle Liskov Substitution ...

Giới thiệu

Đây là đây là bài viết thứ 3 trong series “SOLID cho thanh niên code cứng”. Ở bài viết này, mình sẽ nói về Liskov Substitution Principle – Nguyên lý Thay Thế Lít Kốp (LSP).

  1. Single Responsibility Principle
  2. Open/Closed Principle
  3. Liskov Substitution Principle
  4. Interface Segregation Principle
  5. Dependency Inversion Principle

Nội dung nguyên lý:

Giải thích nguyên lý

Xin cảnh báo trước một chút là nguyên lý này hơi trừu tượng và khó hiểu(các bác developer nước ngoài cũng tranh cãi khá nhiều về nó), do đó mình sẽ cố gắng giải thích một cách đơn giản nhất có thể. Nếu đọc lần đầu không hiểu, các bạn cố gắng đọc kĩ lại vài lần nhé, nếu vẫn không hiểu thì… chịu khó google tìm bài khác vậy.

Để giữ tính đúng đắn của chương trình, class con phải thay thế đượcclass cha. Nói dễ hiểu là thế này: Ngày xửa ngày xưa, hẳn bạn nào cũng có 1 cái Individual Portable Brick Game, tên tiếng Việt là máy chơi game xếp hình “thần thánh”. Thuở ấy, mỗi lần máy hết pin, mình lại xin mấy nghìn ra ngoài hàng mua pin con ó gắn vào chơi tiếp. Một lần nọ, hết pin mà không có tiền, mình đi lượm mấy cục pin con heo gắn vào chơi tạm. Không ngờ gắn pin vào xong, vừa hí hửng bật máy lên thì máy bị cháy vì điện thế của pin hơn bình thường. Thế là đi tong luôn cái máy chỉ vì… tiếc tiền mua pin.

may-tro-choi-dien-tu-brick-game (3)

Theo lý thuyết, class PinConHeo là con của class Pin, khi ta dùng PinConHeo gắn vào làm Pin thì máy phải chạy bình thường. Tuy nhiên trong trường hợp của mình, class Pin Con Heo đã vi phạm LSP vì đãgây lỗi khi dùng thay cho class Pin (Sau này, mình gặp một trải nghiệm tương tự với class PhimConH.., nhưng mà thôi để lần khác kể vậy).

Ví dụ minh họa

Mình sẽ đưa ra 2 ví dụ thường gặp về việc vi phạm LSP:

Ví dụ thứ nhất, class con quăng exception khi gọi hàm

Giả sử, ta muốn viết một chương trình để mô tả các loài chim bay. Đại bàng, chim sẻ, vịt bay được, nhưng chim cánh cụt không bay được. Do chim cánh cụt cũng là chim, ta cho nó kế thừa class Bird. Tuy nhiên, vì cánh cụt không biết bay, khi gọi hàm bay của chim cánh cụt, ta sẽ quăng NoFlyException.

Ta tạo 1 mảng chứa các loài chim rồi duyệt các phần tử. Khi gọi hàm Flycủa class Penguin, hàm này sẽ quăng lỗi. Class Penguin gây lỗi khi chạy, không thay thế được class cha của nó là Bird, do đó nó đã vi phạm LSP.Penguins

Ví dụ thứ 2, class con thay đổi hành vi class cha

Đây là ví dụ kinh điển về hình vuông và hình chữ nhật mà mọi người thường dùng để giải thích LSP, mình chỉ viết và giải thích lại đôi chút.

Đầu tiên, hãy cùng đọc đoạn code dưới đây. Ta có 2 class cho hình vuông và hình chữ nhật. Ai cũng biết hình vuông là hình chữ nhật có 2 cạnh bằng nhau, do đó ta có thể cho class Square kế thừa classRectangle để tái sử dụng code.

Do hình vuông có 2 cạnh bằng nhau, mỗi khi set độ dài 1 cạnh thì ta set luôn độ dài của cạnh còn lại. Tuy nhiên, khi chạy thử, hành động này đã thay đổi hành vi của của class Rectangle, dẫn đến vi phạm LSP.

Trong trường hợp này, để code không vi phạm LSP, ta phải tạo 1 class cha là class Shape, sau đó cho SquareRectangle kế thừa class Shapenày.

specialgrams

Lưu ý và kết luận

Đây là nguyên lý… dễ bị vi phạm nhất, nguyên nhân chủ yếu là do sự thiếu kinh nghiệm khi thiết kế class. Thuông thường, design các class dựa theo đời thật: hình vuông là hình chữ nhật, chim cánh cụt là chim. Tuy nhiên, không thể bê nguyên văn mối quan hệ này vào code. Hãy nhớ 1 điều:

Trong đời sống, A là B (hình vuông là hình chữ nhật, chim cánh cụt là chim) không có nghĩa là class A nên kế thừa class B. Chỉ cho class A kế thừa class B khi class A thay thế được cho class B.

Pin con heo là pin nhưng không thay thế được cho pin, chim cánh cụt là chim nhưng không thay thế được cho chim, do đó 2 ví dụ này vi phạm LSP.

Nguyên lý này ẩn giấu trong hầu hết mọi đoạn code, giúp cho code linh hoạt và ổn định mà ta không hề hay biết. Ví dụ như trong C#, ta có thể chạy hàm foreach với List, ArrayList, LinkedList bởi vì chúng cùng kế thừa interface IEnumerable.  Các class List, ArrayList, .. đã được thiết kế đúng LSP, chúng có thể thay thế cho IEnumerable mà không làm hỏng tính đúng đắn của chương trình.

liskov_substitution_principle_thumb

Một số tài liệu để tham khảo thêm:

  • http://www.butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod
  • http://stackoverflow.com/questions/56860/what-is-the-liskov-substitution-principle
  • http://prasadhonrao.com/solid-principles-liskov-substitution-principle-lsp/

Techtalk via toidicodedao

0