SOLID - Part 3: Nguyên lý L - Liskov Substitution Principle (LSP)

1. Đặt vấn đề: 

Khi một class là child của 1 class khác thì có cần những nguyên tắc, ràng buộc gì không, hay cứ thoải mái mà dùng thôi. Đó là nội dung của phần này.
Trước khi vào, mình muốn nói một chút về khái niệm module mình dùng trong loạt bài này. Module có nghĩa khá rộng, bạn hình dung gì cũng được miễn là bạn thấy dễ hiểu nhất. Module có thể là service, file, project, class hoặc function.

2. Liskov substitution principle – LSP

a. Nguyên tắc và nguyên nhân sử dụng

a.1 Nguyên tắc

Child classes should never break the parent class type definitions

Có nghĩa là class derived mới khi extend lớp base phải đảm bảo không thay đổi behavior của lớp cha. Hiểu đơn giản là lớp con khi override các method lớp cha thì không nên break funtionality của lớp cha vì những thay đổi từ design hay từ khách hàng. 

a.2 Nguyên nhân sử dụng

Việc thừa kế rất nguy hiểm và bạn nên sử dụng hết thành phần trên thừa kế để tránh một codebase lộn xộn. Thậm chí có thể là cả đống func ở codebase nếu bạn sử dụng thừa kế một cách không đúng cách.

Nguyên tắc này có thể giúp bạn sử dụng thừa kế mà không làm rối tung nó lên. Hãy xem các ví dụ sau

b. Áp dụng

b.1 Ví dụ cơ bản

Ta có 1 class Saving nhiệm vụ chính là lưu String từ Username và Email. Bây giờ việc lưu Email có thay đổi, nếu mà Email ko có @ thì không lưu. Code như sau:

Ví dụ trên đã vi phạm rule ở chỗ ta add thêm precondition mà string phải thoả điều kiện nào đó mới lưu. Vậy class SavingEmail sẽ khác với Saving class cha. Mà cha thì mong muốn tất cả các con phải như nhau. Cách fix là đơn giản là đưa điều kiện checkCorrectEmail lên trên lớp cha và đổi tên thành checkCorrect. Khi đó các lớp con sẽ như nhau. Nhưng mà nó sẽ nảy sinh hai vấn đề: 

+ Username đâu có nhu cầu dùng hàm này đâu ?
+ Bây giờ cứ thêm 1 điều kiện là phải chỉnh sửa hàm BaseClass -> nó phình hơi bị to trong khi đâu phải ai cũng dùng.

Gác lại 2 câu hỏi đó. Ta qua tiếp ví dụ nâng cao hơn.

b.2 Ví dụ nâng cao

Các bạn đọc code sau

Ở class Square, sau khi nó lấy giá trị của width, nó sẽ gán giá trị đó cho height luôn. Cho nên khi ta gọi ra thì kết quả hoàn toàn khác nhau. Vi phạm rule, bởi vì ta muốn tất cả phải là 10.

b.3 Cách giải quyết

Cách giải quyết ở đây chính là dùng protocolprotocol. Ta tạo 1 protocol với 1 property là area như sau: 

protocol Polygon {
    var area: Float { get }
}
class Square: Polygon {
    private let side: Float
    init(side: Float) {
        self.side = side
    }
    var area: Float {
        return side * side
    }
}

Với 1 class example ở đây là Square, ta đã thấy các property được tách biệt. "Side" bên hình vuông và "Width/ Height" bên hình chữ nhật. Nhiệm vụ của các class conform Protocol này là phải trả được giá trị area ra. Tổng thể code sẽ như sau:

Flow sâu bên trong thì các bạn đọc thêm bài 2 để hiểu thêm.

3. Tổng kết

Cách giải quyết vấn đề khá giống với Open Close Principle, do đó, nếu bạn tuân thủ được nguyên tắc LSP thì cũng sẽ tránh được việc vi phạm OCP luôn. Như vậy là xong nguyên lý L. Ta sẽ qua tiếp nguyên lý I. 

Bình luận
* Các email sẽ không được công bố trên trang web.
I BUILT MY SITE FOR FREE USING