Чи запах коду порожніх інтерфейсів? [зачинено]


76

У мене є функція, яка повертає об'єкти одного типу (результати запитів), але не має спільних властивостей або методів. Для того, щоб мати загальний тип, я вдався до використання порожнього інтерфейсу як типу повернення і "реалізував" це на обох.

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

Мені також сказали, що .NET Framework використовує порожні інтерфейси для позначення.

Моє запитання: чи порожній інтерфейс є сильним знаком проблеми дизайну чи він широко використовується?

EDIT : Для зацікавлених пізніше я дізнався, що дискриміновані спілки функціональними мовами є ідеальним рішенням для того, чого я намагався досягти. C # ще не здається доброзичливим до цієї концепції.

EDIT : Я написав довшу статтю про цю проблему, детально пояснивши проблему та рішення.


15
Вони називаються маркерними інтерфейсами, і, очевидно, вони широко використовуються.
BoltClock

3
Прочитайте це msdn.microsoft.com/en-us/library/ms182128%28v=vs.80%29.aspx (моя думка повинна бути різними методами)
V4Vendetta

1
Що - то за аналогією stackoverflow.com/questions/835140 / ...
V4Vendetta

9
Чи може запах коду бути приємним запахом, і в такому випадку це добре? Кава, вафлі та бекон, наприклад
Chris S

Коли ви викликаєте свою функцію, ви вже знаєте з параметрів, які ви задаєте, який об'єкт повертається? Якщо так, то матиме кілька функцій, що повертають належний тип, буде більш правильним.
Angel O'Sphere

Відповіді:


48

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

Як розміщував @ V4Vendetta, існує правило статичного аналізу, яке націлено на це: http://msdn.microsoft.com/en-us/library/ms182128(v=VS.100).aspx

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

Ось цитована рекомендація MSDN:

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

Це також відображає розділ "Критика" вже розміщеного посилання на Вікіпедію.

Основною проблемою інтерфейсів маркерів є те, що інтерфейс визначає контракт на реалізацію класів, і цей контракт успадковується усіма підкласами. Це означає, що ви не можете "здійснити" маркер. У наведеному прикладі, якщо ви створюєте підклас, який ви не хочете серіалізувати (можливо, тому, що це залежить від перехідного стану), вам слід вдатися до явного кидання NotSerializableException (за документами ObjectOutputStream).


1
Я думаю, що у моєму варіанті використання (лише два класи та навряд чи будуть отримані), це здається нормальним. Див. Моє роз’яснення редагування.
Седат Капаноглу

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

1
Як вже зазначалося в інших місцях, хоча attributesвони є "правильним" способом зробити це, їх незручніше впроваджувати та використовувати, що на практиці буде працювати проти їх використання.
nicodemus13

9

Ви заявляєте, що ваша функція "повертає абсолютно різні об'єкти на основі певних випадків" - але наскільки вони різні? Чи може один бути автором потоку, інший - класом інтерфейсу, іншим - об’єктом даних? Ні ... я сумніваюся!

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


Це різні типи запитів, але обидва результати запиту, так.
Седат Капаноглу

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

8

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

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


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

@ssg - цілком. Має бути щось подібне з цими об’єктами, якщо ви передаєте їх одному методу, щоб оперувати ними, інакше у вас повинні бути окремі методи.
Оновлено

6

Ви відповіли на власне запитання ... "У мене є функція, яка повертає абсолютно різні об'єкти на основі певних випадків." ... Чому ви хочете мати ту саму функцію, яка повертає абсолютно різні об'єкти? Я не бачу причини, щоб це було корисно, можливо, у вас є хороша, і в цьому випадку, будь ласка, поділіться.

РЕДАГУВАТИ: Враховуючи ваше роз'яснення, ви дійсно повинні використовувати маркерний інтерфейс. "зовсім інший" - це зовсім інше, ніж "є однакові". Якби вони були абсолютно різними (не лише тим, що у них немає спільних учасників), це був би запах коду.


Це схоже на кращого кандидата на коментар. До свого запитання я додав розділ роз’яснень. Попросіть, чи вам ще щось потрібно.
Седат Капаноглу

4

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

Ймовірно, найкраще використання, яке я можу придумати, - це позначення об’єкта як належності до певної підмножини домену, що обробляється відповідним сховищем. Скажімо, у вас є різні бази даних, з яких ви отримуєте дані, і у вас є реалізація сховища для кожної. Конкретний сховище може обробляти лише одну підмножину, і йому не слід надавати екземпляр об’єкта з будь-якої іншої підмножини. Модель вашого домену може виглядати так:

//Every object in the domain has an identity-sourced Id field
public interface IDomainObject
{
   long Id{get;}
}

//No additional useful information other than this is an object from the user security DB
public interface ISecurityDomainObject:IDomainObject {}

//No additional useful information other than this is an object from the Northwind DB
public interface INorthwindDomainObject:IDomainObject {}


//No additional useful information other than this is an object from the Southwind DB
public interface ISouthwindDomainObject:IDomainObject {}

Потім ваші сховища можна зробити загальними для ISecurityDomainObject, INorthwindDomainObject та ISouthwindDomainObject, і тоді у вас буде перевірка під час компіляції, що ваш код не намагається передати об'єкт безпеки в БД Northwind (або будь-яку іншу перестановку). У таких ситуаціях інтерфейс надає цінну інформацію щодо природи класу, навіть якщо він не передбачає жодного контракту на реалізацію.

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