Некоторое время назад мы опубликовали эту забавную игру, которую мы называем Spring API Bingo . Это дань и лесть огромному творчеству Spring при формировании значимых имен классов, таких как
- FactoryAdvisorAdapterHandlerLoader
- ContainerPreTranslatorInfoDisposable
- BeanFactoryDestinationResolver
- LocalPersistenceManagerFactoryBean
Два из вышеперечисленных классов действительно существуют. Вы можете их заметить? Если нет, играйте в Spring API Bingo !
Очевидно, что Spring API страдает от…
Называть вещи
Есть только две трудные проблемы в информатике. Аннулирование кэша, присвоение имен вещам и отдельные ошибки
— Тим Брей цитирует Фила Карлтона
Есть пара таких префиксов или суффиксов, от которых просто трудно избавиться в программном обеспечении Java. Рассмотрим недавнюю дискуссию в Твиттере, которая неизбежно приведет к (очень) интересной дискуссии :
Да, суффикс Impl
— интересная тема. Почему у нас это есть, и почему мы продолжаем называть вещи таким образом?
Спецификация против тела
Ява это причудливый язык. В то время, когда это было изобретено, предметная ориентация была горячей темой. Но у процедурных языков были и интересные особенности. Одним из очень интересных языков в то время была Ада (а также PL / SQL, который был в основном получен из Ады). Ада (как PL / SQL) разумно организует процедуры и функции в пакеты, которые бывают двух видов: спецификация и тело. Из примера википедии :
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
-- Specification package Example is procedure Print_and_Increment (j: in out Number); end Example; -- Body package body Example is procedure Print_and_Increment (j: in out Number) is begin -- [...] end Print_and_Increment; begin -- [...] end Example; |
Вы всегда должны делать это, и две вещи названы одинаково: Example
. И они хранятся в двух разных файлах: Example.ads
(реклама для Ada и s для спецификации) и Example.adb
(b для тела). PL / SQL следовал примеру и называет файлы пакетов Example.pks
и Example.pkb
с pk для Package.
Java пошла другим путем, в основном из-за полиморфизма и из-за того, как работают классы:
- Классы — это и спецификация, и тело в одном
- Интерфейсы не могут называться так же, как их реализующие классы (в основном, потому что, конечно, существует много реализаций)
В частности, классы могут быть гибридом только для спецификации, с частичным телом (когда они абстрактны) и с полной спецификацией и телом (когда они конкретны).
Как это приводит к именованию в Java
Не все ценят чистое разделение характеристик и кузова, и это, безусловно, можно обсудить. Но когда вы находитесь в этом духе Ada-esque, то, вероятно, вам нужен один интерфейс для каждого класса, по крайней мере, там, где выставлен API. Мы делаем то же самое для jOOQ , где мы установили следующую политику для именования вещей:
* Impl
Все реализации (тела), которые находятся в отношении 1: 1 с соответствующим интерфейсом, имеют суффикс Impl
. Если возможно, мы стараемся сохранять эти реализации закрытыми и таким образом запечатанными в пакете org.jooq.impl
. Примеры:
-
Cursor
и соответствующий емуCursorImpl
-
DAO
и его соответствующиеDAOImpl
-
Record
и соответствующая ейRecordImpl
Эта строгая схема именования сразу дает понять, какой интерфейс (и, следовательно, публичный API), а какой — реализация. Хотелось бы, чтобы Java была больше похожа на Аду в этом отношении, но у нас есть полиморфизм, и это здорово, и…
Аннотация*
… и это приводит к повторному использованию кода в базовых классах. Как мы все знаем, общие базовые классы должны (почти) всегда быть абстрактными. Просто потому, что они чаще всего являются неполными реализациями (телами) их соответствующей спецификации. Таким образом, у нас есть много частичных реализаций, которые также находятся в соотношении 1: 1 с соответствующим интерфейсом, и мы ставим их префиксом Abstract
. Чаще всего эти частичные реализации также являются частными для пакета и запечатаны в пакете org.jooq.impl
. Примеры:
-
Field
и соответствующее емуAbstractField
-
Query
и соответствующий емуAbstractQuery
-
ResultQuery
и соответствующий емуAbstractResultQuery
В частности, ResultQuery
— это интерфейс, расширяющий Query
, и, следовательно, AbstractResultQuery
— это частичная реализация, которая расширяет AbstractQuery
, который также является частичной реализацией.
Частичная реализация имеет смысл в нашем API, потому что наш API является внутренним DSL (предметно-ориентированным языком) и, следовательно, имеет тысячи методов, которые всегда одинаковы, независимо от того, что на самом деле делает конкретное Field
, например, Substring
По умолчанию*
Мы делаем все API, связанные с интерфейсами. Это оказалось очень эффективным уже в популярных API Java SE, таких как:
- Коллекции
- Streams
- JDBC
- DOM
Мы также делаем все SPI (Service Provider Interface), связанный с интерфейсами. Существует одно существенное различие между API и SPI с точки зрения развития API:
- API потребляются пользователями, вряд ли реализованы
- SPI реализуются пользователями, практически не потребляются
Если вы не разрабатываете JDK (и, следовательно, не имеете совершенно безумных правил обратной совместимости ), вы, вероятно, в основном безопасно добавляете новые методы в интерфейсы API . Фактически, мы делаем это в каждом дополнительном выпуске, так как мы не ожидаем, что кто-то реализует наш DSL (кто захочет реализовать методы Field
— 286 или DSL
— 677. Это безумие!)
Но SPI разные. Всякий раз, когда вы предоставляете своему пользователю SPI, такие как что-либо с суффиксом *Listener
или *Provider
, вы не можете просто добавить к ним новые методы — по крайней мере, до Java 8, так как это может нарушить реализации, и их много. ,
Что ж. Мы все еще делаем это, потому что у нас нет тех правил обратной совместимости JDK. У нас есть более расслабленные . Но мы предлагаем нашим пользователям самим не реализовывать интерфейсы, а расширять реализацию по Default
, которая пуста. Например, ExecuteListener
и соответствующий DefaultExecuteListener
:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
public interface ExecuteListener { void start(ExecuteContext ctx); void renderStart(ExecuteContext ctx); // [...] } public class DefaultExecuteListener implements ExecuteListener { @Override public void start(ExecuteContext ctx) {} @Override public void renderStart(ExecuteContext ctx) {} // [...] } |
Таким образом, Default*
— это префикс, который обычно используется для предоставления единой общедоступной реализации, которую пользователи API могут использовать и создавать, либо разработчики SPI могут расширять без риска обратной совместимости. Это в значительной степени обходной путь для отсутствия методов интерфейса по умолчанию в Java 6/7, поэтому наименование префиксов еще более уместно.
Java 8 версия этого правила
Фактически, из этой практики становится очевидным, что «хорошим» правилом для определения совместимых с Java-8 SPI является использование интерфейсов и установка всех методов по умолчанию с пустым телом. Если бы jOOQ не поддерживал Java 6, мы, вероятно, указали бы наш ExecuteListener
следующим образом:
1
2
3
4
5
|
public interface ExecuteListener { default void start(ExecuteContext ctx) {} default void renderStart(ExecuteContext ctx) {} // [...] } |
* Утилиты или * Помощник
Хорошо, вот один из них для экспертов по тестированию / тестированию / освещению и поклонникам.
НУЖНО иметь «дамп» для всех видов статических служебных методов. Я имею в виду, конечно, вы могли бы быть сотрудником объектно-ориентированной полиции . Но…
Пожалуйста. Не будь «тем парнем»!
Итак, существуют различные методы идентификации служебных классов. В идеале вы берете соглашение об именовании и затем придерживаетесь его. Например, * использует .
С нашей точки зрения, в идеале вы бы просто сбросили все служебные методы, которые строго не связаны с очень специфической областью в одном классе, потому что, честно говоря, когда вы в последний раз оценивали необходимость проходить миллионы классов, чтобы найти этот служебный метод? Никогда. У нас есть org.jooq.impl.Utils
. Почему? Потому что это позволит вам сделать:
1
|
import static org.jooq.impl.Utils.*; |
Тогда это выглядит почти так, как будто в вашем приложении есть что-то вроде «функций верхнего уровня» «Глобальные» функции. Который мы считаем хорошей вещью. И мы совершенно не покупаем аргумент «мы не можем издеваться над этим», поэтому даже не пытайтесь начать обсуждение
обсуждение
… или, собственно, давайте начнем обсуждение. Какие у вас приемы и почему? Вот пара реакций на оригинальный твит Тома Бужока, чтобы помочь вам начать:
Поехали !
Ссылка: | Страшный DefaultAbstractHelperImpl от нашего партнера по JCG Лукаса Эдера из блога JAVA, SQL и JOOQ . |