LINUX.ORG.RU

[ООП] Интерфейсы


0

2

Я привык при программировании «вслушиваться» в код, анализируя его с точки зрения понятности и логичности. Хороший код в моем понимании как складный рассказ. Поэтому многие вещи я делают интуитивно.

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

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

Попытался для себя составить список когда интерфейс нужен:

1. Есть несколько реализаций. Самый очевидный случай.
2. Реализация одна, но как бы подразумевается, что может быть несколько.

Есть еще такая штука: сегодня реализация одна, а завтра станет несколько. Но я считаю, что это не повод засорять код, рефакторинг «выделение интерфейса» - очень простой.

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

★★★★★

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

Sonsee ()
Ответ на: комментарий от Sonsee

интерфейс делает код более гибким.

Зачем мне эта гибкость здесь и сейчас, если гибкость такого рода у меня элементарно обеспечивается кнопочкой «extract interface» в IDE?

ну например, есть класс, который может собирать объект разными способами, для определения способа делаем интерфейс

См. пункт 1. Тут подразумевается несколько реализаций, поэтому здесь интерфейс оправдан.

dizza ★★★★★ ()

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

bga_ ★★ ()
Ответ на: комментарий от bga_

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

dizza ★★★★★ ()

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

dizza ★★★★★ ()

Если класс не является частью внешнего API пакета, делать интерфейс не нужно.

tailgunner ★★★★★ ()
Ответ на: комментарий от tailgunner

Согласен. Ко внешнему API нужно применять как можно больше практик по уменьшению связности. Обязательные тривиальные getters/setters даже для тупых записей, например.

dizza ★★★★★ ()
Ответ на: комментарий от dizza

Тут подразумевается несколько реализаций

представим, что есть метод, который ходит по матрице и обновляет её.
при каждом обновлении мы удаляем одну колонку и ряд, совмещая два в один
способов «совмещения» 5, один из которых выбирает юзер.
вопрос, нужен интерфейс или нет? и если нет, то как лучше?

Sonsee ()
Ответ на: комментарий от dizza

Это бесползные раздумья из разряда «protected vs private» на этапе проектирования. Слово abstract дописывается/убирается за доли секунды. И лучше уже везде писать чем потом боротся с компилятором. Как правило наоборот запрещать переписывать метод нужно очень редко.

bga_ ★★ ()
Ответ на: комментарий от dizza

> Ко внешнему API нужно применять как можно больше практик по уменьшению связности. Обязательные тривиальные getters/setters даже для тупых записей, например.

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

tailgunner ★★★★★ ()
Ответ на: комментарий от tailgunner

всегда должна быть версия 0, которую постоянно ломают и которая несовместима сама с собой.

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

dizza ★★★★★ ()
Ответ на: комментарий от Sonsee

Ну это надо на код смотреть. Может у тебя там enum-а достаточно и swtich-a. А может нужен целый Strategy.

dizza ★★★★★ ()
Ответ на: комментарий от dizza

>> всегда должна быть версия 0, которую постоянно ломают и которая несовместима сама с собой.

А нафига так делать?

Потому что вот это:

dizza> Когда я хочу переименовать метод в публичном API, я старый делаю @Deprecated и добавляю метод с новым именем

Слова маленького мальчика, который научился пИсать сам и горд этим^W^W^W^W^W^W^W^W^W^Wдополнительная нагрузка, которой лучше избежать.

tailgunner ★★★★★ ()

Ну на каждый не нужно, это да, по ситуации.

CrossFire ★★★★★ ()

это называется KISS, если тебе это не нужно - не тащи, если, конечно, тебе это не нужно

shty ★★★★★ ()
Ответ на: комментарий от tailgunner

Нет фиксированных рецептов конструирования приличного API - практически всегда бывает версия 0, которую постоянно ломают и которая несовместима сама с собой.

//fixed

shty ★★★★★ ()

С моей точки зрения во всех остальных случаях класс вполне способен жить сам по себе.

С моей точки зрения как питониста, класс вообще способен жить сам по себе, а читающие его код люди представляют себе хоть 0, хоть 1, хоть n, хоть даже k интерфейсов.

t184256 ★★★★★ ()
Ответ на: комментарий от t184256

класс вообще способен жить сам по себе

Угу, особенно в статически типизированных языках, наподобие джавы.

baverman ★★★ ()

Интерфейс — это контракт, код класса — реализация. В целом предпочитаю держать их отдельно для лучшей читаемости. Но единого рецепта, конечно, нет.

WFrag ★★★★ ()
Ответ на: комментарий от dizza

Зачем? Геттеры-сеттеры есть по сути детали реализации. Use DTO, Luke.
Касательно твоего вопроса: правило 80/20 работает и тут.

JackyTreehorn ()

> Попытался для себя составить список когда интерфейс нужен

Даже не пытайся. «Ничто не может заменить ума, опыта и вкуса» (c)

archimag ★★★ ()
Ответ на: комментарий от archimag

...сказал дракон, выковыривая из зубов остатки рыцаря.

anonymous ()

Всегда делаю ровно столько, сколько нужно. И ни грамма больше. Код «на всякий случай» в готовом виде нужен в 0.00001 ppm от всех случаев использования старого кода. «Универсализация» только делает всё сложнее и медленее.

Напиши драйвер, поймёшь о чём я.

mi_estas ()
Ответ на: комментарий от tailgunner

дополнительная нагрузка, которой лучше избежать.

Спорить не буду. Но раньше избегал. Но вот когда стал работать в компании, где куча сервисов/либ и тому подобного понял, что если делать по-другому будет хаос.

dizza ★★★★★ ()
Ответ на: комментарий от dizza

Поэтому и говориться о версии 0 - до выпуска «внешним» клиентам. Потом, когда от твоего API уже кто-то зависит, @Deprecated становится единственным выходом, но не раньше же.

tailgunner ★★★★★ ()

Мне кажется, что интерфейс - это логичное продолжение идеи о разделении декларации, реализации и вызова (основа структурного программирования). Класс - реализация, интерфейс - декларация. Мне кажется что в идеале в программе вообще не должны использоваться классовые переменные - только для конструкторов. Но тут как с «нормальными формами» в базах данных: да, это идеал к которому надо стремиться, но и меру надо знать. Из баз же данных можно и почерпнуть методику построения - сначала доводим базу до нормальной формы (пишем все мыслимые и немыслимые интерфейсы), а потом уже денормализуем ее в угоду удобству/скорости (удаляем лишние интерфейсы).

//поклонник джавы

morse ★★★★★ ()
Ответ на: комментарий от morse

Набор публичный методов класса чем не интрефейс в смысле декларации? Особенно, если брать язык вроде С++, там даже декларация отдельная.

dizza ★★★★★ ()
Ответ на: комментарий от dizza

Эта «декларация» намертво привязанна к «реализации» и потому не сильно отличается от таковой в джаве. Идея разделения состоит не в том чтобы эти три вещи были в разных фалйах, а в том чтобы все эти три вещи были независимы. Мы пишем реализацию метода и нам абсолютно все равно кто где и как будет их вызывать - это разделение реализации и вызова. Мы декларируем методы и нам абсолютно все равно кто где и как будет их реализовывать - это разделение декларации и реализации. Да, это не всегда нужно, как не всегда нужно разделение реализации и вызова (см. анонимные классы джавы), но все-таки по умолчанию мы рассматриваем реализацию отдельно от вызова, и объединяем их только при наличии серьезных аргументов за. Совершенно не вижу почему в случае декларации мы должны поступать иначе.

morse ★★★★★ ()
Ответ на: комментарий от dizza

Обязательные тривиальные getters/setters даже для тупых записей, например.

Геттеры и сеттеры нарушают инкапсуляцию и усложняют протокол синхронизации состояния объекта с его полями.

Нужно избегать сеттеров и геттеров всеми возможными способами. У объекта должны быть публичными только те методы, которые действительно участвуют в работе и используются другими объектами. Тупое выставление get<Field>/set<Field> нарушают устойчивость программы и являются причиной трудноуловимых незапланированных поведений, которые, конечно же, для своего устранения требуют написания дополнительных модульных и функциональных тестов!

iZEN ★★★★★ ()

Интерфейс, как и публичные члены класса (public members), представляют из себя КОНТРАКТ, который разработчик заключает с пользователями его обектов — другими разработчиками.

Ну, если разработчик гений и с самого начала может перечислить в интерфейсе все необходимые ему методы, то вперёд и с пестней! Но лично я считаю заранее оговоренное объявление и перечисление публичных членов сродни преждевременной оптимизации, из-за которой проваливается большинство проектов. Слишком уж негибкая программная система получается ИЗНАЧАЛЬНО, а её ещё расти и расти.

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

Товарищ, в общем-то, дурак, раз связывает себя по рукам и ногам с самого начала реализации.

iZEN ★★★★★ ()
Ответ на: комментарий от Sonsee

представим, что есть метод, который ходит по матрице и обновляет её. при каждом обновлении мы удаляем одну колонку и ряд, совмещая два в один

способов «совмещения» 5, один из которых выбирает юзер. вопрос, нужен интерфейс или нет? и если нет, то как лучше?

Паттерн Strategy //К.О.

iZEN ★★★★★ ()
Ответ на: комментарий от morse

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

Без использования интерфейса все тоже самое.

Меня еще больше радует цепочка рассуждений: класс А зависит от класса Б, создадим интерфейс ИБ (Б реализует ИБ), теперь А зависит от ИБ, и Б зависит от ИБ. ЯКОБЫ таким образом мы разорвали связь между А и Б. Фишка в том, что связь между Б и ИБ есть, и она в противоположную сторону, не Б реализует ИБ, а ИБ является копией публичного контракта Б. Все тоже самое.

dizza ★★★★★ ()
Ответ на: комментарий от iZEN

Нужно избегать сеттеров и геттеров всеми возможными способами.

Я практически всегда делаю публичные поля. Но вот недавно гетеры и сетеры позволили сделать довольно сложный рефакторинг базы данных. Было перенесено поле из одной таблицы в другую, а между таблицами было отношение наследования. Гетеры и сетеры позволили не останавливая на долго работу приложения перенести значения из одной таблицы в другую. Сделано было так: первый релиз - сеттер в потомке пишет значение в свое поле и в поле предка. далее скрипт по базе, который копирует разницу значений. Таким образом все значения перенесены, а новые автоматически переносятся. Далее релиз в котором в гетере используется поле предка. При этом весь основной код не пострадал.

dizza ★★★★★ ()
Ответ на: комментарий от dizza

>класс А зависит от класса Б

Не очень понятно что значит сие. Класс А использует некую функциональность которая описывается интерфейсом ИБ? Прекрасно. Значит он может использовать абсолютно любой класс реализующий этот интерфейс. Или он использует конкретные особенности реализации классом Б интерфейса ИБ aka недокументированные функции? Тогда это ОЧЕНЬ плохая программа.

morse ★★★★★ ()
Ответ на: комментарий от iZEN

>Товарищ, в общем-то, дурак, раз связывает себя по рукам и ногам с самого начала реализации.

Отнюдь не каждый интерфейс является частью public API. То, что в плюсах нету пакетной видимости совершенно не является их достоинством.

morse ★★★★★ ()
Ответ на: комментарий от morse

Класс А использует некую функциональность которая описывается интерфейсом ИБ?

Да.

Значит он может использовать абсолютно любой класс реализующий этот интерфейс.

Нету никаких других реализаций. Где ты их в моем примере увидел?

Или он использует конкретные особенности реализации классом Б интерфейса ИБ aka недокументированные функции?

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

dizza ★★★★★ ()
Ответ на: комментарий от dizza

Нет, он использует просто публичные методы

fixed

dizza ★★★★★ ()
Ответ на: комментарий от dizza

>Нету никаких других реализаций. Где ты их в моем примере увидел?

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

morse ★★★★★ ()
Ответ на: комментарий от dizza

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

Это использование объекта класса не по назначению — для такого случая хороша абстракция паскалевского описания типа record, а для Java характерно название такой структуры Plain Old Java Object (POJO). Ключевое слово «Old» («старый») говорит о том, что этот объект класса ничего кроме данных о своём состоянии и методов изменения полей не несёт и подобен record в Паскале. Это самый безответственный класс, так как никакой бизнес-логики не несёт — просто «ящик» для данных.

iZEN ★★★★★ ()
Ответ на: комментарий от iZEN

Ключевое слово «Old» («старый»)

Ещё и «Plain» («плоский») тоже характерное определение для такого рода объектов — финтить им самим никто не даёт, наоборот — финтят ими.

iZEN ★★★★★ ()

Насчёт сокрытия информации о реализации и об открытых интерфейсах доступно написано у Джошуа Блоха в книге «Эффективное программирование на Java» в главе 4 «Классы и интерфейсы»:

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

<...>

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

Для классов и интерфейсов верхнего уровня (не являющихся вложенными) существуют лишь два возможных уровня доступа: доступный только в пределах пакета (package-private) и открытый (public). Если вы объявляете класс или интерфейс верхнего уровня с модификатором public, он будет открытым, в противном случае он будет доступен только в пределах пакета. Если класс или интерфейс верхнего уровня можно сделать доступным только в пакете, так и нужно поступать. При этом класс или интерфейс становится частью реализации этого пакета, а не частью его внешнего API. Вы можете модифицировать его, заменить или исключить из пакета, не опасаясь нанести вред клиентам. Если же вы делаете класс или интерфейс открытым, на вас возлагается обязанность всегда поддерживать его с целью сохранения совместимости.

<...>

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

<...>

Подведем итоги. Всегда следует снижать уровень доступа, насколько это возможно. Тщательно разработав наименьший открытый API, вы должны не дать возможности каким-либо случайным классам, интерфейсам и членам стать частью этого API. За исключением полей типа public static final, других открытых полей в открытом классе быть не должно. Убедитесь в том, что объекты, на которые есть ссылки в полях типа public static final, не являются изменяемыми.

и про интерфейсы:

открытые интерфейсы необходимо проектировать аккуратно. Как только интерфейс создан и повсюду реализован, поменять его почти невозможно. В действительности его нужно правильно строить с первого же раза. Если в Интерфейсе есть незначительньый изъян, он уже всегда будет раздражать и вас, и пользователей. Если же интерфейс имеет серьезные дефекты, он способен погубить АРI. Самое лучшее, что можно предпринять при создании нового интерфейса, - заставить как можно больше программистов реализовать этот интерфейс самыми разнообразными способами, прежде чем он будет «заморожен». Это позволит вам найти все ошибки, пока у вас еще есть возможность их исправить.

<...>

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

iZEN ★★★★★ ()
Ответ на: комментарий от morse

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

Какой еще зуд? Нажать кнопочку «extract interface»?

dizza ★★★★★ ()
Ответ на: комментарий от iZEN

Это использование объекта класса не по назначению

POJO

А у меня что, не POJO? Самый настоящий POJO и был.

Да буть он и не пожо, что бы это принципиально изменило? Вообще о чем ты? :)

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

dizza ★★★★★ ()
Ответ на: комментарий от iZEN

Да и вообще мною уже сто раз на практике был подтвержден тот факт, что в некоторых случая, использовать POJO в качестве хранимых сущностей - идиотизм. Это характерно для доменов, богатых логикой. Например биллинг на POJO - полный изврат. Так ты себя лишаешь массы возможностей. Вообще эти дурацкие правила «все делай так, всегда делай сяк, всегда делай геттеры/сеттеры, не делай, всегда делай интерфейсы, всегда используй пожо для хранимых объектов и т.д.» для индусов. Лучший способ угробить код - написать его по каким-то однобоким правилам.

dizza ★★★★★ ()
Ответ на: комментарий от dizza

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

energyclab ()
Ответ на: комментарий от energyclab

Интерфейсы никак не помогают в «уменьшении привилегий». У классов и так есть способ уменьшения привилегий в виде не-public модификаторов доступа.

Вот это вообще не понял:

тоесть как минимум сетеры и гетеры

Здоров, выворачиваем кишки на изнанку. Лучше не обманывать себя и не городить лишние, только мешающие слои абстракций и сделать public-поля.

dizza ★★★★★ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.