Хороший чистый дизайн приложения требует дисциплины в сохранении СУХОГО :
Все должно быть сделано один раз.
Необходимость сделать это дважды — совпадение.
Необходимость сделать это три раза — это закономерность.— неизвестный мудрец
Теперь, если вы следуете правилам Xtreme Programming, вы знаете, что нужно делать, когда сталкиваетесь с шаблоном:
Потому что мы все знаем, что происходит, когда вы этого не делаете:
НЕ СУХОЙ: метод перегрузки
Одной из наименее сухих вещей, которые вы можете сделать, которая по-прежнему приемлема, является перегрузка методов — в тех языках, которые позволяют это (в отличие от Ceylon , JavaScript). Будучи внутренним языком, специфичным для предметной области, jOOQ API интенсивно использует перегрузки. Рассмотрим тип поля (моделирование столбца базы данных):
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. Тем не менее, главное правило заключается в том, чтобы всегда иметь как можно меньше реализаций для перегруженных методов. Попробуйте вызвать один метод из другого. Например, эти два метода очень похожи:
Condition eq(T value); Condition eq(Field<T> field);
Первый метод является частным случаем второго, когда пользователи jOOQ не хотят явно объявлять переменную связывания. Это буквально реализовано так:
@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 из итератора , вы должны пройти через все эти хлопоты, например:
// 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? ------------------------+
Когда, интуитивно, вы хотели бы иметь:
// Not Enterprise enough iterator.stream();
Другими словами, тонкие детали реализации Java 8 Streams скоро попадут в большой объем клиентского кода, и многие новые служебные функции будут повторять эти вещи снова и снова.
См . Объяснение Брайана Гетца о переполнении стека .
С другой стороны делегирования реализаций перегрузки, конечно, труднее (т.е. больше работы) реализовать такой API. Это особенно обременительно, если поставщик API также позволяет пользователям самостоятельно реализовывать API (например, JDBC). Другая проблема заключается в длине стековых трасс, генерируемых такими реализациями. Но мы уже показали в этом блоге, что глубокие следы стека могут быть признаком хорошего качества .
Теперь ты знаешь почему.
навынос
Еда на вынос проста. Всякий раз, когда вы сталкиваетесь с образцом, рефакторинг. Найдите наиболее распространенный знаменатель, выделите его в реализацию и убедитесь, что эта реализация практически никогда не используется путем делегирования отдельных этапов ответственности от метода к методу.
Следуя этим правилам, вы будете:
- Меньше ошибок
- Иметь более удобный API
Удачного рефакторинга!