Интерфейсы (интерфейс / абстрактный класс) не абстракции?

В последнее время я читал сообщения, в которых говорится о предполагаемом неправильном представлении о том, что интерфейсы являются абстракциями. Одно из таких сообщений - http://blog.ploeh.dk/2010/12/02/InterfacesAreNotAbstractions.aspx

Я немного смущен. Если у меня нет интерфейсов (интерфейс / абстрактный класс), как я могу внедрить свои зависимости и издеваться над ними?

Кроме того, я слышал, как люди говорят о том, что не используют интерфейсы, у которых есть только один разработчик. Поставьте лайк этому блогу здесь - http://simpleprogrammer.com/2010/11/02/back-to-basics-what-is-an-interface/.

Теперь все это, не нарушает ли это принцип - программа для интерфейса, а не реализация?


person Sandbox    schedule 09.12.2010    source источник
comment
Связанный: stackoverflow.com/questions/2659366/   -  person jaco0646    schedule 19.10.2018


Ответы (4)


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

Когда мы говорим «интерфейс» в терминах программирования интерфейса. Такой интерфейс означает внешние методы и свойства класса. Это не обязательно должен быть интерфейс языкового уровня. (Интерфейс ключевого слова.)

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

person John Sonmez    schedule 09.12.2010
comment
Когда мы говорим «интерфейс» с точки зрения программирования для интерфейса. Такой интерфейс означает внешние методы и свойства класса. Это не обязательно должен быть интерфейс языкового уровня. Итак, я все время ошибался? Итак, по вашему мнению, конкретный класс может быть интерфейсом? - person Sandbox; 09.12.2010
comment
Правильный. В частности, публичные подписи методов и свойств составляют интерфейс этого класса. Каждый раз, когда вы создаете класс, все, что вы выбираете, становится частью внешнего интерфейса этого класса. Меняя его, ломает тех, кто на него полагается. Если другой класс полагается не только на ваш интерфейс (они полагаются на конкретные детали реализации внутри класса, например, как сортируется список или хранятся данные), то даже изменение небольших внутренних вещей может нарушить их. - person John Sonmez; 09.12.2010
comment
Но когда код ссылается на конкретный тип, не означает ли это, что если завтра у нас будет другой класс аналогичного типа, нам придется изменить код. Вместо этого с интерфейсами / абстрактными классами вы можете просто заменить другую реализацию во время выполнения. - person Sandbox; 09.12.2010
comment
Вы все еще можете сделать это, создав подклассы конкретного класса. Но это не относится к делу. Если вам не нужно что-то менять для другой реализации, не создавайте для этого структуру. Это создает дополнительную сложность, не имеющую текущей ценности, а только обнадеживающую будущую ценность. - person John Sonmez; 09.12.2010
comment
Создание подклассов создает свои собственные сложности. Итак, я не думаю, что создание подкласса конкретного класса - действительно хорошее решение. Не говоря уже об этом, как я сегодня узнаю, что мне понадобится другая реализация в будущем? Я имею в виду, что плохого в использовании интерфейса? - person Sandbox; 09.12.2010
comment
Делать подклассы я не предлагаю, это вообще плохо. Интерфейсы создают дополнительный уровень косвенности, который не добавляет ценности, если у вас нет нескольких реализаций этого интерфейса. Если вы делаете это с каждым создаваемым классом, вы в конечном итоге создаете большое количество косвенных ссылок в своем проекте, что добавляет сложности. - person John Sonmez; 09.12.2010
comment
@John, спасибо, что были терпеливы с моими вопросами. Но что вы имеете в виду, когда говорите, что это добавляет сложности? Как наличие нескольких интерфейсов добавляет сложности? › - person Sandbox; 09.12.2010
comment
Самый простой способ - это щелкнуть источник, перейти к определению и найти: «О, это интерфейс», а затем вам нужно проверить, что на самом деле реализует этот интерфейс. О, только один урок. Хм, это правда? Я что-то упускаю? О, нет, кто-то просто добавил этот интерфейс, чтобы можно было выполнять внедрение зависимостей, на самом деле это не служит цели. - person John Sonmez; 09.12.2010
comment
Что ж, это действительно служило цели внедрения зависимостей и насмешек. - person Sandbox; 09.12.2010
comment
На самом деле это служило только цели издевательства, потому что, если у вас нет более одной реализации, внедрение зависимостей на самом деле ничего вам не дало, просто добавило сложности. - person John Sonmez; 09.12.2010

Я бы сказал, что не согласен со многими пунктами в связанных статьях:

  • интерфейсы являются контрактами. Контракт состоит из двух частей - сигнатуры метода (чисто синтаксической) и документации.

  • интерфейсы являются абстракциями. Я не видел примера нарушения LSP. Пример IRectangle совсем не лучший. То же самое можно сказать и о Set extends Collection, где запрещено добавлять дубликаты. Если вам передали Collection, вы можете быть удивлены, что дубликаты не допускаются. С Collection интерфейсами об этом позаботятся путем документирования того, что разработчики могут добавлять ограничения.

  • Дырявые абстракции неизбежны. Но это полностью зависит от дизайнера. И, кстати, «интерфейсы - это дырявые абстракции» означает, что они являются абстракциями.

  • Ребята, похоже, упустили «попадание» в моду юнит-тестирование. Мок-реализации - очень хорошая причина для использования интерфейса (хотя вы также можете имитировать конкретные классы).

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

  • Кстати, изначально SortedSet имел только одну реализацию в JDK - TreeSet. Теперь их двое. И многое другое из внешних библиотек.

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

Тем не менее, вам не нужен интерфейс для всего. Но это зависит от конкретного случая. Я, например, не использую интерфейсы для вспомогательных классов. И действительный пункт статей - «программирование интерфейса» не обязательно включает ключевое слово interface. «Открытый интерфейс» класса - это (теоретически) набор его общедоступных методов.

person Bozho    schedule 09.12.2010
comment
Мок-реализации не являются хорошей причиной для использования интерфейса, они неизбежное зло, если вам нужна фиктивная реализация. Проблема в том, что наши языки программирования не поддерживают идею создания фиктивных реализаций, поэтому мы злоупотребляем интерфейсом для достижения результата. Хотя, я сам это делаю, но все же считаю важным понимать, что это неправильно. Мы платим цену за дополнительный интерфейс, цена - сложность и отсутствие сплоченности, потому что связанные вещи больше не связаны напрямую в нашем коде. - person John Sonmez; 09.12.2010
comment
как я уже сказал, макеты можно делать и для конкретных классов (по крайней мере, в Java есть несколько мощных фреймворков). Однако я не думаю, что использовать mocks для интерфейсов неправильно. Это разные реализации одной и той же концепции, не так ли? Тот факт, что он используется в определенной среде (модульный тест), ничего от этого не берет - это все еще интерфейс с двумя допустимыми реализациями. - person Bozho; 09.12.2010
comment
Кроме того, вам не следует использовать вспомогательные классы, они не имеют определенной ответственности. Они нарушают ОО. - person John Sonmez; 09.12.2010
comment
у них есть определенная ответственность - помогать;) они уменьшают дублирование кода. - person Bozho; 09.12.2010
comment
Я думаю, вам будет нелегко продать это. - person John Sonmez; 09.12.2010
comment
как однажды сказал Мартин Фольвер, я не пурист в области объектно-ориентированного программирования. Ну я сосед. Но есть такие люди;) - person Bozho; 09.12.2010

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

Вот вариант использования, который у меня часто бывает, когда наличие только одного разработчика (на мой взгляд) совершенно нормально: у вас есть компонент Swing, скажем, CarComparisonResultsPanel, который позволяет пользователю видеть результаты сравнения автомобилей. Как пользователь панели, я предпочел бы иметь CarComparisonResult интерфейс только с getCarSimilarities() и getCarDifferences(), чем реализацию JPanel, которая реализует эти методы, а также множество других.

РЕДАКТИРОВАТЬ: Чтобы сделать мой пункт «не переусердствуйте» немного яснее, вот несколько примеров переусердия: интерфейсы для фабрик, строителей, вспомогательные / служебные классы, компоненты графического интерфейса, которые не добавляют соответствующие общедоступные методы к своим родителям. , ...

person espinchi    schedule 09.12.2010
comment
Я действительно согласен с вами в этом. В этом случае вы используете интерфейс, чтобы уменьшить сложность и видимость кода с помощью CarComparisonResultsPanel. - person John Sonmez; 09.12.2010
comment
Как решить не переусердствовать? Я имею в виду, что с вашим клиентским кодом, ссылающимся на интерфейсы / абстрактные классы, вы всегда можете поменять реализацию во время выполнения. Кроме того, если у вас нет интерфейсов, как вы собираетесь издеваться над ними? Эти две причины заставляют меня думать, что интерфейсы / абстрактные классы совершенно допустимы во всех случаях. - person Sandbox; 09.12.2010
comment
В моем блоге simpleprogrammer.com есть серия статей, посвященных основам, в которых подробно рассказывается об интерфейсах и этой конкретной теме, погрузившись в DI, IoC и т. д., вы можете проверить это. - person John Sonmez; 09.12.2010
comment
Как вы говорите, не переусердствовать - это очень расплывчато ... но я предполагаю, что, если выразить это словами, без изложения сценариев с реальным кодом просто невозможно (или, по крайней мере, за пределами моих способностей к объяснению!) - person espinchi; 09.12.2010

Принципы, лежащие в основе программирования интерфейса, не обязательно должны быть связаны только с ситуациями. При разработке интерфейсов вы задаете общие вопросы: «Где я ожидаю, что это будет использовано? Кем? И с какой целью?» Вопросы, которые следует задавать даже при создании классов реализации.

Может случиться так, что при разработке интерфейса вы придете к осознанию того, что вам на самом деле не нужно делать его интерфейсом, и для тестирования будет достаточно разрешения перегрузки и наследования. Как упоминалось в первой статье, если вы постоянно получаете корреляцию 1: 1 между объектами и интерфейсами без какой-либо другой цели, кроме «Я программирую против интерфейсов», вы просто портите свой код.

Но это не означает, что вы не можете продолжить разработку класса, как если бы вы создавали для него интерфейс / базовый класс, с набором общих методов / свойств / полей, которые обеспечивают базовую функциональность, а затем добавляют другие методы / свойства / поля, более специфичные для реализации. Это все равно, ИМО, будет подпадать под принципы программирования интерфейса. Кроме того, это определенно оставит вам возможность извлечь интерфейс / базовый класс, когда возникнет ясная и определенная необходимость.

person Wes P    schedule 09.12.2010
comment
Я бы сказал, что разрешение перегрузки и наследования только для целей тестирования намного хуже, чем создание интерфейса 1: 1. Путем наследования вы привносите новую сложность в свой общедоступный API и открываете множество возможностей для нарушения поведения вашего класса. Интерфейс 1: 1 по крайней мере не имеет таких побочных эффектов. - person NOtherDev; 09.12.2010