Подтвердить что ты не робот

Зачем настаивать на том, что все реализации интерфейса расширяют базовый класс?

Я просто смотрел код Java Hamcrest на GitHub и заметил, что они использовали стратегию, которая казалась неинтуитивной и неудобной, но мне стало интересно, не хватает ли я чего-то.

Я заметил в API HamCrest, что есть интерфейс Matcher и абстрактный класс BaseMatcher. Интерфейс Matcher объявляет этот метод с помощью этого javadoc:

    /**
     * This method simply acts a friendly reminder not to implement Matcher directly and
     * instead extend BaseMatcher. It easy to ignore JavaDoc, but a bit harder to ignore
     * compile errors .
     *
     * @see Matcher for reasons why.
     * @see BaseMatcher
     * @deprecated to make
     */
    @Deprecated
    void _dont_implement_Matcher___instead_extend_BaseMatcher_();

Затем в BaseMatcher этот метод реализуется следующим образом:

    /**
     * @see Matcher#_dont_implement_Matcher___instead_extend_BaseMatcher_()
     */
    @Override
    @Deprecated
    public final void _dont_implement_Matcher___instead_extend_BaseMatcher_() {
        // See Matcher interface for an explanation of this method.
    }

По общему признанию, это и эффективно и мило (и невероятно неудобно). Но если намерение для каждого класса, которое реализует Matcher, также расширить BaseMatcher, зачем вообще использовать интерфейс? Почему бы просто не сделать Matcher абстрактным классом в первую очередь и у него есть все другие матчи? Есть ли какое-то преимущество для этого, как это сделал Hamcrest? Или это отличный пример плохой практики?

ИЗМЕНИТЬ

Некоторые хорошие ответы, но в поисках более подробной информации я предлагаю щедрость. Я думаю, что проблема обратной/двоичной совместимости - лучший ответ. Тем не менее, я хотел бы, чтобы проблема совместимости была разработана более, в идеале, с некоторыми примерами кода (желательно на Java). Кроме того, существует ли нюанс между "обратной" совместимостью и "двоичной" совместимостью?

ДАЛЬНЕЙШЕЕ ИЗОБРАЖЕНИЕ

7 января 2014 года - pigroxalot предоставил ответ ниже, ссылаясь на этот комментарий к Reddit авторов HamCrest. Я призываю всех прочитать его, и если вы найдете его информативным, ответьте на вопрос о свиносалоте.

ДАЖЕ ДАЛЬНЕЙШЕЕ ИЗОБРАЖЕНИЕ

12 декабря 2017 года - ответ на pigroxalot был как-то удален, не уверен, как это произошло. Это слишком плохо... эта простая ссылка была очень информативной.

4b9b3361

Ответ 1

git log имеет эту запись с декабря 2006 года (примерно через 9 месяцев после первоначальной проверки):

Добавлен абстрактный класс BaseMatcher, который должен расширять все Матчи. Это позволяет использовать будущую совместимость API [sic] по мере развития интерфейса Matcher.

Я не пытался разобраться в деталях. Но сохранение совместимости и непрерывности по мере развития системы - сложная проблема. Это означает, что иногда вы оказываетесь в дизайне, который никогда не создавали бы, если бы вы все это разработали с нуля.

Ответ 2

Но если намерение для каждого класса, реализующего Matcher, также расширить BaseMatcher, зачем вообще использовать интерфейс?

Это не совсем намерение. Абстрактные базовые классы и интерфейсы предоставляют совершенно разные "контракты" с точки зрения ООП.

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

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

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

Данная практика на самом деле довольно распространена в коде COM/OLE старой школы и в других средах, облегчающих межпроцессные коммуникации (IPC), где становится основополагающим для разделения реализации от интерфейса - это именно то, что делается здесь.

Ответ 3

Я думаю, что произошло то, что изначально API-интерфейс Matcher был создан в виде интерфейса.
Затем при реализации интерфейса различными способами была обнаружена общая база кода, которая затем была реорганизована в класс BaseMatcher.

Итак, я предполагаю, что интерфейс Matcher был сохранен, поскольку он является частью исходного API, и затем описательный метод был добавлен в качестве напоминания.

Просматривая код, я обнаружил, что интерфейс может быть легко устранен, поскольку он ТОЛЬКО реализован BaseMatcher и в двух тестовых единицах, которые можно легко изменить, чтобы использовать BaseMatcher.

Итак, чтобы ответить на ваш вопрос - в этом конкретном случае нет никакого преимущества, чтобы делать это таким образом, кроме того, чтобы не нарушать реализации другими людьми Матчи.

Что касается плохой практики? По-моему, это понятно и эффективно - так нет, я так не думаю, просто немного странно: -)

Ответ 4

Hamcrest обеспечивает сопоставление и сопоставление. Это крошечный нишевый рынок, но они, похоже, делают это хорошо. Реализации этого интерфейса Matcher засоряются в нескольких модульных тестовых библиотеках, например, Mockito ArgumentMatcher и через глупое большое количество крошечной анонимной копии -пастевые реализации в модульных тестах.

Они хотят иметь возможность расширять Matcher новым методом, не нарушая всех существующих классов реализации. Им будет чертовски обновить. Представьте себе, что внезапно все ваши классы unittest показывают сердитые красные ошибки компиляции. Гнев и раздражение убьют нишу на рынке за один быстрый ход. См. http://code.google.com/p/hamcrest/issues/detail?id=83 для небольшого вкуса. Кроме того, нарушение изменений в hamcrest разделило бы все версии библиотек, которые используют Hamcrest, до и после изменения и делают их взаимоисключающими. Опять же, адский сценарий. Таким образом, чтобы сохранить свободу, Matcher должен быть абстрактным базовым классом.

Но они также в насмешливом бизнесе, и интерфейсы легче издеваться над базовыми классами. Когда люди Mockito unit test Mockito, они должны быть в состоянии издеваться над матчи. Таким образом, они также нуждаются в том, чтобы абстрактный базовый класс имел интерфейс Matcher.

Я думаю, что они серьезно рассмотрели варианты и обнаружили, что это наименее плохая альтернатива.

Ответ 5

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

interface Match<T>

    default void newMethod(){ impl... }

Это отличный инструмент, который дает нам большую свободу в дизайне и эволюции интерфейса.

Однако, что, если вы действительно хотите добавить абстрактный метод, который не имеет реализации по умолчанию?

Я думаю, вам нужно просто пойти дальше и добавить метод. Это сломает некоторые существующие коды; и они должны быть исправлены. Не очень большое дело. Это, вероятно, превосходит другие способы обхода, которые сохраняют двоичную совместимость за счет закручивания всей конструкции.

Ответ 6

Но если это намерение для каждого класса, который реализует Matcher для также расширить BaseMatcher, зачем вообще использовать интерфейс? Почему не просто сделать Matcher абстрактным классом в первую очередь и иметь все остальные его продлевают?

Разделяя интерфейс и реализацию (абстрактный класс по-прежнему является реализацией), вы выполняете Принцип инверсии зависимостей. Не путайте с инъекцией зависимости, ничего общего. Вы можете заметить, что в интерфейсе Hamcrest хранится пакет hamcrest-api, а абстрактный класс - в hamcrest-core. Это обеспечивает низкую связь, поскольку реализация зависит только от интерфейсов, но не от другой реализации. Хорошая книга по этой теме: Интерфейсно ориентированный дизайн: с шаблонами.

Есть ли какое-то преимущество для этого, как это сделал Hamcrest? Или это отличный пример плохой практики?

Решение в этом примере выглядит уродливым. Я думаю, что комментариев достаточно. Создание таких методов заглушки является избыточным. Я бы не стал следовать этому подходу.