Хороший чистый дизайн приложения требует дисциплины в сохранении СУХОГО :
Все должно быть сделано один раз.
Необходимость сделать это дважды — совпадение.
Необходимость сделать это три раза — это закономерность.— неизвестный мудрец
Теперь, если вы следуете правилам Xtreme Programming, вы знаете, что нужно делать, когда сталкиваетесь с шаблоном:
Потому что мы все знаем, что происходит, когда вы этого не делаете:
НЕ СУХОЙ: метод перегрузки
Одной из наименее сухих вещей, которые вы можете сделать, которая по-прежнему приемлема, является перегрузка методов — в тех языках, которые позволяют это (в отличие от Ceylon , JavaScript). Будучи внутренним языком, специфичным для предметной области, jOOQ API интенсивно использует перегрузки. Рассмотрим тип поля (моделирование столбца базы данных):
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
public interface Field<T> { // [...] Condition eq(T value); Condition eq(Field<T> field); Condition eq(Select<? extends Record1<T>> query); Condition eq(QuantifiedSelect<? extends Record1<T>> query); Condition in(Collection<?> values); Condition in(T... values); Condition in(Field<?>... values); Condition in(Select<? extends Record1<T>> query); // [...] } |
Таким образом, в некоторых случаях отсутствие СУХОСТИ неизбежно, также в определенной степени при реализации вышеуказанного API. Тем не менее, главное правило заключается в том, чтобы всегда иметь как можно меньше реализаций для перегруженных методов. Попробуйте вызвать один метод из другого. Например, эти два метода очень похожи:
1
2
|
Condition eq(T value); Condition eq(Field<T> field); |
Первый метод является частным случаем второго, когда пользователи jOOQ не хотят явно объявлять переменную связывания. Это буквально реализовано так:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
@Override public final Condition eq(T value) { return equal(value); } @Override public final Condition equal(T value) { return equal(Utils.field(value, this )); } @Override public final Condition equal(Field<T> field) { return compare(EQUALS, nullSafe(field)); } @Override public final Condition compare(Comparator comparator, Field<T> field) { switch (comparator) { case IS_DISTINCT_FROM: case IS_NOT_DISTINCT_FROM: return new IsDistinctFrom<T>( this , nullSafe(field), comparator); default : return new CompareCondition( this , nullSafe(field), comparator); } } |
Как вы видете:
-
eq()
— это просто синоним унаследованного методаequal()
-
equal(T)
является более специализированной, удобной формойequal(Field<T>)
-
equal(Field<T>)
— более специализированная, удобная формаcompare(Comparator, Field<T>)
-
compare()
наконец, предоставляет доступ к реализации этого API
Все эти методы также являются частью общедоступного API и могут напрямую вызываться потребителем API, поэтому nullSafe()
повторяется в каждом методе.
Почему все проблемы?
Ответ прост.
- Существует очень небольшая вероятность ошибки копирования-вставки во всем API.
- … Потому что тот же API должен быть предложен для
ne
,gt
,ge
,lt
,le
- Независимо от того, какая часть API проходит тестирование на интеграцию, сама реализация, безусловно, проходит определенный тест.
- Таким образом, чрезвычайно легко предоставить пользователям очень богатый API с множеством удобных методов, поскольку пользователи не хотят помнить, как эти методы более общего назначения (например,
compare()
) действительно работают.
Последний пункт особенно важен, и из-за рисков, связанных с обратной совместимостью, например, не всегда сопровождается JDK. Чтобы создать поток Java 8 из итератора , вы должны пройти через все эти хлопоты, например:
01
02
03
04
05
06
07
08
09
10
11
|
// Aagh, my fingers hurt... StreamSupport.stream(iterator.spliterator(), false ); // ^^^^^^^^^^^^^ ^^^^^^^^^^^ ^^^^^ // | | | // Not Stream! | | // | | // Hmm, Spliterator. Sounds like | | // Iterator. But what is it? ---------+ | // | // What's this true and false? | // And do I need to care? ------------------------+ |
Когда, интуитивно, вы хотели бы иметь:
1
2
|
// Not Enterprise enough iterator.stream(); |
Другими словами, тонкие детали реализации Java 8 Streams скоро попадут в большой объем клиентского кода, и многие новые служебные функции будут повторять эти вещи снова и снова.
См . Объяснение Брайана Гетца о переполнении стека .
С другой стороны делегирования реализаций перегрузки, конечно, труднее (т.е. больше работы) реализовать такой API. Это особенно обременительно, если поставщик API также позволяет пользователям самостоятельно реализовывать API (например, JDBC). Другая проблема заключается в длине стековых трасс, генерируемых такими реализациями. Но мы уже показали в этом блоге, что глубокие следы стека могут быть признаком хорошего качества .
Теперь ты знаешь почему.
навынос
Еда на вынос проста. Всякий раз, когда вы сталкиваетесь с шаблоном, рефакторинг. Найдите наиболее распространенный знаменатель, выделите его в реализацию и убедитесь, что эта реализация практически никогда не используется путем делегирования отдельных этапов ответственности от метода к методу.
Следуя этим правилам, вы будете:
- Меньше ошибок
- Иметь более удобный API
Удачного рефакторинга!
Ссылка: | Хранение вещей СУХОЙ: перегрузка методов от нашего партнера JCG Лукаса Эдера в блоге JAVA, SQL и AND JOOQ . |