Có một sự khác biệt nhỏ giữa Java và C # có liên quan ở đây. Trong Java, mọi thành viên đều là ảo theo mặc định. Trong C #, mọi thành viên đều được niêm phong theo mặc định - ngoại trừ các thành viên giao diện.
Các giả định đi kèm với điều này ảnh hưởng đến hướng dẫn - trong Java, mọi loại công khai nên được coi là không phải là cuối cùng, theo Nguyên tắc thay thế của Liskov [1]. Nếu bạn chỉ có một triển khai, bạn sẽ đặt tên cho lớp Parser
; nếu bạn thấy rằng bạn cần nhiều triển khai, bạn sẽ chỉ cần thay đổi lớp thành một giao diện có cùng tên và đổi tên triển khai cụ thể thành một mô tả.
Trong C #, giả định chính là khi bạn nhận được một lớp (tên không bắt đầu bằng I
), đó là lớp bạn muốn. Xin lưu ý bạn, đây không phải là chính xác gần 100% - một ví dụ điển hình sẽ là các lớp như Stream
(mà thực sự phải là một giao diện, hoặc một vài giao diện), và mọi người đều có hướng dẫn và nền tảng riêng từ các ngôn ngữ khác. Ngoài ra còn có các trường hợp ngoại lệ khác như Base
hậu tố được sử dụng khá rộng rãi để biểu thị một lớp trừu tượng - giống như với một giao diện, bạn biết loại được cho là đa hình.
Ngoài ra còn có một tính năng tiện dụng tuyệt vời khi để lại tên không có tiền tố I cho chức năng liên quan đến giao diện đó mà không phải dùng đến việc biến giao diện thành một lớp trừu tượng (sẽ bị tổn thương do thiếu tính kế thừa nhiều lớp trong C #). Điều này đã được phổ biến bởi LINQ, sử dụng IEnumerable<T>
làm giao diện và Enumerable
là kho lưu trữ các phương thức áp dụng cho giao diện đó. Điều này là không cần thiết trong Java, nơi các giao diện cũng có thể chứa các triển khai phương thức.
Cuối cùng, I
tiền tố được sử dụng rộng rãi trong thế giới C # và bằng cách mở rộng, thế giới .NET (vì phần lớn mã .NET được viết bằng C #, nên tuân theo hướng dẫn C # cho hầu hết các giao diện công cộng). Điều này có nghĩa là bạn gần như chắc chắn sẽ làm việc với các thư viện và mã theo ký hiệu này và việc áp dụng truyền thống để ngăn ngừa sự nhầm lẫn không cần thiết - không giống như bỏ qua tiền tố sẽ giúp mã của bạn trở nên tốt hơn :)
Tôi cho rằng lý luận của chú Bob là như thế này:
IBanana
là khái niệm trừu tượng của chuối. Nếu có thể có bất kỳ lớp triển khai nào không có tên nào tốt hơn Banana
, thì sự trừu tượng hóa là hoàn toàn vô nghĩa, và bạn nên bỏ giao diện và chỉ sử dụng một lớp. Nếu có một tên tốt hơn (giả sử, LongBanana
hoặc AppleBanana
), không có lý do gì để không sử dụng Banana
làm tên của giao diện. Do đó, việc sử dụng I
tiền tố biểu thị rằng bạn có một sự trừu tượng hóa vô dụng, điều này làm cho mã khó hiểu hơn mà không có lợi ích. Và vì OOP nghiêm ngặt sẽ giúp bạn luôn mã hóa các giao diện, nên nơi duy nhất bạn không thấy I
tiền tố trên một loại sẽ là trên một hàm tạo - tiếng ồn khá vô nghĩa.
Nếu bạn áp dụng điều này cho IParser
giao diện mẫu của mình , bạn có thể thấy rõ rằng sự trừu tượng hoàn toàn nằm trong lãnh thổ "vô nghĩa". Hoặc là có điều gì đó cụ thể về việc thực hiện cụ thể của một phân tích cú pháp (ví dụ JsonParser
, XmlParser
...), hoặc bạn chỉ nên sử dụng một lớp. Không có thứ gọi là "triển khai mặc định" (mặc dù trong một số môi trường, điều này thực sự có ý nghĩa - đáng chú ý là COM), hoặc có một triển khai cụ thể hoặc bạn muốn có một lớp trừu tượng hoặc các phương thức mở rộng cho "mặc định". Tuy nhiên, trong C #, trừ khi cơ sở mã của bạn đã bỏ qua I
-prefix, hãy giữ nó. Chỉ cần ghi chú tinh thần mỗi khi bạn thấy mã như thế class Something: ISomething
- điều đó có nghĩa là ai đó không giỏi làm theo YAGNI và xây dựng trừu tượng hợp lý.
[1] - Về mặt kỹ thuật, điều này không được đề cập cụ thể trong bài viết của Liskov, nhưng nó là một trong những nền tảng của bài báo OOP gốc và trong bài đọc về Liskov của tôi, cô ấy đã không thách thức điều này. Theo cách hiểu ít nghiêm ngặt hơn (một ngôn ngữ được sử dụng bởi hầu hết các ngôn ngữ OOP), điều này có nghĩa là bất kỳ mã nào sử dụng loại công khai nhằm thay thế (tức là không phải là cuối cùng / niêm phong) phải hoạt động với bất kỳ triển khai tuân thủ nào của loại đó.