Чи потрібно використовувати інтерфейс, коли його буде реалізовувати лише один клас?


243

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



16
Або для того, щоб зробити його легшим для тестування.

11
Дозволити декілька класів реалізовувати інтерфейси та мати свій код залежно від інтерфейсів - ОСОБЛИВО ізоляція для тестування одиниць. Якщо ви проводите тестування одиниць, у вас буде інший клас, що реалізує цей інтерфейс.
StuperUser


6
Публічні поля та методи самі по собі є "інтерфейсом". Якщо брак поліморфізму навмисно планується, тоді немає ніяких причин використовувати інтерфейс. Одиничне тестування інших згаданих - це заплановане використання поліморфізму.
mike30

Відповіді:


205

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

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


84
Тестування +1 означає, що ви майже завжди маєте дві реалізації
jk.

24
@YannisRizos Не погоджуйтесь з вашою останньою точкою через Yagni. Виведення інтерфейсу з загальнодоступних методів класів є тривіальним після факту, як і заміна CFoo на IFoo у споживчих класах. Немає сенсу писати це заздалегідь про необхідність.
Ден Нілі

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

6
Я думаю, що відсутній інтерфейс - не хороший приклад для YAGNI, але для "розбитого вікна" та відсутньої документації. Користувачі класу практично змушені кодувати проти реалізації замість абстракції, як слід.
Фабіо Фракассі

10
Чому б ви коли-небудь забруднювали свою кодову базу безглуздою суттю просто для задоволення деяких тестових рамок? Я маю на увазі серйозно, я просто хлопець, який працює на базі JavaScript, який намагається розібратися в WTF, не так з реалізаціями OOD для розробників C # і Java, я все ще зустрічаюсь у своєму прагненні стати більш чітким загальним фахівцем, але чому не варто ' t вони ляскають IDE з ваших рук і не дозволять вам мати його назад, поки ви не навчитеся писати чистий, розбірливий код, коли вони застануть вас робити такі речі в коледжі? Це просто нецензурно.
Ерік Реппен

144

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

Ось одне дуже корисне правило:

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

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

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


14
Я б схвалив це набагато більше разів, якби міг. ІМО найкраща відповідь на це питання.
Фабіо Фракассі

4
Якщо клас виконує лише одну роль (тобто для нього буде визначений лише один інтерфейс), то чому б цього не зробити за допомогою публічних / приватних методів?
Меттью Фінлай

4
1. Організація коду; наявність інтерфейсу у власному файлі, який містить лише підписи та коментарі до документації, допомагає зберегти чистий код. 2. Структури, які змушують викривати методи, які ви бажаєте залишати приватними (наприклад, контейнери, які вводять залежності через публічні сетери). 3. Окреме складання, як уже говорилося; якщо клас Fooзалежить від інтерфейсу, Barто BarImplйого можна змінити без необхідності перекомпілювати Foo. 4. Більш тонкий контроль доступу, ніж публічні / приватні пропозиції (піддайте один клас двом клієнтам через різні інтерфейси).
sacundim

3
І останнє: 5. Клієнтам (в ідеалі) не повинно байдуже, скільки класів має мій модуль чи скільки, або навіть вміти розповісти. Все, що вони повинні бачити, - це набір типів , а також деякі фабрики чи фасади для того, щоб кулька котилася. Тобто, я часто бачу цінність в інкапсуляції навіть того, з яких класів складається ваша бібліотека.
sacundim

4
@sacundim If you make class Foo refer directly to class BarImpl, you're strongly committing yourself to change Foo every time you change BarImplЗмінивши BarImpl, які зміни можна уникнути в Foo, використовуючи interface Bar? Оскільки підписи та функціональність методу не змінюється в BarImpl, Foo не вимагатиме змін навіть без інтерфейсу (вся мета інтерфейсу). Я говорю про сценарій, коли лише один клас, тобто BarImplреалізує Bar. У багатокласовому сценарії я розумію принцип інверсії залежності і наскільки корисний інтерфейс.
Шишир Гупта

101

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

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


Не завжди ви можете використовувати інтерфейс в одному класі, якщо цей клас є загальним класом.
Джон Ісая Кармона

Крім того, ви можете ввести інтерфейс, коли він, нарешті, потрібен легко в сучасній IDE, використовуючи рефакторинг "екстракту інтерфейсу".
Ганс-Пітер Стрер

Розглянемо клас корисності, де існують лише загальнодоступні статичні методи та приватний конструктор - цілком прийнятний та пропагуваний такими авторами, як Джошуа Блох та ін.
Даррелл Тіг

63

Хоча теоретично у вас не повинно бути інтерфейсу тільки заради інтерфейсу, відповідь Яніса Різоса натякає на подальші ускладнення:

Коли ви пишете одиничні тести і використовуєте макетні рамки, такі як Moq або FakeItEasy (щоб назвати два останніх, які я використав), ви неявно створюєте інший клас, який реалізує інтерфейс. Пошук у коді або статичний аналіз може стверджувати, що існує лише одна реалізація, але насправді є внутрішня макетна реалізація. Щоразу, коли ви починаєте писати макети, ви виявите, що витяг інтерфейсів має сенс.

Але зачекайте, є ще більше. Існує більше сценаріїв, коли є неявні реалізації інтерфейсу. Наприклад, використовуючи стек зв'язку .NET WCF, наприклад, генерує проксі для віддаленої служби, яка, знову ж таки, реалізує інтерфейс.

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


2
+1: Я не впевнений, що YAGNI застосовується тут, оскільки інтерфейс та використання фреймів (наприклад, JMock тощо), які використовують інтерфейси, можуть насправді заощадити ваш час.
Деко

4
@Deco: хорошим глузуючим фреймворкам (серед них JMock) навіть інтерфейси не потрібні.
Майкл Боргвардт

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

8
Я б сказав, що це навпаки. Використання макетних об'єктів при тестуванні означає створення альтернативних реалізацій для ваших інтерфейсів за визначенням. Це вірно, чи ви створюєте власні класи FakeImplementation чи дозволяєте глузливим фреймворкам робити важкий підйом для вас. Можливо, є деякі рамки, як, наприклад, EasyMock, які використовують різні хакі та хитрості низького рівня, щоб знущатися з конкретних класів - і більше сил для них! - але концептуально, макетні об'єкти - це альтернативні варіанти виконання контракту.
Авнер Шахар-Каштан

1
Ви не знімаєте тестів у виробництві. Чому макет класу, якому не потрібен інтерфейс, потрібен інтерфейс?
Ерік Реппен

32

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

Виробляти Foo / FooImpl для всіх реально варто. IDE може створити інтерфейс / реалізацію безкоштовно, але коли ви переходите до коду, у вас з'являється додаткове когнітивне навантаження від F3 / F12, foo.doSomething()щоб перейти до підпису інтерфейсу, а не до реальної реалізації, яку ви хочете. Плюс у вас є захаращеність двох файлів замість одного на все.

Тож вам слід робити це лише тоді, коли вам щось потрібно.

Зараз звертаємось до контраргументів:

Мені потрібні інтерфейси для структур вприскування Dependency

Інтерфейси для підтримки фреймворків застарілі. У Java інтерфейси, що раніше були вимогою до динамічних проксі, попередньо CGLIB. Сьогодні вам це зазвичай не потрібно. Прогрес і користь для продуктивності розробників вам більше не потрібні в EJB3, Spring тощо.

Мені потрібні макети для тестування одиниць

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

Але якщо ви використовуєте глузуючі рамки, такі як Moq, EasyMock або Mockito, ви можете знущатися над класами, і вам не потрібні інтерфейси. Це аналогічно налаштуванню foo.method = mockImplementationв динамічній мові, де можна призначити методи.

Нам потрібні інтерфейси для дотримання принципу інверсії залежності (DIP)

DIP говорить, що ви будуєте, щоб залежати від контрактів (інтерфейсів), а не від реалізації. Але клас - це вже договір і абстракція. Ось для чого і публічні / приватні ключові слова. В університеті канонічним прикладом було щось на кшталт матричного чи поліномального класу - споживачі мають публічний API для створення матриць, додавання їх тощо, але їм заборонено дбати, чи матриця реалізована у розрідженій чи щільній формі. Для доказу цього пункту не було потрібних IMatrix або MatrixImpl.

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

Це є наслідком для думки дядька Боба Мартіна щодо глузування - вам слід лише знущатися над основними архітектурними межами. У веб-програмі основними межами є доступ до HTTP та БД. Усі дзвінки класу / методу між ними не є. Те саме стосується і DIP.

Дивитися також:


4
Смішні класи, а не інтерфейси (принаймні в Java та C #) слід вважати застарілими, оскільки немає жодного способу запобігти запуску конструктора надкласового класу, що може спричинити невмисний взаємозв'язок вашого об’єкта з навколишнім середовищем. Знущання над інтерфейсом безпечніше і простіше, оскільки не потрібно думати про код конструктора.
Жуль

4
Я не стикався з проблемами з глузуючими класами, але мене розчарувала навігація IDE, яка не працює, як очікувалося. Вирішення реальної проблеми перемагає гіпотетичну.
wrschneider

1
@Jules Ви можете знущатися над конкретним класом, включаючи його конструктор на Java.
assylias

1
@assylias як ти не дозволяєш конструктору працювати?
Жуль

2
@Jules Це залежить від вашої глузливої ​​структури - наприклад, з jmockit ви можете просто писати, new Mockup<YourClass>() {}і весь клас, включаючи його конструктори, знущаються, будь то інтерфейс, абстрактний клас чи конкретний клас. Ви також можете "перекрити" поведінку конструктора, якщо бажаєте цього зробити. Я припускаю, що в Mockito або Powermock є рівнозначні способи.
assylias

22

Схоже, відповіді з обох боків огорожі можна підсумувати так:

Добре спроектуйте і поставте інтерфейси там, де потрібні інтерфейси.

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

На прикладі (жахливо надуманого) припустімо, що ви будуєте Carклас. У вашому класі вам неодмінно знадобиться шар інтерфейсу. У цьому конкретному прикладі, він приймає форму IginitionSwitch, SteeringWheel, GearShift, GasPedal, і BrakePedal. Оскільки цей автомобіль містить AutomaticTransmission, вам не потрібно ClutchPedal. (А оскільки це жахливий автомобіль, немає А / С, радіо чи сидіння. Насправді, профнастилу теж не вистачає - потрібно просто повіситись на це кермо і сподіватися на найкраще!)

Тож якому з цих класів потрібен інтерфейс? Відповідь може бути всі вони, або жодна з них - залежно від вашого дизайну.

Ви можете мати інтерфейс, який виглядав приблизно так:

Interface ICabin
    Event IgnitionSwitchTurnedOn()
    Event IgnitionSwitchTurnedOff()
    Event BrakePedalPositionChanged(int percent)
    Event GasPedalPositionChanged(int percent)
    Event GearShiftGearChanged(int gearNum)
    Event SteeringWheelTurned(float degree)
End Interface

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

Це та сама причина, що ви не використовуєте тестування приватних членів - вони існують лише для підтримки загальнодоступного API, і тому їх поведінку слід перевірити шляхом тестування API.

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


Редагувати:

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

Територія, над якою здійснюється панування чи влада; володіння суверена або співдружності тощо. Також використовується образно. [WordNet smislu 2] [1913 Вебстер]

З точки зору мого прикладу - розглянемо IgnitionSwitch. У машині м'ясного простору перемикач запалювання відповідає за:

  1. Автентифікація (не ідентифікація) користувача (їм потрібен правильний ключ)
  2. Надання струму стартеру, щоб він фактично міг запустити машину
  3. Подача струму в систему запалювання, щоб вона могла продовжувати працювати
  4. Відключивши струм, тому машина зупиниться.
  5. Залежно від того, як ви його бачите, у більшості (всіх?) Нових автомобілів є вимикач, який запобігає видаленню ключа від запалювання, поки передача знаходиться поза Парк, тому це може бути частиною його домену. (Насправді це означає, що мені потрібно переосмислити та переробити свою систему ...)

Ці властивості складають домен того IgnitionSwitch, про що він знає і за що відповідає.

The IgnitionSwitchне несе відповідальності за GasPedal. Перемикач запалювання абсолютно не знає педалі газу. Вони обидва працюють абсолютно незалежно одне від одного (хоча автомобіль був би бездоганним без обох!).

Як я спочатку зазначив, це залежить від вашого дизайну. Ви можете спроектувати зображення, IgnitionSwitchяке має два значення: On ( True) та Off ( False). Або ви можете спроектувати його для автентифікації наданого для нього ключа та безлічі інших дій. Це складна частина того, щоб бути розробником, вирішуючи, де намалювати лінії на піску - і, чесно кажучи, більшість часу це абсолютно відносно. Ці рядки в піску є важливими - саме там знаходиться ваш API, а значить, там, де мають бути ваші інтерфейси.


Не могли б ви детальніше розібратися, що ви маєте на увазі під "маєте власний домен"?
Ламін Санне

1
@LaminSanneh, докладно. Чи допомагає це?
Уейн Вернер

8

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


8

Від MSDN :

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

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

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

Інтерфейси корисні у випадках, коли ви не можете використовувати успадкування класів. Наприклад, структури не можуть успадкувати класи, але вони можуть реалізувати інтерфейси.

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


5

Щоб відповісти на питання: Є в цьому більше, ніж це.

Одним з важливих аспектів інтерфейсу є намір.

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

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


5

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

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

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

Хоча модуль B має лише одну реалізацію свого інтерфейсу, інтерфейс все ще необхідний.

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


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

4

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

Клас реалізує інший інтерфейс, який я не хочу виставляти
часто, з класом адаптерів, який з'єднує код сторонніх розробників.

interface NameChangeListener { // Implemented by a lot of people
    void nameChanged(String name); 
} 

interface NameChangeCount { // Only implemented by my class
    int getCount();
}

class NameChangeCounter implements NameChangeListener, NameChangeCount {
    ...
}

class SomeUserInterface {
    private NameChangeCount currentCount; // Will never know that you can change the counter
}

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

interface SomeRepository { // Guarantee that the external library details won't leak trough
    ...
}

class OracleSomeRepository implements SomeRepository { 
    ... // Oracle prefix allow us to quickly know what is going on in this class
}

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

package project.domain;

interface UserRequestSource {
    public UserRequest getLastRequest();
}

class UserBehaviorAnalyser {
    private UserRequestSource requestSource;
}

package project.ui;

class OrderCompleteDialog extends SomeUIClass implements project.domain.UserRequestSource {
    // UI concern, no need for my domain object to know about this method.
    public void displayLabelInErrorMode(); 

    // They most certainly need to know about *that* though
    public UserRequest getLastRequest();
}

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

interface Sender {
    void sendMessage(Message message)
}

class PacketSender implements Sender {
    void sendMessage(Message message);
    void setPacketSize(int sizeInByte);
}

class Throttler { // This class need to have full access to the object
    private PacketSender sender;

    public useLowNetworkUsageMode() {
        sender.setPacketSize(LOW_PACKET_SIZE);
        sender.sendMessage(new NotifyLowNetworkUsageMessage());

        ... // Other details
    }
}

class MailOrder { // Not this one though
    private Sender sender;
}

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


2

Інтерфейси дійсно важливі, але постарайтеся контролювати їх кількість.

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

http://ayende.com/blog/153889/limit-your-abstractions-analyzing-a-ddd-application

Це його перший пост у цілій серії, тому продовжуйте читати!


Код "подрібнених спагетті" також відомий як код равіолі c2.com/cgi/wiki?RavioliCode
CurtainDog

Мені звучить більше як лазань-код або баклава-код - код із занадто великою кількістю шарів. ;-)
dodgy_coder

2

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


2

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


1

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

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

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

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

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

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