По умолчанию против Impl при реализации интерфейсов в Java

34

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

Давайте предположим, что у вас есть интерфейс, Orderкоторый предназначен для реализации различными способами, но при первом создании проекта существует только начальная реализация. Вы идете DefaultOrderили OrderImplили какой-то другой вариант, чтобы избежать ложной дихотомии? И что вы делаете, когда приходит больше реализаций?

И самое главное ... почему?

Гэри Роу
источник

Ответы:

59

Имена имеют возможность передать смысл. Почему вы отказались от этой возможности с Impl?

Прежде всего, если у вас когда-либо будет только одна реализация, покончите с интерфейсом. Это создает проблему именования и ничего не добавляет. Хуже того, это может вызвать проблемы с непоследовательными сигнатурами методов в API, если вы и все другие разработчики не будете осторожны, чтобы всегда использовать только интерфейс.

Учитывая это, мы можем предположить, что каждый интерфейс имеет или может иметь две или более реализации.

  • Если у вас есть только один в данный момент, и вы не знаете, чем другой может отличаться, по умолчанию хорошее начало.

  • Если у вас есть два прямо сейчас, назовите каждого в соответствии с его назначением.

    Пример: недавно у нас был конкретный класс Context (применительно к базе данных). Было понято, что нам нужно иметь возможность представлять контекст, находящийся в автономном режиме, поэтому имя «Контекст» использовалось для нового интерфейса (для обеспечения совместимости для старых API), и была создана новая реализация, OfflineContext . Но угадайте, во что переименовали оригинал? Это верно, ContextImpl (yikes).

    В этом случае DefaultContext , вероятно, будет в порядке, и люди получат его, но это не так наглядно, как могло бы быть. В конце концов, если это не в автономном режиме , что это? Итак, мы пошли с: OnlineContext .


Особый случай: использование префикса «I» на интерфейсах

Один из других ответов предложил использовать префикс 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
«если у вас когда-нибудь будет только одна реализация», как вы заранее знаете, что у вас будет только одна реализация? «каждый интерфейс имеет или может иметь две или более реализаций» ... прежде чем иметь две реализации, у вас сначала нет ни одной, у вас есть одна, затем у вас есть две, я имею в виду, что может быть момент, когда у вас есть только одна реализация, просто до реализации второго.
Тулаинс Кордова
2
@NickC Я не занимаюсь педантизмом по поводу семантики (я поэт и даже не знал об этом). Английский не мой родной язык, поэтому я не могу быть педантичным по этому поводу. Я говорил о некорректной логике. Вы используете интерфейсы для развязки. Это не требует определенного количества реализаций.
Тулаинс Кордова
4
if you will only ever have one implementation, do away with the interface- если вы не хотите протестировать компонент, в этом случае вы можете сохранить этот интерфейс для создания MockOrder, OrderStub или аналогичного.
JBRWilkinson
15

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

Если интерфейс используется для развязки , то я выбираю Implреализации.

Если целью интерфейса является поведенческая абстракция , то реализации именуются в соответствии с тем, что они конкретно делают. Я часто добавляю имя интерфейса к этому. Так что если интерфейс называется Validator, я использую FooValidator.

Я считаю, что Defaultэто очень плохой выбор. Во-первых, это загрязняет возможности завершения кода, потому что имена всегда начинаются с него. Другое дело, что значение по умолчанию может меняться со временем. Так что то, что сначала может быть значением по умолчанию, может через некоторое время быть устаревшей функцией. Так что либо вы всегда начинаете переименовывать свои классы, как только изменяются значения по умолчанию, либо вы живете с вводящими в заблуждение именами.

SpaceTrucker
источник
8

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

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

При таком подходе реализация может быть анонимным внутренним классом или частным классом с DefaultImpl именем или в имени, или он может быть полностью назван как-то иначе. Какой бы выбор не был сделан, вызывающему абоненту не нужно заботиться, поэтому вы получаете больше гибкости сейчас и позже, когда / если вы решите изменить его.

Некоторые большие примеры этой модели на практике являются java.util.Collectionsи java.util.concurrent.Executorsклассами полезности, чьи методы возвращают скрытые реализации. Как упоминает Effective Java (в пункте 1), этот шаблон может помочь уменьшить «концептуальный вес» API.

Эндрю Макнами
источник
+1 за интересный дополнительный случай: анонимные реализации.
Гари Роу
1
Также широко используется в Гуаве .
Николь
3

Я всегда говорю OrderImplпросто потому, что он отображается в алфавитном порядке сразу после Orderинтерфейса.

Бен Хоффштейн
источник
2
И как вы справляетесь с текущими дальнейшими реализациями, для специальной обработки и так далее?
Гэри Роу
1
Вы спрашивали о первоначальной реализации. Как только вы начнете реализовывать DomesticOrder, ForeignOrder или что-то еще, они будут названы более продуманно и без учета их положения в алфавите.
Бен Хоффштейн
Совершенно верно - я отредактировал исходный вопрос, чтобы отразить это новое требование.
Гэри Роу
Не очень хорошая идея, если будет более одной реализации.
Садег
0

Вы можете назвать интерфейс с префиксом I (IWhзна), а затем сделать реализацию как угодно.

Официальные соглашения по 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или что - то подобное.

Jiggy
источник
0

если возможно, назовите его после того, что / как он делает свое дело.

обычно худший подход - называть его после того, как он должен использоваться.

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

Если он используется, если не предоставлена ​​другая реализация, назовите его DefaultX

user470365
источник
-2

Большинство из этих ответов описывают, что делается, но не почему.

Что случилось с принципами ОО? Что Impl говорит мне о классе и как его использовать? Вы должны быть обеспокоены пониманием использования, а не того, как оно построено.

Если я создаю интерфейс Person, что такое PersonImpl? Бессмысленно. По крайней мере, DefaultPerson говорит мне, кто бы ни закодировал его, он не должен был создавать интерфейс.

Интерфейсы набирают полиморфизм и должны использоваться как таковые.

user108620
источник
1
Принятый ответ от @NickC подробно описывает, что делается и почему.
Гари Роу