Чи вважається інтерфейс "порожнім", якщо він успадковується від інших інтерфейсів?


12

Наскільки я можу сказати, порожні інтерфейси вважають поганою практикою - особливо там, де такі речі, як атрибути, підтримуються мовою.

Однак чи вважається інтерфейс "порожнім", якщо він успадковується від інших інтерфейсів?

interface I1 { ... }
interface I2 { ... } //unrelated to I1

interface I3
    : I1, I2
{
    // empty body
}

Все, що I3потрібно реалізовувати I1і I2, а також об'єкти з різних класів, які успадковують, I3можна потім використовувати взаємозамінно (див. Нижче), тож правильно називати I3 порожнім ? Якщо так, що було б кращим способом архітектури?

// with I3 interface
class A : I3 { ... }
class B : I3 { ... }

class Test {
    void foo() {
        I3 something = new A();
        something = new B();
        something.SomeI1Function();
        something.SomeI2Function();
    }
}

// without I3 interface
class A : I1, I2 { ... }
class B : I1, I2 { ... }

class Test {
    void foo() {
        I1 something = new A();
        something = new B();
        something.SomeI1Function();
        something.SomeI2Function(); // we can't do this without casting first
    }
}

1
Неможливо вказати кілька інтерфейсів як тип параметра / поля тощо - це недолік дизайну в C # / .NET.
CodesInChaos

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

@CodesInChaos Ні, це не так, як це вже два способи зробити. Одне в цьому питанні, іншим способом було б вказати параметр загального типу для аргументу методу та використовувати обмеження, де вказати обидва інтерфейси.
Енді

Чи є сенс, що клас, який реалізує I1 та I2, але не I3, не повинен бути в змозі використовуватись foo? (Якщо відповідь "так", тоді продовжуйте!)
користувач253751

@Andy Хоча я не повністю згоден з твердженням CodesInChaos, що це недолік як такий, я не думаю, що параметри загального типу в методі є рішенням - це покладає на відповідальність знати тип, який використовується в реалізації методу, на будь-який викликає метод, який почуває себе неправильно. Можливо, добре підійде якийсь синтаксис, схожий var : I1, I2 something = new A();на локальні змінні (якщо ігнорувати добрі моменти у відповіді Конрада)
Кай

Відповіді:


27

Все, що реалізує I3, потрібно буде реалізувати I1 та I2, а об'єкти з різних класів, які успадковують I3, потім можуть бути взаємозамінними (див. Нижче), тож правильно називати I3 порожнім? Якщо так, що було б кращим способом архітектури?

«Пакетування» I1і I2в I3пропозиції хороший (?) Ярлик, або яке - то синтаксичний для всюди , де ви хочете і повинні бути реалізовані.

Проблема такого підходу полягає в тому, що ви не можете зупинити інших розробників від явного впровадження I1 та I2 одночасно, але він не працює обома способами - хоча : I3це еквівалентно : I1, I2, : I1, I2не є еквівалентом : I3. Навіть якщо вони функціонально однакові, клас, який підтримує і те, I1і I2не буде виявлено як підтримуючий I3.

Це означає, що ви пропонуєте два способи досягнення одного і того ж - «ручний» та «солодкий» (із синтаксичним цукром). І це може погано вплинути на послідовність коду. Пропонуючи два різні способи досягти одного і того ж, тут, там, а в іншому місці - це вже 8 можливих комбінацій :)

void foo() {
    I1 something = new A();
    something = new B();
    something.SomeI1Function();
    something.SomeI2Function(); // we can't do this without casting first
}

Гаразд, ви не можете. Але, можливо, це насправді хороший знак?

Якщо I1і I2маються на увазі різні обов'язки (інакше не потрібно було б їх розділяти в першу чергу), то, можливо foo(), неправильно намагатися somethingвиконувати два різні обов'язки за один раз?

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

Редагувати:

Якщо ви наполягаєте на тому, щоб переконатися, що fooвикористовується щось, що реалізується, I1і I2ви можете передати їх як окремі параметри:

void foo(I1 i1, I2 i2) 

І вони можуть бути тим самим об’єктом:

foo(something, something);

Що fooбайдуже, якщо вони однакові чи ні?

Але якщо це все одно (наприклад, тому що вони не є без громадянства) або ви просто вважаєте, що це виглядає некрасиво, можна замість цього використати генеричні дані

void Foo<T>(T something) where T: I1, I2

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

Я I3 : I2, I1дещо сприймаю як зусилля з імітації типів об'єднання мовою, яка не підтримує його з поля (C # або Java не мають, тоді як типи об'єднання існують у функціональних мовах).

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


4

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

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


Так? Ви, звичайно, можете реалізувати два інтерфейси в одному класі. Ви не успадковуєте інтерфейси, ви реалізуєте їх.
Енді

@Andy Де я сказав, що один клас не може реалізувати два інтерфейси? Що стосується вживання слова «успадкувати» замість «здійснити», семантики. У C ++ спадкування спрацювало б чудово, оскільки найближчим до інтерфейсів є абстрактні класи.
Ніл

Успадкування та реалізація інтерфейсу - це не одне і те ж поняття. Класи, що успадковують декілька підкласів, можуть призвести до деяких дивних проблем, що не стосується впровадження декількох інтерфейсів. І інтерфейсів, у яких відсутні C ++, не означає, що різниця зникає, це означає, що C ++ обмежений тим, що може робити. Спадщина - це "є-а", а інтерфейси вказують "можна робити".
Енді

@Andy Weird проблеми, викликані зіткненням між методами, реалізованими у зазначених класах, так. Однак це вже не інтерфейс ні в імені, ні на практиці. Абстрактні класи, які декларують, але не реалізують методи, - це в будь-якому сенсі цього слова, крім назви, інтерфейсу. Термінологія змінюється між мовами, але це не означає, що посилання та вказівник - це не те саме поняття. Це педантичний момент, але якщо ви наполягаєте на цьому, тоді нам доведеться погодитися не погодитися.
Ніл

"Термінологія змінюється між мовами" Ні, це не так. Термінологія ОО є узгодженою для різних мов; Я ніколи не чув, щоб абстрактний клас означав щось інше в кожній мові ОО, якою я користувався. Так, ви можете мати семантику інтерфейсу на C ++, але справа в тому, що ніщо не заважає комусь переходити і додавати поведінку до абстрактного класу цією мовою і порушувати цю семантику.
Енді

2

Порожні інтерфейси, як правило, вважають поганою практикою

Я не погоджуюсь. Читайте про інтерфейси маркерів .

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


Я думаю, що ОП їх знає, але вони насправді трохи суперечливі. Хоча деякі автори рекомендують їх (наприклад, Джош Блох у "Ефективній Java"), деякі заявляють, що вони є антипатерном та спадщиною з часів, коли на Java ще не було анотацій. (Анотації на Java - приблизно еквівалент атрибутів у .NET). Моє враження, що вони набагато популярніші в світі Java, ніж .NET.
Конрад Моравський

1
Я користуюсь ними час від часу, але відчувається ненажерливість - недоліки в моїй голові полягають у тому, що ти не можеш допомогти тому, що їм передається у спадок, тому, коли ти позначиш клас з одним, усі його діти отримують позначено також, хочете ви цього чи ні. І вони, схоже, заохочують погану практику використання instanceofзамість поліморфізму
Конрад Моравський
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.