За замовчуванням vs Impl при реалізації інтерфейсів на Java


34

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

Припустимо, що у вас є інтерфейс, Orderякий повинен бути реалізований різними способами, але існує лише початкова реалізація, коли проект вперше створений. Ви йдете за DefaultOrderчи OrderImplіншим варіантом, щоб уникнути помилкової дихотомії? А що ви робите, коли з'являється більше реалізацій?

І найголовніше ... чому?

Відповіді:


59

Імена мають можливість передати значення. Чому б ви відкинули цю можливість разом із Impl?

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

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

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

  • Якщо у вас зараз два, назвіть кожного відповідно до його призначення.

    Приклад: Нещодавно у нас був конкретний клас Context (стосовно посилання на базу даних). Було зрозуміло, що нам потрібно вміти представляти контекст, який був офлайн, тому ім'я Context було використано для нового інтерфейсу (для підтримки сумісності для старих API), і була створена нова реалізація, OfflineContext . Але вгадайте, на що було перейменовано оригінал? Правильно, ContextImpl ( поступається ).

    У цьому випадку DefaultContext , ймовірно, буде нормальним, і люди отримають його, але це не так описово, як це могло б бути. Зрештою, якщо це не офлайн , що це? Тож ми поїхали з: OnlineContext .


Особливий випадок: використання префікса "Я" на інтерфейсах

Один з інших відповідей пропонував використовувати префікс I на інтерфейсах. Переважно, не потрібно цього робити.

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

Приклад: Багато об'єктів можуть бути "EventDispatcher". Для API це повинно відповідати інтерфейсу. Але ви також хочете надати базовий диспетчер подій для делегації. DefaultEventDispatcher було б добре, але це трохи довго, і якщо ви збираєтеся бачити ім'я його часто, ви могли б віддати перевагу використовувати базове ім'я EventDispatcher для конкретного класу, а також здійснювати IEventDispatcher для призначених для користувача реалізацій:

/* Option 1, traditional verbose naming: */
interface EventDispatcher { /* interface for all event dispatchers */ }
class DefaultEventDispatcher implements EventDispatcher {
  /* default event dispatcher */
}

/* Option 2, "I" abbreviation because "EventDispatcher" will be a common default: */
interface IEventDispatcher { /* interface for all event dispatchers */ }
class EventDispatcher implements IEventDispatcher {
  /* default event dispatcher. */
}

3
+1 за тверду відповідь. Як і міркування про використання Impl.
Гері Роу

2
+1 абсолютно згоден. Іменування інтерфейсу від концепції домену, яку він представляє, повністю не вистачає сенсу.
rupjones

9
"якщо у вас буде колись одна реалізація", як ви заздалегідь знаєте, що у вас буде лише одна реалізація? "кожен інтерфейс має або може мати дві або більше реалізації" ... перед тим, як мати дві реалізації, спочатку у вас немає жодної, у вас є одна, потім у вас є дві, я маю на увазі момент, коли у вас є лише одна реалізація, просто до впровадження другого.
Tulains Córdova

2
@ NickC Я не буду педантин з приводу семантики (я поет, і я цього навіть не знав). Англійська мова не є моєю рідною мовою, тому я не можу з цим бути педантичним. Я говорив про хибну логіку. Ви використовуєте інтерфейси для роз'єднання. Для цього не потрібно певної кількості реалізацій.
Тулен Кордова

4
if you will only ever have one implementation, do away with the interface- якщо ви не хочете перевірити компонент, у такому випадку ви можете зберегти цей інтерфейс для створення MockOrder, OrderStub або подібного.
JBRWilkinson

15

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

Якщо інтерфейс використовується для роз'єднання , то я вибираю Implдля реалізації.

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

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


8

Я погоджуюся з відповіддю Ніколь (особливо, що інтерфейс, мабуть, не потрібен у більшості випадків), але заради обговорення я викину додаткову альтернативу, окрім OrderImplта DefaultOrder: приховую реалізацію за статичним заводським методом, як Orders.create(). Наприклад:

public final class Orders {
  public static Order create() {
    return new Order() {
      // Implementation goes here.
    };
  }
}

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

Деякі чудові приклади цієї схеми на практиці - класи java.util.Collectionsта java.util.concurrent.Executorsутиліти, методи яких повертають приховані реалізації. Як згадує Ефективна Java (у пункті 1), ця модель може допомогти зменшити "Концептуальну вагу" API.


+1 для цікавого додаткового випадку: анонімні реалізації.
Гері Роу

1
Також широко використовується в Гуаві .
Ніколь

3

Я завжди працюю OrderImplпросто тому, що він з'являється в алфавітному порядку після Orderінтерфейсу.


2
І як вам впоратися з подальшими подальшою реалізацією, для спеціального керування тощо?
Гері Роу

1
Ви запитали про початкову реалізацію. Після того, як ви почнете впроваджувати DomesticOrder, ForeignOrder або будь-що інше, вони називаються більш продуманими та незалежно від їх положення в алфавіті.
Бен Гофштейн

Цілком правильно - я відредагував оригінальне запитання, щоб відобразити цю нову вимогу.
Гері Роу

Непогана ідея Якщо буде більше, ніж одна реалізація.
Садег

0

Ви можете назвати інтерфейс з префіксом I (IWever), а потім зробити імплементацію Що завгодно.

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


Навіщо ви префіксувати інтерфейс з I? Чи це сприяє розпізнаванню в навігації?
Гері Роу

@Gary: префіксація типів інтерфейсу за допомогою I - це добре встановлена ​​конвенція на мові Delphi. У класах Delphi типи з префіксом T. Отже, у нас буде інтерфейс IOrder та реалізація за замовчуванням TOrder з TSomethingOrder і TBullMarketOrder як конкретні реалізації.
Мар'ян Венема

3
Я бачив, що ця конвенція також широко використовується в розробці .NET. Можливо, тому, що документація та приклади Microsoft використовують це.
Бен Гофштейн

5
Здається, що @Renesis придумав корисний випадок для використання його в Java. Особисто я б покладався на IDE, щоб він сказав мені різницю, інакше ми мали б E для Enums, C для класів і все в основному зайве.
Гері Роу

0

Я думаю, що, хоча Default може мати сенс у деяких випадках, було б корисніше описати реалізацію. Отже , якщо ваш інтерфейс , UserProfileDAOто ваші реалізації можуть бути UserProfileSQLDAOабо UserProfileLDAPDAOабо що - щось подібне.


0

по можливості, назвіть його після того, що / як він робить свою справу.

Зазвичай найгірший підхід - це його називання після того, як його слід використовувати.

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

Якщо він використовується, якщо не передбачена інша реалізація, назвіть його DefaultX


-2

Більшість цих відповідей описують, що робиться, а не чому.

Що сталося з принципами ОО? Що Impl розповідає мені про клас та як ним користуватися? Ви повинні бути стурбовані розумінням звичаїв, а не способів їх побудови.

Якщо я створюю інтерфейс Person, що таке PersonImpl? Безглуздо. Принаймні DefaultPerson каже мені, хто не кодував це, не повинен був створювати інтерфейс.

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


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