Це шкідлива звичка не використовувати інтерфейси? [зачинено]


40

Я використовую інтерфейси рідко і знаходжу їх поширеними в інших кодах.

Також я створюю підкласні та суперкласи (створюючи власні класи) рідко в коді.

  • Це погано?
  • Ви б запропонували змінити цей стиль?
  • Чи має цей стиль побічні ефекти?
  • Це тому, що я не працював над якимись великими проектами?

10
Яка мова це?

2
Окрім якої мови, яка парадигма це?
Армандо

1
Хіба поняття "інтерфейс" не перевершує мову? У Java є вбудований тип інтерфейсу, але розробники C ++ часто також створюють інтерфейси (приєднуючи їх до I), і всі вони служать одній цілі.
Рікет

4
@Ricket: Ні, не дуже. Проблема в тому, що C ++ пропонує успадкування декількох класів. У C ++ "інтерфейс" набагато концептуальніший, і насправді ми в основному використовуємо те, що вони б визначили як абстрактний базовий клас. #
DeadMG

2
@ Ricket ні, особливо якщо ви працюєте на функціональній мові, а не на процедурній або OOP.
альтернатива

Відповіді:


36

Є кілька причин, чому ви можете використовувати інтерфейси:

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

http://msdn.microsoft.com/en-us/library/3b5b8ezk(v=vs.80).aspx

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


13
Його мова не вказана, а ваша занадто специфічна - наприклад, у C ++ структура може наслідувати наслідок від класу, а ви можете множити успадковувати неабразивні бази.
DeadMG

2
Здається, ця відповідь є C #, але стосується також Java, якщо ви виймаєте номер 4, і це стосується лише C ++, оскільки, звичайно, у C ++ дозволено багатократне успадкування.
Ricket

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

3
@Ricket: Чому саме в чотирьох пунктах моєї відповіді. Якщо жодна з цих причин не застосовується, інтерфейс вам не потрібен.
Роберт Харві

17

І спадкування класу, і інтерфейси мають своє місце. Спадщина означає "є", тоді як інтерфейс надає контракт, який визначає, як щось "поводиться".

Я б сказав, що частіше використовувати інтерфейси - це зовсім не погана практика. Зараз я читаю "Ефективний C # - 50 конкретних способів поліпшити свій C #" від Білла Вагнера. У пункті 22 зазначається, і я цитую "Віддавайте перевагу визначенню та реалізації інтерфейсів для спадкування".

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

Кілька цитат із книги Білла Вагнерса ...

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

"Інтерфейси кодування забезпечують більшу гнучкість для інших розробників, ніж кодування до типів базового класу."

"Використання інтерфейсів для визначення API для класу забезпечує більшу гнучкість."

"Коли ваш тип виставляє властивості як типи класів, він виставляє до цього класу весь інтерфейс. Використовуючи інтерфейси, ви можете викрити лише ті методи та властивості, якими ви хочете користуватися клієнтами."

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


2
Ну, я прочитав це і подумав, що це хороша відповідь.
Ерік Кінг

1
@ mc10 Не впевнений, у чому проблема. Він посилався на книгу, і нижня половина його відповіді - це лише цитати з тієї книги, які він чітко дає вам знати, якщо ви не хочете витрачати свій час.
Піт

@ Pete Я не сказав, що відповідь поганий, але лише, що це було занадто довго. Сумніваюсь, що вам іноді потрібна була ціла цитата.
kevinji

3
ха-ха, якщо це tl; dr, я не впевнений, що тобі сподобається вся ця StackExchange високоякісна відповідь.
Нік

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

8

Одне, що не було згадано, - це тестування: у всіх макетних бібліотеках C #, а також у деяких для Java, класам не можна глузувати, якщо вони не реалізують інтерфейс. Це призводить до багатьох проектів, що дотримуються практики Agile / TDD, щоб дати кожному класу власний інтерфейс.

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


Я думаю, що інтерфейси найкраще використовувати, коли у вас є два або більше класів, які, абстрактно, роблять «те саме», але різними способами.

Наприклад, .Net Framework має кілька класів, які зберігають списки речей , але всі вони зберігають різні речі. Таким чином, має сенс мати абстрактний IList<T>інтерфейс, який можна реалізувати за допомогою різних методів.

Ви також повинні використовувати його, коли ви хочете, щоб 2+ класи були взаємозамінними або заміними в майбутньому. Якщо в майбутньому з'явиться новий спосіб зберігання списків AwesomeList<T>, то, якщо припустити, що ви використовували IList<T>весь код, його зміна на використання AwesomeList<T>означатиме лише зміну кількох десятків рядків, а не кілька сотень / тисяч.


5

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


4

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

Проблема полягає в тому, що на таких мовах, як C # та Java зі слабкими загальними значеннями часу компіляції, ви можете порушити DRY, оскільки немає способу написати один метод, який може мати справу з більш ніж одним класом, якщо всі класи не успадковуються з однієї бази. Однак C # 4 dynamic може з цим впоратися.

Справа в тому, що успадкування - це як глобальні змінні - як тільки ви додасте його і ваш код залежить від нього, Бог допоможе вам його забрати. Однак ви можете додати його в будь-який час і навіть додати його, не змінюючи базовий клас, використовуючи обгортку сортів.


1
Причина для голосування?
DeadMG

Не впевнений, майте нагороду. Це була гарна відповідь.
Брайан Боттчер

3

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


3

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

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


2

Якщо ви говорите про використання Java, одна з причин використання інтерфейсів полягає в тому, що вони дозволяють використовувати проксі-об'єкти без бібліотек генерації коду. Це може бути суттєвою перевагою, коли ви працюєте зі складними рамками, такими як Spring. Більше того, для деяких функціональних можливостей потрібні інтерфейси: RMI - класичний приклад цього, оскільки ви повинні описати функціонал, який ви надаєте з точки зору інтерфейсів (які успадковуються від java.rmi.Remote), однак ви хочете реалізувати їх.


1

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

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


0

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

Погано:

class B; // forward declaration
class A
{
  B* b;
};

class B
{
  A* a;
}

Непогано:

class PartOfClassB_AccessedByA
{
};

class A
{
  PartOfClassB_AccessedByA* b;
};

class B : public PartOfClassB_AccessedByA
{
  A* a;
}

Зазвичай A, B, PartOfClassB_AccessedByA реалізується з окремими файлами.


0

Програмування на основі інтерфейсу допомагає зробити код більш гнучким і легшим для тестування. Класи реалізації можна змінювати, не торкаючись клієнтського коду [Гнучкість]. Хоча тестування коду може замінити фактичне програмування на основі інтерфейсу, допомагає зробити код більш гнучким та легшим для тестування. Класи реалізації можна змінювати, не торкаючись клієнтського коду [Гнучкість]. Під час тестування коду можна замінити фактичний об’єкт макетними об'єктами [Testability] .bject об'єктами макету [Testability].

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