Реалізація інтерфейсу, коли вам не потрібен один із властивостей


31

Досить прямо. Я реалізую інтерфейс, але є одна властивість, непотрібна для цього класу, і насправді його не слід використовувати. Моя початкова ідея полягала в тому, щоб просто зробити щось на кшталт:

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

Я гадаю, що в цьому немає нічого поганого, але це не є "правильним". Хтось раніше раніше стикався з подібною ситуацією? Якщо так, то як ви підійшли до цього?


1
Я тумано згадую, що в C # є якийсь напівпоширений клас, який реалізує інтерфейс, але в документації прямо вказано, що певний метод не реалізований. Я спробую побачити, чи зможу його знайти.
Маг Сі

Мені напевно було б цікаво побачити це, якщо ви зможете його знайти.
Кріс Пратт

23
Я можу вказати на декілька випадків цього в бібліотеці .NET - І ВСІ ВІДПОВІДАЮТЬСЯ БУДИМИ ГРОМОВИМИ ПОМИЛЬНІСТЬ . Це знакове і поширене порушення Принципу заміни Ліскова - причини не порушувати LSP можна знайти у моїй відповіді тут
Джиммі Хоффа

3
Чи потрібно вам реалізувати цей специфічний інтерфейс, чи можете ви ввести суперінтерфейс і використовувати його?
Крістофер Шульц

6
"одна властивість, яка не потрібна для цього класу" - чи потрібна частина інтерфейсу, залежить від клієнтів інтерфейсу, а не від виконавців. Якщо клас не може розумно реалізувати член інтерфейсу, то клас не є належним чином для інтерфейсу. Це може означати, що інтерфейс погано розроблений - можливо, намагається зробити занадто багато - але це не допомагає класу.
Себастьян Redl

Відповіді:


51

Це класичний приклад того, як люди вирішують порушити Принцип заміщення Ліскова. Я настійно не перешкоджаю цьому, але заохочую, можливо, інше рішення:

  1. Можливо, клас, який ви пишете, не забезпечує функціонал, який призначений інтерфейсом, якщо він не використовує всіх членів інтерфейсу.
  2. Крім того, цей інтерфейс може робити кілька речей і може бути розділений відповідно до принципу поділу інтерфейсу.

Якщо це стосується вас першого, просто не реалізуйте інтерфейс цього класу. Думайте про це як про електричну розетку, де отвір для заземлення є непотрібним, щоб він фактично не кріпився до землі. Ви нічого не затикаєте в землю і нічого страшного! Але як тільки ти використаєш щось, що потребує ґрунту, - ти можеш стати вражаючим невдачею. Краще не пробиваючи підроблені меленого отвір в. Так що, якщо ваш класі не на самому ділі робити те , що інтерфейс має намір, не реалізує інтерфейс.


Ось декілька швидких бітів з Вікіпедії:

Принцип заміщення Ліскова можна просто сформулювати так: "Не зміцнюйте передумови та не послаблюйте пост-умови".

Більш формально, принцип заміщення Ліскова (LSP) - це особливе визначення підтипового відношення, яке називається (сильним) поведінковим підтипом, яке було спочатку запроваджене Барбарою Ліськовим у виступленні конференції 1987 року під назвою Абстракція даних та ієрархія. Це швидше семантичне, а не просто синтаксичне відношення, оскільки воно має намір гарантувати семантичну сумісність типів в ієрархії , [...]

Для семантичної сумісності та замінюваності між різними реалізаціями одних і тих самих контрактів - вам потрібно, щоб усі вони здійснювали однакові поведінки.


Принцип поділу інтерфейсу говорить про ідею, що інтерфейси слід розділяти на згуртовані набори, щоб вам не потрібен інтерфейс, який робить багато розрізнених речей, коли ви хочете лише один об'єкт. Подумайте ще раз про інтерфейс електричної розетки, він також може мати терморегулятор, але це ускладнить установку електричної розетки і може ускладнити її використання в неопалювальних цілях. Як і електрична розетка з термостатом, великі інтерфейси важко реалізувати і важко використовувати.

Принцип сегрегації інтерфейсу (ISP) зазначає, що жоден клієнт не повинен бути змушений залежати від методів, які він не використовує. [1] ISP розбиває дуже великі інтерфейси на більш дрібні та конкретні, тому клієнтам доведеться лише знати про цікаві для них методи.


Абсолютно. Це, мабуть, тому, в першу чергу, мені це не було правильно. Іноді просто потрібно ніжне нагадування, що ти робиш щось дурне. Спасибі.
Кріс Пратт

@ChrisPratt - це дійсно поширена помилка, тому формалізми можуть бути цінними - класифікація запахів коду допомагає швидше їх виявити та згадати раніше використані рішення.
Джиммі Хоффа

4

Мені добре здається, якщо це ваша ситуація.

Однак мені здається, що ваш інтерфейс (або його використання) порушений, якщо похідний клас насправді не реалізує все це. Розгляньте розділення цього інтерфейсу.

Відмова від відповідальності: для правильного здійснення цього вимагається багатократне успадкування, і я не маю поняття, чи підтримує це C #.


6
C # не підтримує множинне успадкування класів, але він підтримує інтерфейси.
mgw854

2
Я думаю, ти маєш рацію. Зрештою, інтерфейс - це контракт, і навіть якщо я знаю, що цей клас не буде використовуватися таким чином, що він порушує все, що використовує інтерфейс, якщо це властивість вимкнено, це не очевидно.
Кріс Пратт


4

Я натрапив на цю ситуацію. Фактично, як вказувалося інших місцях, у БКЛ є такі випадки ... Я спробую навести кращі приклади та навести деякі обґрунтування:

Коли у вас вже є інтерфейс, що постачається, який ви зберігаєте з міркувань сумісності та ...

  • Інтерфейс містить членів, які застаріли або видалені. Наприклад, BlockingCollection<T>.ICollection.SyncRoot(серед інших), якщо ICollection.SyncRootвона сама по собі не застаріла, вона кинеться NotSupportedException.

  • Інтерфейс містить учасників, задокументованих як необов'язкові, і реалізація може кинути виняток. Наприклад, про MSDN щодо IEnumerator.Resetцього написано:

Метод Скидання передбачений для сумісності з COM. Це не обов'язково потрібно реалізовувати; натомість реалізатор може просто кинути NotSupportedException.

  • Помилково оформляючи інтерфейс, у першу чергу це повинно було бути більше одного інтерфейсу. Це звичайна модель в BCL для реалізації версій контейнерів з читанням NotSupportedException. Я це зробив сам, це те, що зараз очікується ... Я ICollection<T>.IsReadOnlyповертаюсяtrue щоб ви могли сказати їм про себе. Правильна конструкція мала б мати читану версію інтерфейсу, і тоді повний інтерфейс успадковується від цього.

  • Немає кращого інтерфейсу для використання. Наприклад, у мене є клас, який дозволяє вам отримувати доступ до елементів за індексом, перевіряйте, чи містить він елемент, а за яким індексом він має якийсь розмір, ви можете скопіювати його в масив ... це здається роботою, IList<T>але мій клас має фіксованого розміру і не підтримує додавання та видалення, тому він працює більше як масив, ніж список. Але є НЕ IArray<T>в BCL .

  • Інтерфейс належить API, який переноситься на кілька платформ, а при реалізації певної платформи деякі його частини не підтримуються. В ідеалі був би якийсь спосіб його виявити, так що портативний код, який використовує такий API, може вирішувати, чи не називати ці частини ... чи, якщо ви їх зателефонуєте, це цілком доречно NotSupportedException. Це особливо вірно, якщо це порт на нову платформу, яка не була передбачена в оригінальному дизайні.


Також врахуйте, чому це не підтримується?

Іноді InvalidOperationExceptionкращий варіант. Наприклад, ще одним способом додати поліморфізм до класу є різноманітна реалізація внутрішнього інтерфейсу, і ваш код вибирає, який саме інстанціювати, залежно від параметрів, наведених у конструкторі класу. [Це особливо корисно, якщо ви знаєте, що набір опцій виправлений, і ви не хочете дозволити вводити сторонні класи шляхом введення залежностей.] Я зробив це, щоб підтримати ThreadLocal оскільки реалізація відстеження та відстеження є Занадто далеко, і що може впливати на непростежувальну реалізацію? навіть тхо , це не залежить від стан об’єкта. У цьому випадку я сам представив клас, і я знав, що цей метод потрібно реалізувати, просто кинувши виняток.ThreadLocal.ValuesInvalidOperationException

Іноді значення за замовчуванням має сенс. Наприклад, у ICollection<T>.IsReadOnlyзгаданому вище, має сенс просто повернути «треть» або «фальсифікат» залежно від випадку. Отже ... що таке семантика IFoo.Bar? можливо, якесь розумне значення за замовчуванням повернути.


Додаток: якщо ви керуєте інтерфейсом (і вам не потрібно залишатися з ним для сумісності), не повинно бути випадків, коли вам доведеться кидати NotSupportedException. Хоча, можливо, вам доведеться розділити інтерфейс на два або більше менших інтерфейсу, щоб вони правильно підходили до вашого випадку, що може призвести до «забруднення» в екстремальних ситуаціях.


0

Хтось раніше раніше стикався з подібною ситуацією?

Так, дизайнери бібліотек .Net зробили. Клас Словник робить те, що ви робите. Він використовує явну реалізацію для ефективного приховування * деяких методів IDictionary. Тут це пояснено краще , але, підсумовуючи, для використання методів Add, CopyTo або Remove у словнику, які беруть KeyValuePairs, спочатку потрібно закинути об'єкт на IDictionary.

* Це не "приховування" методів у суворому розумінні слова "ховати", як це використовує Microsoft . Але я не знаю кращого терміна в цьому випадку.



Чому потік?
користувач2023861

2
Можливо, тому, що ви не відповіли на запитання. Іш. Типу.
Гонки легкості з Монікою

@LightnessRacesinOrbit, я оновив свою відповідь, щоб зробити її більш зрозумілою. Я сподіваюся, що це допомагає ОП.
користувач2023861

2
Схоже, це відповідає мені на питання. Той факт, що це може бути хорошою чи поганою ідеєю, схоже, не входить в рамки питання, але все ж може бути корисним для відповідей, щоб вказати.
Ellesedil

-6

Ви завжди можете просто реалізувати та повернути доброякісне значення або значення за замовчуванням. Адже це лише власність. Він може повернути 0 (властивість int за замовчуванням) або будь-яке значення, яке має сенс у вашій реалізації (int.MinValue, int.MaxValue, 1, 42 тощо).

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

Кидати виняток здається поганою формою.


2
Чому? Чим краще повернення невірних даних?
Гонки легкості з Монікою

1
Це порушує LSP: див. Відповідь Джиммі Хоффи для пояснення того, чому.

5
Якщо немає можливих правильних значень повернення, краще кинути виняток, ніж повертати неправильне значення. Незалежно від того, що ви робите, ваша програма вийде з ладу, якщо вона випадково викликає цю властивість. Але якщо ви кинете виняток, тоді буде очевидно, чому це несправно.
Tanner Swett

Я зараз не знаю, як значення було б невірним, коли ваш реалізує інтерфейс! Це ваша реалізація, вона може бути все, що завгодно.
Джон Рейнор

Що робити, якщо правильне значення було невідомим під час компіляції?
Енді
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.