Лучшая практика для предоставленных реализаций интерфейсов

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

В настоящее время я реализовал это через интерфейс Mapper с методом run, который принимает метод карты. Пользователи используют это так:

wheelValues.map(new Mapper() {
    @Override
    public double run(double input) { ••• }
});

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

public interface Mapper {
    // This?
    static final Mapper ABS = new Mapper() {...}
    // Or this?
    static class Abs implements Mapper {...}
}

Есть много способов решить эту проблему. Я бы порекомендовал статический метод, который возвращает новый экземпляр вашей реализации по умолчанию. Таким образом, каждый из нескольких вызывающих абонентов получит свой собственный экземпляр. Я бы также подумал о переименовании вашего метода run() во что-нибудь другое, например transform(). Это лучше показывает, что делает метод (run подразумевает выполнение потока).

Jason 18.12.2018 22:38

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

Tom Hawtin - tackline 18.12.2018 22:54

@ TomHawtin-tackline Я бы сказал, что это зависит от реализации решения. Учитывая приведенную выше информацию, невозможно определить, должен ли каждый вызывающий объект получить свой собственный экземпляр или один общий экземпляр. Используя статический метод, реализация метода может решить, в каком направлении двигаться. Статическое поле ограничивает это решение.

Jason 19.12.2018 01:38
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
3
50
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Сделайте API настолько маленьким, насколько практичным. Поэтому не добавляйте дополнительный класс, в котором будет достаточно статического метода или поля final.

enum - плохой выбор. Как, например, java.nio.file.StandardCopyOption.

OTOH, вы не можете разрешить изменение параметров типа, если используете константы перечисления или статические поля, поэтому метод может быть предпочтительнее, хотя бы для согласованности. Как, например, java.util.Collections.emptySet или java.util.stream.collectors.toSet.

Чтобы не создавать слишком много экземпляров, вам может понадобиться частное статическое поле за любым методом.

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

Ответ принят как подходящий

По возможности начните с использования стандартных библиотечных интерфейсов; например, ваш Mapper выглядит как дубликат DoubleFunction (или Function<Double, Double>). Абсолютное значение предоставляется Math.abs, и вы уже можете сослаться на него, говоря, что Math::abs, "реализация по умолчанию" не требуется.

Что касается готовых реализаций, можно выделить две общие категории:

  • Полностью чистые функции (у которых нет регуляторов или настроек) обычно где-то реализуются как константы. Хорошим примером этого является String.CASE_INSENSITIVE_ORDER, константа Comparator<String>, выполняющая то, что написано в ее названии.

  • Функции, которым требуется личная копия, потому что у них есть какой-то параметр, реализованы как статические методы, которые возвращают экземпляр этого функционального интерфейса. Хорошим примером здесь является Predicate.isEqual(target), который возвращает экземпляр Predicate (объект-> логическое значение), который возвращает истину, если проверяемое значение равно целевому значению (предоставляется при создании экземпляра): private static final Predicate IS_CORRECT = Predicate.isEqual(correctAnswer).

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

Richik SC 19.12.2018 00:14

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

Jason 19.12.2018 01:43

Другие вопросы по теме