Triển khai giao diện khi bạn không cần một trong các thuộc tính


31

Khá đơn giản. Tôi đang triển khai một giao diện, nhưng có một thuộc tính không cần thiết cho lớp này và trên thực tế, không nên sử dụng. Ý tưởng ban đầu của tôi là chỉ làm một cái gì đó như:

int IFoo.Bar
{
    get { raise new NotImplementedException(); }
}

Tôi cho rằng không có gì sai với điều này, mọi người, nhưng nó không cảm thấy "đúng". Có ai khác gặp phải một tình huống tương tự trước đây? Nếu vậy, làm thế nào bạn tiếp cận nó?


1
Tôi mơ hồ nhớ lại rằng có một số lớp được sử dụng phổ biến trong C # thực hiện một giao diện nhưng nói rõ trong tài liệu rằng một phương thức nhất định không được thực hiện. Tôi sẽ thử xem tôi có thể tìm thấy nó không.
sư Xy

Tôi chắc chắn sẽ thích thú khi thấy điều đó, nếu bạn có thể tìm thấy nó.
Chris Pratt

23
Tôi có thể chỉ ra nhiều trường hợp này trong thư viện của .NET - VÀ HỌ TẤT CẢ NHẬN ĐƯỢC NHƯ NHIỀU MISTAKES TERRIBLE MISTAKES . Đây là một sự vi phạm mang tính biểu tượng và phổ biến của Nguyên tắc thay thế Liskov - những lý do không vi phạm LSP có thể được tìm thấy trong câu trả lời của tôi ở đây
Jimmy Hoffa

3
Bạn có bắt buộc phải thực hiện giao diện cụ thể này không, hoặc bạn có thể giới thiệu một siêu giao diện và sử dụng giao diện đó không?
Christopher Schultz

6
"Một thuộc tính không cần thiết cho lớp này" - Việc một phần của giao diện có cần thiết hay không là tùy thuộc vào các máy khách của giao diện, chứ không phải người triển khai. Nếu một lớp không thể thực hiện hợp lý một thành viên của giao diện, thì lớp đó không phù hợp với giao diện. Điều này có thể có nghĩa là giao diện được thiết kế kém - có thể cố gắng làm quá nhiều - nhưng điều đó không giúp ích gì cho lớp.
Sebastian Redl

Câu trả lời:


51

Đây là một ví dụ cổ điển về cách mọi người quyết định vi phạm Nguyên tắc thay thế Liskov. Tôi đặc biệt không khuyến khích điều đó nhưng sẽ khuyến khích một giải pháp khác:

  1. Có lẽ lớp bạn đang viết không cung cấp chức năng mà giao diện quy định nếu nó không sử dụng tất cả các thành viên của giao diện.
  2. Ngoài ra, giao diện đó có thể được thực hiện nhiều thứ và có thể được tách ra theo Nguyên tắc phân chia giao diện.

Nếu trường hợp đầu tiên là trường hợp của bạn, chỉ cần không thực hiện giao diện trên lớp đó. Hãy nghĩ về nó giống như một ổ cắm điện trong đó lỗ trên mặt đất là không cần thiết để nó không thực sự gắn vào mặt đất. Bạn không cắm bất cứ thứ gì có mặt bằng và không có vấn đề lớn! Nhưng ngay khi bạn sử dụng thứ gì đó cần một nền tảng - bạn có thể gặp thất bại ngoạn mục. Tốt hơn hết là đừng đục lỗ trên mặt đất giả. Vì vậy, nếu lớp của bạn không thực sự làm những gì giao diện dự định, đừng thực hiện giao diện.


Dưới đây là một vài bit nhanh từ wikipedia:

Nguyên tắc thay thế Liskov có thể được xây dựng đơn giản là, "Đừng củng cố các điều kiện trước và không làm suy yếu các điều kiện sau".

Chính thức hơn, nguyên tắc thay thế Liskov (LSP) là một định nghĩa cụ thể về mối quan hệ phụ, được gọi là phân nhóm hành vi (mạnh), được đưa ra ban đầu bởi Barbara Liskov trong một địa chỉ chính của hội nghị năm 1987 có tên là trừu tượng hóa và phân cấp. Đây là một mối quan hệ ngữ nghĩa chứ không chỉ đơn thuần là cú pháp bởi vì nó dự định đảm bảo khả năng tương tác ngữ nghĩa của các loại trong một hệ thống phân cấp , [...]

Đối với khả năng tương tác ngữ nghĩa và khả năng thay thế giữa các triển khai khác nhau của cùng một hợp đồng - bạn cần tất cả chúng để cam kết thực hiện cùng một hành vi.


Nguyên tắc phân chia giao diện nói lên ý tưởng rằng các giao diện nên được tách thành các bộ gắn kết sao cho bạn không yêu cầu giao diện thực hiện nhiều thứ khác nhau khi bạn chỉ muốn một cơ sở. Hãy nghĩ lại về giao diện của ổ cắm điện, nó cũng có thể có bộ điều chỉnh nhiệt, nhưng nó sẽ khiến việc lắp đặt ổ cắm điện trở nên khó khăn hơn và có thể khiến việc sử dụng cho mục đích không sưởi ấm trở nên khó khăn hơn. Giống như một ổ cắm điện có bộ điều chỉnh nhiệt, giao diện lớn rất khó thực hiện và khó sử dụng.

Nguyên tắc phân tách giao diện (ISP) nói rằng không có máy khách nào bị buộc phải phụ thuộc vào các phương thức mà nó không sử dụng. [1] ISP chia các giao diện rất lớn thành các giao diện nhỏ hơn và cụ thể hơn để khách hàng sẽ chỉ phải biết về các phương thức mà họ quan tâm.


Chắc chắn rồi. Đây có lẽ là lý do tại sao nó không cảm thấy đúng với tôi, ngay từ đầu. Đôi khi bạn chỉ cần một lời nhắc nhở nhẹ nhàng rằng bạn đang làm điều gì đó ngu ngốc. Cảm ơn.
Chris Pratt

@ChrisPratt đó là một lỗi thực sự phổ biến, đó là lý do tại sao các hình thức có thể có giá trị - phân loại mùi mã giúp nhận biết nhanh hơn và thu hồi các giải pháp được sử dụng trước đây.
Jimmy Hoffa

4

Có vẻ tốt với tôi, nếu đây là tình huống của bạn.

Tuy nhiên, đối với tôi, giao diện của bạn (hoặc sử dụng giao diện) bị hỏng nếu một lớp phái sinh không thực sự thực hiện tất cả giao diện đó. Xem xét tách giao diện đó lên.

Tuyên bố từ chối trách nhiệm: Điều này đòi hỏi nhiều kế thừa để thực hiện đúng và tôi không biết liệu C # có hỗ trợ điều đó không.


6
C # không hỗ trợ nhiều kế thừa của các lớp, nhưng nó hỗ trợ nó cho các giao diện.
mgw854

2
Tôi nghĩ bạn đúng. Giao diện hoàn toàn là một hợp đồng và ngay cả khi tôi biết lớp đặc biệt này sẽ không được sử dụng theo cách nó phá vỡ mọi thứ sử dụng giao diện nếu thuộc tính này bị vô hiệu hóa, điều đó không rõ ràng.
Chris Pratt


4

Tôi đã gặp tình huống này. Trong thực tế như đã chỉ ra ở nơi khác , BCL có những trường hợp như vậy ... Tôi sẽ cố gắng cung cấp các ví dụ tốt hơn và cung cấp một số lý do:

Khi bạn có một giao diện đã được giao mà bạn giữ vì lý do tương thích và ...

  • Giao diện chứa các thành viên đã lỗi thời hoặc bị mất phương hướng. Ví dụ BlockingCollection<T>.ICollection.SyncRoot(trong số những người khác) trong khi ICollection.SyncRootkhông bị lỗi thời, nó sẽ ném NotSupportedException.

  • Giao diện chứa các thành viên được ghi lại là tùy chọn và việc triển khai có thể đưa ra ngoại lệ. Ví dụ trên MSDN liên quan đến IEnumerator.Resetnó nói:

Phương thức Reset được cung cấp cho khả năng tương tác COM. Nó không nhất thiết phải được thực hiện; thay vào đó, người triển khai có thể chỉ cần ném NotSupportedException.

  • Do nhầm lẫn về thiết kế giao diện, nó đã có nhiều hơn một giao diện ở vị trí đầu tiên. Đây là một mẫu phổ biến trong BCL để triển khai các phiên bản chỉ đọc của các container NotSupportedException. Tôi đã thực hiện nó một mình, đó là những gì được mong đợi bây giờ ... Tôi thực hiện ICollection<T>.IsReadOnlytrở lại trueđể bạn có thể nói với họ appart. Thiết kế chính xác sẽ có phiên bản Giao diện dễ đọc và sau đó giao diện đầy đủ kế thừa từ đó.

  • Không có giao diện tốt hơn để sử dụng. Chẳng hạn, tôi có một lớp cho phép bạn truy cập các mục theo chỉ mục, kiểm tra xem nó có chứa một mục không và trên chỉ mục nào, nó có kích thước nào đó, bạn có thể sao chép nó vào một mảng ... có vẻ như là một công việc IList<T>nhưng lớp của tôi có một kích thước cố định và không hỗ trợ thêm hay xóa, vì vậy nó hoạt động giống như một mảng hơn là một danh sách. Nhưng không có IArray<T>trong BCL .

  • Giao diện thuộc về một API được chuyển đến nhiều nền tảng và trong quá trình triển khai một nền tảng cụ thể, một số phần của nó không được hỗ trợ. Lý tưởng nhất là sẽ có một số cách để phát hiện ra nó, để mã di động sử dụng API đó có thể quyết định bất cứ điều gì hoặc không gọi các bộ phận đó ... nhưng nếu bạn gọi chúng, thì hoàn toàn phù hợp để có được NotSupportedException. Điều này đặc biệt đúng, nếu đây là một cổng tới một nền tảng mới không lường trước được trong thiết kế ban đầu.


Cũng xem xét tại sao nó không được hỗ trợ?

Đôi khi InvalidOperationExceptionlà một lựa chọn tốt hơn. Ví dụ, một cách nữa để thêm tính đa hình trong một lớp là bằng cách thực hiện nhiều giao diện bên trong khác nhau và mã của bạn đang chọn cách nào để khởi tạo tùy thuộc vào các tham số được đưa ra trong hàm tạo của lớp. [Điều này rất hữu ích nếu bạn biết rằng tập hợp các tùy chọn đã được sửa và bạn không muốn cho phép các lớp bên thứ ba được giới thiệu bằng cách tiêm phụ thuộc.] Tôi đã thực hiện điều này để backport ThreadLocal vì việc triển khai theo dõi và không theo dõi là quá xa appart, và những gì ThreadLocal.Valuesném vào việc thực hiện không theo dõi?InvalidOperationExceptionthậm chí tho, nó không phụ thuộc vào trạng thái của đối tượng. Trong trường hợp này, tôi đã tự giới thiệu lớp và tôi biết rằng phương pháp này phải được thực hiện bằng cách chỉ cần ném một ngoại lệ.

Đôi khi một giá trị mặc định có ý nghĩa. Ví dụ như ICollection<T>.IsReadOnlyđã đề cập ở trên, sẽ rất hợp lý khi chỉ cần trả lại ọtrue, hoặc ´falfalse tùy theo trường hợp. Vậy ... ngữ nghĩa của là IFoo.Bargì? có thể có một số giá trị mặc định hợp lý để trả về.


Phụ lục: nếu bạn đang kiểm soát giao diện (và bạn không cần ở lại với nó để tương thích) thì không nên có trường hợp bạn phải ném NotSupportedException. Mặc dù, bạn có thể phải chia giao diện thành hai hoặc nhiều giao diện nhỏ hơn để phù hợp với trường hợp của bạn, điều này có thể dẫn đến "ô nhiễm" trong các tình huống khắc nghiệt.


0

Có ai khác gặp phải một tình huống tương tự trước đây?

Vâng, các nhà thiết kế thư viện .Net đã làm. Lớp Từ điển làm những gì bạn đang làm. Nó sử dụng triển khai rõ ràng để ẩn hiệu quả * một số phương thức IDadata. Ở đây , nó được giải thích tốt hơn , nhưng để tóm tắt, để sử dụng các phương thức Add, CopyTo hoặc Remove của Dictionary, sử dụng KeyValuePairs, trước tiên bạn phải truyền đối tượng vào IDadata.

* Không phải là "che giấu" các phương thức theo nghĩa chặt chẽ của từ "ẩn" khi Microsoft sử dụng nó . Nhưng tôi không biết một thuật ngữ tốt hơn trong trường hợp này.


... Thật là khủng khiếp.
Cuộc đua nhẹ nhàng với Monica

Tại sao các downvote?
2023861

2
Có lẽ bởi vì bạn đã không trả lời câu hỏi. Tự sướng. Sắp xếp
Cuộc đua nhẹ nhàng với Monica

@LightnessRacesinOrbit, tôi đã cập nhật câu trả lời của mình để làm cho nó rõ ràng hơn. Tôi hy vọng nó sẽ giúp OP.
2023861

2
Có vẻ như nó trả lời câu hỏi cho tôi. Thực tế rằng nó có thể là một ý tưởng tốt hay xấu dường như không nằm trong phạm vi của câu hỏi, nhưng vẫn có thể hữu ích cho các câu trả lời để chỉ ra.
Ellesedil

-6

Bạn luôn có thể thực hiện và trả về giá trị lành tính hoặc giá trị mặc định. Rốt cuộc nó chỉ là một tài sản. Nó có thể trả về 0 (thuộc tính mặc định của int) hoặc bất kỳ giá trị nào có ý nghĩa trong quá trình triển khai của bạn (int.MinValue, int.MaxValue, 1, 42, v.v ...)

//We don't officially implement this property
int IFoo.Bar
{
     { get; }
}

Ném một ngoại lệ có vẻ như hình thức xấu.


2
Tại sao? Làm thế nào là trả lại dữ liệu không chính xác tốt hơn?
Cuộc đua nhẹ nhàng với Monica

1
Điều này phá vỡ LSP: xem câu trả lời của Jimmy Hoffa để được giải thích tại sao.

5
Nếu không có giá trị trả về đúng có thể, tốt hơn là ném một ngoại lệ hơn là trả về giá trị không chính xác. Bất kể bạn làm gì, chương trình của bạn sẽ gặp trục trặc nếu nó vô tình gọi tài sản này. Nhưng nếu bạn ném một ngoại lệ, thì rõ ràng tại sao nó bị trục trặc.
Tanner Swett

Bây giờ tôi không biết giá trị sẽ không chính xác như thế nào khi người bạn thực hiện giao diện! Đó là sự thực hiện của bạn, nó có thể là bất cứ điều gì bạn muốn.
Jon Raynor

Điều gì xảy ra nếu giá trị chính xác không xác định theo thời gian biên dịch?
Andy
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.