Является ли «статический интерфейс» хорошей практикой?

13

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

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

Простой пример - просто конверт для глобальных констант. По сравнению с классом, вы можете легко заметить отсутствующий шаблон public static finalкак предполагаемый (что делает его менее многословным).

public interface Constants {
    String LOG_MESSAGE_FAILURE = "I took an arrow to the knee.";
    int DEFAULT_TIMEOUT_MS = 30;
}

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

public interface ConfigKeys {
    static createValues(ConfigKey<?>... values) {
        return Collections.unmodifiableSet(new HashSet(Arrays.asList(values)));
    }

    static ConfigKey<T> key(Class<T> clazz) {
        return new ConfigKey<>(clazz);
    }

    class ConfigKey<T> {
        private final Class<T> type;
        private ConfigKey(Class<T> type) {
            this.type = type;
        }
        private Class<T> getType() {
            return type;
        }
    }
}

import static ConfigKeys.*;
public interface MyAppConfigKeys {
    ConfigKey<Boolean> TEST_MODE = key(Boolean.class);
    ConfigKey<String> COMPANY_NAME = key(String.class);

    Set<ConfigKey<?>> VALUES = createValues(TEST_MODE, COMPANY_VALUE);

    static values() {
        return VALUES;
    }
}

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

Я считаю, что это хорошая новая функция, и особенно тот факт, что статические члены не наследуются, является интересной концепцией, которая была представлена ​​только для интерфейсов.

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

Меня больше интересуют причины (не) использовать шаблоны, подобные этим двум.


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

Vlasec
источник
1
Моя реакция коленного рефлекса на это заключается в том, что это может привести только к плохим вещам. Хотя общедоступные статические методы, по сути, являются просто глобальными функциями с пространством имен. Такое ощущение, что любой код реализации в интерфейсе идет вразрез с его назначением в качестве определения контракта при отсутствии реализации. Я говорю оставить частичную реализацию абстрактным классам. Мне нужно будет прочитать, почему это было добавлено.
Адриан
Я должен идти сейчас, но я напишу что-нибудь завтра об этом. Суть в том, что раньше интерфейс имел ясную цель, которая была четко отделена от цели AbstractClass. Теперь они сильно перекрываются, что нарушает независимое от языка определение интерфейса. Кроме того, есть случаи, когда вы не можете наследовать от нескольких интерфейсов, если они реализуют методы по умолчанию. Так что теперь есть исключения для множественного интерфейса наследования, где раньше их не было. Поскольку Java резко отклонился от общего определения интерфейса ...
Адриан
это также может сбить с толку новых разработчиков, которые пытаются правильно разобраться с основными понятиями ООП, такими как интерфейсы, абстрактные класы, когда использовать композицию вместо наследования и т. д.
Адриан
Алистер, вы говорите о defaultметодах. Я говорю о staticметодах и областях. Они не наследуются, поэтому они не нарушают множественное наследование.
Власек

Ответы:

1

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

Помещение констант в интерфейсы называется, Constant Interface Antipatternпоскольку константы часто являются просто деталями реализации. Дальнейшие недостатки описаны в статье в Википедии об интерфейсе Constant .

Вы также можете создать некоторую утилиту "класс" таким образом.

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

Маркус Пшайдт
источник
1
Я не думаю, что это на самом деле ответ на поставленный вопрос, так как вы могли бы сделать обе эти вещи без этой новой функциональности.
Адриан
Ах, так по документации, он предназначен как вспомогательный метод. Впрочем, это тоже публично, что противоречит этому. Так что, возможно, это также поможет классам, которые реализуют интерфейс. Но они не наследуются, поэтому вам придется использовать статический импорт, чтобы создать ощущение, что вы его унаследовали. Что ж, спасибо за поиск в документах.
Власек
2
Нет ничего плохого в том, чтобы помещать константы в интерфейсы, если эти константы каким-то образом являются частью API, описанного интерфейсом. Единственное, что является антипаттерном, это помещает константы в интерфейсы без методов, а классы реализуют интерфейс только для ссылки на константы без префикса класса. Это злоупотребление интерфейсами и, поскольку введение статического импорта полностью устарело.
Майкл Боргвардт
1

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

  • Класс не может быть расширен без super()вызова, но интерфейс может быть "реализован"
  • Класс не может быть создан без отражений, в то время как интерфейс может быть легко создан как простой анонимный класс: new MyInterface() {};

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

Есть также странная вещь в наследовании от «статического интерфейса»:

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

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

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

Как и @MarkusPscheidt, эта идея по сути является расширением антипаттерна Constant Interface . Этот подход первоначально широко использовался, чтобы позволить одному набору констант наследоваться многими разнородными классами. Причиной того, что это считается антипаттерном, стало то, что проблемы возникли при использовании этого подхода в реальных проектах. Я не вижу оснований думать, что эти проблемы будут касаться только констант.

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

JimmyJames
источник
Да, статический импорт звучит как лучшая идея, чем наследование констант, и он работает с статическими членами как классов, так и интерфейсов. И это видно только внутри класса / интерфейса.
Власек
@ Влас Верно, но нет необходимости делать это на интерфейсе. Единственная разница между выполнением этого с классом и интерфейсом состоит в том, что вы можете наследовать от более чем одного интерфейса. Стандартная практика для такого служебного класса - сделать конструктор закрытым, чтобы предотвратить создание экземпляров или расширение. Нет смысла делать это в интерфейсе над классом. Это просто открывает дверь для неправильного использования.
JimmyJames
Я могу видеть значение в константах на интерфейсах. Интерфейсы имеют тенденцию определять методы, а методы иногда принимают константы в качестве параметров. Например: interface ColorFilter {Image applyGradient(int colorSpace, int width, byte[] pixels); }. Я могу себе представить , чтобы там быть некоторые различные константы RGB, RGBA, CMYK, GREYSCALE, и INDEXEDт.д.
Стейн де Витт
@StijndeWitt Это не самое плохое, но вы можете использовать перечислимые или вложенные классы для интерфейса для этой цели. Реальная проблема заключается в том, чтобы использовать это, чтобы ввести статику в область действия многих классов посредством наследования. Этот подход явно уступает использованию статического импорта.
JimmyJames
0

Я бы сказал, если вам интересно, как правильно использовать новую функцию, взгляните на код в JDK. Методы по умолчанию на интерфейсах очень широко используются и их легко найти, но статические методы на интерфейсах кажутся очень редкими. Некоторые примеры включаютjava.time.chrono.Chronology , java.util.Comparator, java.util.Mapи довольно много интерфейсов в java.util.functionи java.util.stream.

Большинство примеров, которые я рассмотрел, включают в себя использование этих API, которые, вероятно, очень распространены: преобразование между различными типами в API времени, например, для сравнений, которые, вероятно, часто выполняются между объектами, передаваемыми в функции или объекты, содержащиеся в коллекции. Мой вывод: если есть часть вашего API, которая должна быть гибкой, но вы можете покрыть, скажем, 80% случаев использования несколькими значениями по умолчанию, рассмотрите возможность использования статических методов в ваших интерфейсах. Учитывая, как редко эта функция используется в JDK, я буду очень консервативен, когда использую ее в своем собственном коде.

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

ngreen
источник
-1

Интересно, есть ли причины не использовать его.

Как насчет совместимости со старыми JVM?

Считаете ли вы это хорошей идеей в целом?

Нет. Это несовместимое назад изменение, которое добавляет пыль в выразительность языка. Два больших пальца вниз.

Существуют ли модельные ситуации, когда вы настоятельно рекомендуете занятия?

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

ОБНОВЛЕНИЕ: Пожалуйста, не более отрицательных голосов. Очевидно, что некоторые люди думают, что статические методы в интерфейсах - это колени пчелы, и я тем самым капитулирую. Я бы просто удалил этот ответ, но комментарии к нему - интересная дискуссия.

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