Liskov Substutition là một nguyên tắc khá trừu tượng. Có một vài ví dụ kinh điển như con vịt đồ chơi trong họ nhà vịt, hay hình vuông có thừa kế từ hình tam giác hay không. Cùng xét một một best practice điển hình, phổ biến mà có thể liên tưởng tới Liskov Substitution:
Khởi tạo giá trị mặc định cho thuộc tính
Việc này thường là để giải quyết Null Exception. Có nghĩa là đề phòng chúng ta sử dụng một thuộc tính nào đó mà nó chưa chắc tồn tại (nên tốt nhất là luôn cho chúng tồn tại). Liskov Substitution nhắc nhở chúng ta vấn đề tương tự ở mức chiều sâu hơn, và có thể phát sinh lỗi nghiêm trọng hơn runtime exception là lỗi logic. Việc một dẫn xuất không cung cấp đầy đủ những gì mà tổ tiên nó định nghĩa tiềm tàng những rủi ro khi người sử dụng hoàn toàn không hiểu được những gì lớp dẫn xuất cung cấp (thông tin cung cấp là lớp đại diện – lớp tổ tiên). Lỗi này xảy ra có thể do sự thiếu cẩn trọng của bên thừa kế, nhưng thường có lẽ là do lỗi thiết kế khi hành vi hoặc thuộc tính của một lớp trừu tượng không được diễn đạt rõ ràng hoặc cách trừu tượng hóa là sai lầm. Tôi có thể đưa ra một vài hướng dẫn giúp tránh các sai sót liên quan đến Liskov Substitution:
Các giả định
Khi trừu tượng hóa và đưa ra thiết kế, bạn phải hiểu nó không chỉ mô tả hệ thống hiện tại, mà còn quy định cách hệ thống phát triển trong tương lai. Hãy đưa ra các giả định để xem thiết kế hiện tại có đáp ứng được yêu cầu mới hay không. Một số giả định này hoàn toàn có thể nằm trong kế hoạch phát triển sản phẩm. Một số khác có thể là các giả định thiếu thực tế hơn. Các giả định này không nên quá tham vọng mà cần bám vào ngữ cảnh cụ thể của sản phẩm. Ví dụ một sản phẩm B2B thì không nên đưa giả định có cả triệu khách hàng kết nối.
Chiều sâu của thừa kế
Best practice là không nên vượt quá 3 cấp thừa kế. Hãy cẩn thận khi một class thừa kế một class khác.
Cách tôi thường làm là interface < abstract class < concrete class. Không nên cho phép một concrete class thừa kế một concrete class khác. Hãy sử dụng ngay sealed (hay final) khi có thể.
Và nếu bạn muốn tận dụng một concrete class, hãy sử dụng
Composition over inheritance
Kết hợp là cách tuyệt vời để mở rộng một class thay vì những rắc rối của thừa kế. Cách này không chỉ mở rộng mã nguồn sẵn có. Hãy chú ý tới nó khi bạn thiết kế. Một thiết kế sử dụng Kết hợp dễ gặp là Pipeline, các command được đưa vào một pipeline và được thực hiện tuần tự. Nó cho phép bạn thêm những phép xử lý mới vào giữa chu trình. Middleware của .net cũng là một dạng Pipeline. Rõ ràng thừa kế không áp dụng được trong trường hợp này và Pipeline tỏ ra dễ dàng và linh hoạt hơn hẳn.
=====