Минимальный API против удобства

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

Пример Google: в HashBiMap (doc):

Почему у BiMap нет метода getKeyForValue()?

Мы думали об этом (Даг Ли даже полушутя предложил назвать его teg()!). Но на самом деле вам это не нужно; просто позвоните inverse().get().

Часто задаваемые вопросы о коллекциях Google

Пример этого на интерфейсе Set: add() и remove() — это минимальные методы, а addAll() и removeAll() — для удобства. addAll() может быть реализован в терминах add(), так что на самом деле это не дает клиенту новых возможностей для работы с Set. Но он очищает клиентский код.

Я подумал о создании класса Utility, который включал бы больше удобных методов. Но тогда я ухожу от ООП, и мне приходится включать объект, над которым выполняется операция, в качестве аргумента при каждом вызове. Хотя я предполагаю, что это следует примеру класса Java Collections.


person Nick Heiner    schedule 01.01.2010    source источник
comment
Поскольку Google — это Бог, идите с Google.   -  person President James K. Polk    schedule 01.01.2010
comment
Ваш пример пограничный. Свободные интерфейсы немного меняют сценарий. Вызов V value = originalMap.inverse().get(), возможно, лучше, чем BiMap‹V,K› tempMap = originalMap.inverse(); Значение V = tempMap.get(); Также комбинация методов лучше, чем создавать еще одну отдельную функцию с большим количеством кода, чем просто вызовы методов класса.   -  person Vinko Vrsalovic    schedule 01.01.2010
comment
Это одна из проблем, для решения которой были разработаны трейты Scala (javaforyou.wordpress.com/2009/07/11/traits-in-scala-deep-dive). К сожалению, инструменты Scala по-прежнему остаются мусором....   -  person skaffman    schedule 01.01.2010


Ответы (9)


Я бы определенно предоставил дополнительные API всякий раз, когда есть шанс, что класс сможет (даже если сегодня это не так) реализовать этот API более эффективно, чем клиент. (Например, Set.removeAll().) И вообще я бы предоставлял дополнительные API всякий раз, когда это очищает клиентский код.

Не могли бы вы привести пример API Google, который не предоставляет, казалось бы, полезного удобного метода в пользу того, чтобы клиент выполнял несколько вызовов?

person Ross    schedule 01.01.2010
comment
+1 Я согласен с тем, что правдоподобие более эффективной реализации имеет первостепенное значение при принятии решения о том, предлагать ли технически избыточную операцию на интерфейсе. В случае BiMap неправдоподобно, чтобы getKeyForValue() могла делать что-то, чего не может сделать inverse().get(), поэтому мы урезаем его. - person Kevin Bourrillion; 01.01.2010

Предлагая больше методов, переопределение виртуальных методов становится более сложным/опасным.

Рассмотрим, например, add() и addAll(). Вызывает ли addAll() add()? Можно (это может быть простая оболочка, вызывающая add() для каждого элемента по очереди), но это не обязательно. Итак, если вы затем создадите подкласс и добавите какой-то новый инвариант (возможно, например, вы сделаете add() для добавления вещей в отдельный контейнер для хранения порядка вставки или чего-то еще, существует множество вариантов контейнеров, которые полезны в разных приложениях) , теперь вам нужно знать, вызывает ли addAll() add(). Если это так, отлично, ваш подкласс поддерживает правильное поведение. Но это не обязательно!

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

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

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

person DrPizza    schedule 01.01.2010

Я буду использовать ответ Джона Ф.:

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

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

Здесь много сторон:

http://martinfowler.com/bliki/HumaneInterface.html

person Mario    schedule 09.08.2010

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

Как правило, как только ваше сообщество разработчиков становится достаточно большим, люди все равно пишут свои собственные расширения, помощники и утилиты для вашего API.

person John Feminella    schedule 01.01.2010

Одним из решений является предоставление как интерфейса, так и абстрактной реализации, реализующей удобные методы. Для примера сравните

interface List ...

и

class AbstractList implements List ...

в пакете java.util. Таким образом, клиент может создать подкласс из абстрактного класса и просто реализовать абстрактный метод.

Однако лично мне не было бы стыдно помещать удобные методы в служебный класс. Вы не можете программировать чистый ООП на ломаном языке. Чего здесь не хватает Java, так это либо черты, либо методы расширения. Насколько я знаю, методы расширений обсуждаются для Java 7.

person akuhn    schedule 01.01.2010

Пока служебные методы действительно полезны, а не просто плод вашего воображения («когда-нибудь они могут пригодиться, если оборотни завоюют США»), я бы добавил их к исходным классам.

Следуя вашему примеру, addAll() и removeAll() являются служебными методами, которые разумны и должны быть добавлены в Set. Но addEven() и removeEven() неразумны, даже если они могут быть полезны в каком-то конкретном случае.

Теперь, как определить, какие методы являются разумными? Всего два пути: опыт и опыт.

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

person Vinko Vrsalovic    schedule 01.01.2010

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

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

person TofuBeer    schedule 01.01.2010

Еще одна точка отсчета: List в Java имеет оба add() и addAll().

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

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

Другими словами: цель вашей библиотеки — быть полезной. Удобные методы делают ее более удобной, но они не помогают, если библиотека изначально бесполезна. Фундаментальные методы помогают обеспечить полноту вашего кода (аспект математического совершенства), но опять же они не помогут, если ваша библиотека изначально бесполезна.

Другими словами: на 100% сосредоточьтесь на том, чтобы сделать его полезным, и пусть удобство использования и полнота вытекают из этого.

person 13ren    schedule 01.01.2010

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

person ZoFreX    schedule 01.01.2010