Неправильно ли каждый конкретный класс наследовать интерфейс?

Это в ответ на некоторые комментарии, сделанные Zed Shaw в его блоге давным-давно.

Затем эксперты отправятся в реализовать свою Пылающую Башню Вавилона без комментариев, ужасно сложный макет включал тесты, убедившись, что КАЖДЫЙ У ОДНОГО КЛАССА ИНТЕРФЕЙС, и заканчивая каждый класс с помощью "Impl" потому что, ну, вот что самое лучшее практика.

Я использую Spring и Google Guice в равных измерениях, и я заметил, что эти фреймворки используют постфикс Impl, но экономно. В моем коде я использую интерфейсы повсюду, потому что мне сказали, что это облегчает насмешку и т.д. У меня есть наивное понимание проблемы? (Возможно, макетные фреймворки могут работать с абстрактными классами или классами, я не знаю. Я никогда не пробовал). Для моих конкретных реализаций я выбрал соглашение Spring для префикса имени реализации со словом "По умолчанию".

e.g. BottleOpener (interface) is implemented by DefaultBottleOpener (class)

Какова наилучшая практика по этой проблеме?

UPDATE1 Я считаю полезным возвращать интерфейс из метода, потому что я всегда могу вернуть анонимный класс.

Ответ 1

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

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

Интерфейсы отделяют API от того, как они реализованы. Если ваша реализация не меняется, интерфейс не имеет большого смысла.

Зед Шоу - потрясающий писатель и разработчик, но возьмите то, что он говорит, с солью. Я думаю, что некоторые из лучших гиперболы, которыми он занимается, - это развлекательная ценность. У него есть точка ( "Не делай ничего только потому, что кто-то, кто утверждает, что является авторитетом, говорит тебе об этом" лучшей практике "), но так, как он говорит, это театральный театр.

Ответ 2

Лучшая практика заключается в следующем:

  • Выберите соглашение и будьте последовательны в этом; и
  • Не переходите за борт, чтобы все реализовало интерфейс.

Ответ 3

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

Ответ 4

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

Тем не менее, я видел код, где есть 400 классов и 400+ интерфейсов для этих классов. Просто выкапывать кучу кода с его плохими соглашениями об именах было достаточно, чтобы дать мне завещания. С другой стороны, я имею дело с сторонней фирменной библиотекой без интерфейсов. Издеваться над этими объектами становится боль.

Ответ 5

Я не вижу никакого преимущества в создании интерфейса для каждого класса. Это просто заставляет вас писать кучу кода дважды.

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

Я создаю интерфейс, когда в данном контексте он служит полезной цели. В основном это означает:

(a) На практике моя самая распространенная причина заключается в реализации/моделировании множественного наследования. Если мне нужно A extends B, но мне также нужно иметь функцию, которая принимает C, и я хочу передать A этой функции, тогда я должен сделать либо B, либо C интерфейсом, а не родительским классом, и сделать его A extends B C или наоборот.

(b) Когда я создаю API для библиотеки или функции утилиты, которая будет реализована чем-то вне этой библиотеки или утилиты, обычно несколькими причинами. Примером этого может служить интерфейс "Comparable" в библиотеке Java. Утилита sort нужна функция для сравнения пары объектов. Когда программа вызывает утилиту сортировки с объектами, определенными в вызывающей программе, нельзя ожидать, что утилита сортировки знает, как их сравнивать, поэтому вызывающая программа должна предоставить функцию сравнения. Сигнатура для функции сравнения логически идет в интерфейсе, который вызывающий может реализовать для своих объектов.

(c) Если я хочу опубликовать интерфейс, не раскрывая реализацию. Это может быть по частным причинам, но на практике это обычно потому, что другой человек или команда реализует интерфейс.

Ответ 6

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

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

Ответ 7

Ну, я "слышал", что в SmallTalk вы всегда начинаете определять интерфейсы до фактической реализации... Поэтому, я думаю, это действительно зависит от цели и цели проекта, которую вы хотите достичь...

Ответ 8

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

Обратите внимание, что я имею в виду объекты, а не коллекции или другие вещи, которые по существу являются "данными".

Однако наличие класса "IClassName" или аналогичное, и наличие этого шаблона во всей кодовой базе отражает плохое понимание понятий интерфейса. Интерфейсы обычно должны быть объявлены классом-потребителем, как объявление "эй, вот услуги, которые мне нужны". Таким образом, интерфейс представляет взаимодействия между двумя объектами, а не один вид объекта в мире. Это также помогает сохранять обязанности отдельно, так как каждый класс имеет дело с собственными идеальными интерфейсами, и поэтому может помочь сохранить логику домена отдельно от логики реализации.

(редактировать)

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

У Зед Шоу есть хорошая статья.

http://www.zedshaw.com/essays/indirection_is_not_abstraction.html

Ответ 9

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

Есть случаи, когда это лишнее, но это не очень дорого.

Можно даже сделать случай, когда объекты Domain Objects реализуют интерфейсы. Преимущества:

  • Вы можете скрыть уродливые детали от код клиента (например, JPA аннотации, сеттеры, которые будут использоваться только рамки ORM)
  • Как и в DAO или Services, вы можете запрограммировать абстракции. Подумайте о пользователе абстракция - ваша бизнес-логика не зависит от того, как и где они хранятся (например, БД или ОС), и вы можете легко использовать адаптер если вам нужно работать с сторонний код.

Ответ 10

Я думаю, это зависит от вашего дизайна. Если вы хотите использовать инъекцию зависимостей или тестирование имитирующего объекта, то интерфейсы имеют смысл. Кроме того, если вы планируете иметь несколько реализаций, то интерфейсам определенно требуется указать контракт для поведения.

Если вы сомневаетесь, возможно, возьмите гибкий подход?

Просто нужно открыть бутылку пива

class BeerBottleOpener // life good

Теперь нужно открыть бутылку для вина

class WineBottleOpener // life still good

Рефакторинг немного

interface BottleOpener;
class BeerBottleOpener implements BottleOpener;
class WineBottleOpener implements BottleOpener;
// now living the life!  ;-)

Btw, для макетирования я использовал EasyMock, который требует интерфейсов. Теперь переключился на Mockito, который может издеваться над классами и более ясен и немного проще.

Ответ 11

Переход на борт на интерфейсах - очень реальная возможность. Не обращая внимания на [пренебрежимое] ограничение производительности при каждом вызове метода с помощью виртуальной диспетчеризации, оно также вводит больше параметров в систему. Это может затруднить отладку и защиту программного обеспечения. Если ваш метод/функция принимает какой-либо старый интерфейс, вы приняли решение принять, что разработчик может реализовать интерфейс неправильно или, возможно, даже злонамеренно. Как ваша программа/library/структура позволяет варьировать важную роль в процессе проектирования.

Ответ 12

Предоставление всего интерфейса (или, в более общем плане, для гибкости, которая, вероятно, не используется) называется overengineering. Если вы, вероятно, никогда не будете иметь более чем одну конкретную реализацию чего-то, интерфейс просто раздувается. Код, который позволяет избежать создания неиспользуемой гибкости, легче понять, потому что без интерфейса, скрывающего тот факт, что у вас есть только одна реализация, вы знаете, какой конкретный тип вы имеете в виду, а код проще рассуждать.

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

Ответ 13

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