Оголошення інтерфейсу в тому ж файлі, що і базовий клас, це хороша практика?


29

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

public class FooService: IFooService 
{ ... }

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

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


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

11
@MadKeithV для нещільного зчеплення та перевірки?
Луї Ріс

10
@MadKeithV: ти маєш рацію. Але коли ви пишете одиничні тести для коду, який спирається на IFooService, ви зазвичай надаєте MockFooService, який є другою реалізацією цього інтерфейсу.
Док Браун

5
@DocBrown - якщо у вас є кілька реалізацій, то, очевидно, коментар відходить, але це також корисність того, щоб мати можливість перейти безпосередньо до "єдиної реалізації" (тому що їх принаймні дві). Тоді виникає питання: яка інформація є в конкретній реалізації, яка вже не повинна бути в описі / документації інтерфейсу в точці, де ви використовуєте інтерфейс?
Joris Timmermans

1
Відверта самореклама: stackoverflow.com/questions/5840219/…
Marjan Venema

Відповіді:


24

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

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

 


5
Я погоджуюся з цією відповіддю, адже IDE має підходити під наш дизайн, а не навпаки, правда?
marktani

1
Крім того, без Resharper, F12 і Shift + F12 майже все роблять те саме. Навіть не клацніть правою кнопкою миші :) (якщо чесно, це призведе лише до прямого використання інтерфейсу, не впевненого, що робить версія Resharper)
Daniel B

@DanielB: У Resharper Alt-End переходить до реалізації (або подає вам список можливих реалізацій, до яких потрібно перейти).
StriplingWarrior

@StriplingWarrior тоді, здається, це саме те, що робить і ванільна VS. F12 = переходимо до визначення, Shift + F12 = переходимо до реалізації (я впевнений, що це працює без Resharper, хоча у мене це встановлено, тому важко підтвердити). Це один із найкорисніших ярликів навігації, я просто поширюю слово.
Даніель Б

@DanielB: Типовим для Shift-F12 є "знайти звички". jetbrains.com/resharper/webhelp / ...
StriplingWarrior

15

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


10

За словами SOLID, ви не тільки повинні створювати інтерфейс, і не тільки він повинен бути в іншому файлі, він повинен бути в іншій збірці.

Чому? Оскільки будь-яка зміна вихідного файлу, який збирається в збірку, вимагає перекомпіляції збірки, а будь-яка зміна в зборі вимагає перекомпіляції будь-якої залежної збірки. Отже, якщо ваша мета, заснована на SOLID, полягає в тому, щоб мати можливість замінити реалізацію A на реалізацію B, тоді як клас C, залежний від інтерфейсу, я не повинен знати різниці, ви повинні переконатися, що збірка з I він не змінюється, захищаючи тим самим звичаї.

"Але це просто перекомпіляція" Я чую, як ви протестуєте. Добре, що може бути, але у вашому додатку для смартфонів, що простіше пропускання даних користувачів; завантаження одного бінарного файлу, що змінився, або завантаження цього бінарного файлу та п’ять інших з кодом, який від цього залежить? Не кожна програма написана для споживання настільними комп'ютерами в локальній мережі. Навіть у тому випадку, коли пропускна здатність та пам’ять дешеві, менші випуски патчів можуть мати значення, оскільки вони банальні для передачі на всю локальну мережу через Active Directory або подібні шари управління доменом; ваші користувачі чекатимуть лише декількох секунд, коли він буде застосований наступного разу, коли він увійде, а не кілька хвилин, щоб все це було перевстановлено. Не кажучи вже про те, що чим менше збірок, які потрібно перекомпілювати під час створення проекту, тим швидше він буде складений,

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


8

Чому виникає незручність мати окремі файли? Для мене це набагато акуратніше і чистіше. Зазвичай створюється підпапка з назвою "Інтерфейси" і вставляти туди файли IFooServer.cs, якщо ви хочете побачити менше файлів у вашому Провіднику рішень.

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


6

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

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

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


3
+1 за вказівку на те, що практики команди повинні диктувати структуру файлів і за те, що суперечить нормам підходу.

3

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

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


2

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

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

Однак, як правило, ви повинні дотримуватися стратегії "один публічний клас / інтерфейс відповідає одному файлу": це легко дотримуватися, і це полегшує ваш вихідний дерево легше.


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


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

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

2
@KeithS Клас без інтерфейсу повинен бути у вашому дизайні лише тоді, коли ви мертві, впевнені, що це не має сенсу підкласирувати, і ви робите цей клас sealed. Якщо ви підозрюєте, що можете через деякий час додати другий підклас, ви вкладаєте інтерфейс відразу. PS Достойний якісний помічник рефакторингу може бути вашим за скромною ціною однієї ліцензії ReSharper :)
dasblinkenlight

1
@KeithS І так, усі ваші класи, які не призначені спеціально для підкласифікації, повинні бути запечатані. Насправді класи побажань були запечатані за замовчуванням, змушуючи вас чітко призначити класи для успадкування так само, як мова, яка в даний час змушує вас призначити функції для переосмислення, позначаючи їх virtual.
dasblinkenlight

2
@KeithS: Що відбувається, коли ваша одна реалізація стає двома? Те саме, що коли змінюється ваша реалізація; ви змінюєте код, щоб відобразити цю зміну. Тут застосовується YAGNI. Ви ніколи не дізнаєтесь, що зміниться в майбутньому, тому зробіть найпростішу можливу річ. (На жаль, тестування місій з цією максимою)
Groky

2

Інтерфейси належать своїм клієнтам, а не їх реалізації, як визначено в Agile Principles, Practices and Patterns by Robert C. Martin. Таким чином, поєднання інтерфейсу та реалізації в одному місці протилежне його принципу.

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

ОНОВЛЕННЯ: Це не є гнучким принципом. "Банда з чотирьох" в моделях дизайну, ще в 94-му році, вже говорить про клієнтів, які дотримуються інтерфейсів, а програмування - на інтерфейси. Їх погляд схожий.


1
Використання інтерфейсів виходить далеко за межі домену, яким володіє Agile. Agile - це методологія розробки, яка особливим чином використовує мовні конструкції. Інтерфейс - це мовна конструкція.

1
Так, але інтерфейс все-таки належить клієнту, а не реалізації, незалежно від того, мова йде про спритний чи ні.
Паткос Чаба

Я з вами з цього приводу.
Мар'ян Венема

0

Мені особисто не подобається "я" перед інтерфейсом. Як користувач такого типу, я не хотів би бачити, що це інтерфейс чи ні. Тип - цікава річ. З точки зору залежностей, ваш FooService - це ОДНА можлива реалізація IFooService. Інтерфейс повинен бути поблизу місця, де він використовується. З іншого боку, реалізація повинна бути там, де її можна легко змінити, не впливаючи на клієнта. Отже, я вважаю за краще два файли - як правило.


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

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

4
Подобається це чи ні, використання "Я" перед іменем інтерфейсу є де-факто стандартом у .NET. Недотримання стандарту в проекті .NET, на мій погляд, було б порушенням «принципу найменшого здивування».
Піт

Головний мій пункт: розділити на два файли. Один для абстракції і один для реалізації. Якщо використання Iсередовища нормальне у вашому оточенні: Використовуйте його! (Але мені це не подобається :))
ollins

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

0

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


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

0

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

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

Особисто, якби я натрапив на кодову базу, яка використовувала одну конвенцію над іншою, я б не надто хвилювався.


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