SOLID - Interface Segregation (P1)
Nguyên lý này có một phát biểu rõ ràng nhưng lại mù mờ trong cách hiểu và cách áp dụng. Bạn có đang thực sự áp dụng nguyên lý này trong công việc? Có một vài lý do. Trước tiên tôi muốn chúng ta làm rõ về cách tiếp cận.
Một class không thừa kế interface, nhưng thực thi interface
Mặc dù interface nằm trong danh sách thừa kế của một class, nhưng về bản chất, class không thừa kế thứ gì từ interface, vì interface hoàn toàn rỗng. Interface là phương tiện để mô tả hành vi (hay đặc trưng) của một class. Vì sao chúng ta cần interface? Hãy nhớ rằng C++ không có interface. Trong C# hay Java, một class chỉ được thừa kế một class khác nhưng có thể implement nhiều interface. Điều này khá rõ ràng trong Java khi có từ khóa inherits và implements riêng trong khi C# chỉ là một dãy class và interface sau dấu hai chấm.
Điều khác biệt giữa interface và class hay abstract class mà chúng ta có thể nhìn thấy, đó là interface mô tả những hành vi và những hành vi này có thể được chia sẻ trong cả hệ thống, không phải của riêng một họ class nào. Điều này liên quan đến cách để bạn có thể Segregate các interface. Dường như bạn phải thoát ra lối tư duy hướng đối tượng để có thể thiết kế các interface. Mở rộng tầm nhìn, loại bỏ các chi tiết thừa để tìm thấy các hành vi phổ biến trong hệ thống. Mặc dù các hành vi này là sự tương tác giữa các đối tượng, nhưng hãy tạm bỏ các đối tượng ra. Ví dụ thế này:
- Bạn cần ghi log track các hành động trong hệ thống. Các thông tin cần thiết là CreatedDate và CreatedBy. Đây là một hành vi thậm chí là phổ quát cho mọi đối tượng.
- Bạn tạo một interface IHasCreationInfo
- Với mỗi hành động diễn ra, bạn kiểm tra "if cmd is IHasCreationInfo then logger.Write(cmd)". Bạn vốn không quan tâm tới đối tượng nào, chỉ quan tâm tới đặc trưng cụ thể.
Một câu hỏi đặt ra là nếu không có use case ghi log như trên, chúng ta có cần tạo ra interface IHasCreationInfo hay không?
Một ví dụ khác là trong gia đình có vợ chống, con cái và một con mèo. Chúng ta thấy rằng vợ chồng, con cái là các đối tượng có mối liên hệ về thừa kế, chia sẻ nhiều đặc trưng. Con mèo thì không liên quan gì cả. Nhưng cả gia đình chia sẻ nhiều hành vi chung như ăn, ngủ, đi, đứng. Khi chúng ta thực hiện hành động cho ăn, nếu không có interface, chúng ta cần hai phương thức FeedHuman(Human) và FeedKitty(Cat). FeedHuman rõ ràng có thể Feed Wife, Feed Husband, Feed Son, Feed Daughter. Wife, Husband, Son, Daughter đều có hành vi Eat thừa kế từ Human. Kitty cũng có hành vi Eat thừa kế từ Cat. Việc phân tách hành vi Eat ra interface IEatable giúp chúng ta đơn giản hóa việc cho ăn: Feed(IEatable). Sau này gia đình có nuôi thêm chó, vẹt, cá mây chiều thì phương thức Feed(IEatable) vẫn không thay đổi.
Hãy tưởng tượng class là các sợi chỉ ngang, interface interface là các sợi chỉ dọc, đan vào nhau và bạn có một tấm vải chắc chắn. Interface kết nối các class lại với nhau. Nếu interface chỉ được dùng cho một class và các dẫn xuất của nó thì khó mà nói nó phát huy hết sức mạnh. Chúng ta có nguyên tắc thứ 5 Dependency Inversion để nhấn mạnh việc trừu tượng hóa, thực tế là việc tạo các abstract class và các interface. Nhưng việc tạo ra IHuman với phương thức Eat và ICat với phương thức Eat của riêng nó chưa phát huy tối đa sức mạnh đúng không? Phân tách interface là thế, IHuman có phương thức Speak của Human, ICat có phương thức Meow của Cat, và IEatable mà abstract hay concrete class của IHuman, ICat đều implement. Phân tách không phải chỉ là chia nhỏ ra, mà chia có mục đích rõ ràng. Hãy ghi nhớ rằng chúng ta chỉ nên phân tách các hành vi được chia sẻ trong hệ thống. Speak là phương thức chỉ Human làm được, tách một interface ISpeakable chỉ làm chương trình thêm rối rắm. Các use case liên quan đến Speak chắc chắn liên quan đến Human, và sử dụng IHuman sẽ chứa nhiều ngữ nghĩa hơn ISpeakable.
Có những hành vị phụ thuộc vào một đối tượng nhưng cũng có những hành vị phụ thuộc vào một hành vi khác. Slogan của OOP là mọi thứ đều là đối tượng. Chúng ta có nên, qua interface coi một hành vi - đặc trưng - phương thức - thuộc tính cũng là một đối tượng? Tôi nghĩ như thế thì thật xoắn não. Hãy coi interface là một phương tiện giúp chúng ta nhìn các đối tượng ở một góc độ khác, là lưỡi dao để chúng ta khắc họa các class. Segregate interface có thể coi như là quá trình chúng ta khắc họa các mặt của một class, mặt nào riêng và mặt nào chia sẻ.
Bài sau, tôi sẽ bàn về các kiến trúc phần mềm hiện tại và có thể nó đã mài cùn kiến thức OOP của chúng ta ra sao.
All rights reserved