В последнее время у меня была привычка «маскировать» коллекции Java с понятными для человека именами классов. Несколько простых примеров:
// Facade class that makes code more readable and understandable.
public class WidgetCache extends Map<String, Widget> {
}
Или же:
// If you saw a ArrayList<ArrayList<?>> being passed around in the code, would you
// run away screaming, or would you actually understand what it is and what
// it represents?
public class Changelist extends ArrayList<ArrayList<SomePOJO>> {
}
Коллега указал мне, что это плохая практика, и вводит задержку / задержку, а также является анти-паттерном ОО. Я могу понять, что это приводит к очень небольшому снижению производительности, но не могу представить, что это вообще важно. Поэтому я спрашиваю: хорошо это или плохо, и почему?
java
anti-patterns
generics
collections
herpylderp
источник
источник
ChangeList
компиляция прерветсяextends
, потому что List является интерфейсом, требуетimplements
. @randomA то, что вы представляете, не находит смысла из-за этой ошибкиimplementation
естьHashMap
илиTreeMap
то, что у него было, было опечаткой.Ответы:
Лаг / латентность? Я звоню BS по этому вопросу. От этой практики накладных расходов должно быть ровно ноль. ( Редактировать: в комментариях было указано, что это может фактически препятствовать оптимизации, выполняемой виртуальной машиной HotSpot. Я не знаю достаточно о реализации виртуальной машины, чтобы подтвердить или опровергнуть это. Я основывал свой комментарий на C ++ реализация виртуальных функций.)
Есть некоторые накладные расходы кода. Вы должны создать все необходимые вам конструкторы из базового класса, передав их параметры.
Я также не вижу в этом анти-паттерна. Однако я вижу в этом упущенную возможность. Вместо того, чтобы создавать класс, который выводит базовый класс просто для переименования, как насчет того, чтобы создать класс, который содержит коллекцию и предлагает улучшенный интерфейс для конкретного случая? Должен ли ваш кеш виджетов действительно предлагать полный интерфейс карты? Или он должен вместо этого предложить специализированный интерфейс?
Кроме того, в случае коллекций шаблон просто не работает вместе с общим правилом использования интерфейсов, а не реализаций - то есть в простом коде коллекции вы должны создать
HashMap<String, Widget>
, а затем назначить его переменной типаMap<String, Widget>
. ВашWidgetCache
не может распространятьсяMap<String, Widget>
, потому что это интерфейс. Это не может быть интерфейс, который расширяет базовый интерфейс, потомуHashMap<String, Widget>
что не реализует этот интерфейс, как и любой другой стандартный набор. И хотя вы можете сделать его классом, который расширяетсяHashMap<String, Widget>
, вам необходимо объявить переменные какWidgetCache
илиMap<String, Widget>
, и первый из них теряет гибкость для замены другой коллекции (может быть, некоторой коллекции отложенной загрузки ORM), в то время как второй вид побеждает точку иметь класс.Некоторые из этих контрпунктов также применимы к моему предлагаемому специализированному классу.
Это все моменты для рассмотрения. Это может быть или не быть правильным выбором. В любом случае предложенные коллегой аргументы недействительны. Если он думает, что это анти-шаблон, он должен назвать это.
источник
public class Properties extends Hashtable<Object,Object>
injava.util
говорит: «Поскольку свойства наследуются от Hashtable, методы put и putAll можно применять к объекту Properties. Их использование настоятельно не рекомендуется, поскольку они позволяют вызывающей стороне вставлять записи, чьи ключи или значения не являются строками. " Композиция была бы намного чище.По мнению IBM, на самом деле это анти-паттерн. Эти классы типа typedef называются псевдо-типами.
Статья объясняет это намного лучше, чем я, но я постараюсь обобщить это в случае, если ссылка не работает:
WidgetCache
не может обработатьMap<String, Widget>
В статье предлагается следующий трюк, чтобы облегчить жизнь без использования псевдотипов:
Который работает из-за автоматического вывода типа.
(Я пришел к этому ответу через этот связанный вопрос переполнения стека)
источник
newHashMap
алмазного оператора сейчас не решена?WidgetCache
могло быть назначено переменной типаWidgetCache
илиMap<String,Widget>
без преобразования, но там был средством, с помощью которого статический методWidgetCache
мог сделать ссылкуMap<String,Widget>
и вернуть ее как типWidgetCache
после выполнения любой желаемой проверки. Такая функция может быть особенно полезна с обобщениями, которые не нужно стирать типом (так как ...Максимальное снижение производительности может быть ограничено поиском в vtable, который вы, скорее всего, уже понесли. Это не веская причина, чтобы противостоять этому.
Ситуация достаточно распространена, так что большинство всех статически типизированных языков программирования имеют специальный синтаксис для типов псевдонимов, обычно называемый a
typedef
. Java, вероятно, не копировала их, потому что изначально не имела параметризованных типов. Расширение класса не является идеальным из-за причин, которые Себастьян так хорошо описал в своем ответе, но это может быть разумным решением для ограниченного синтаксиса Java.У typedefs есть ряд преимуществ. Они выражают намерение программиста более четко, с лучшим названием, на более подходящем уровне абстракции. Их легче искать в целях отладки или рефакторинга. Рассмотрим нахождение везде
WidgetCache
используется против нахождения этих конкретных видов использованияMap
. Их легче изменить, например, если позже вы обнаружите, что вам нуженLinkedHashMap
вместо этого, или даже ваш собственный пользовательский контейнер.источник
Я предлагаю вам, как уже упоминалось, использовать композицию вместо наследования, чтобы вы могли предоставлять только те методы, которые действительно необходимы, с именами, которые соответствуют предполагаемому варианту использования. Действительно ли пользователям вашего класса нужно знать, что
WidgetCache
это за карта? И быть в состоянии сделать с этим все, что они хотят? Или им просто нужно знать, что это кеш для виджетов?Пример класса из моей кодовой базы, с решением аналогичной проблемы, которую вы описали:
Вы можете видеть, что внутренне это «просто карта», но публичный интерфейс не показывает это. И у него есть «дружественные к программисту» методы, такие как
appendMessage(Locale locale, String code, String message)
более простой и осмысленный способ вставки новых записей. А пользователи класса не могут этого сделать, напримерtranslations.clear()
, потому чтоTranslations
не расширяютсяMap
.При желании вы всегда можете делегировать некоторые из необходимых методов для внутренней карты.
источник
Я вижу это как пример значимой абстракции. У хорошей абстракции есть пара черт:
Он скрывает детали реализации, которые не имеют отношения к коду, его потребляющему.
Это настолько сложно, насколько это необходимо.
Расширяя, вы открываете весь интерфейс родительского элемента, но во многих случаях многое из этого может быть лучше скрыто, поэтому вы захотите сделать то, что предлагает Себастьян Редл, и предпочесть композицию, а не наследование, и добавить экземпляр родительского объекта как частный член вашего пользовательского класса. Любой из методов интерфейса, которые имеют смысл для вашей абстракции, может быть легко делегирован (в вашем случае) внутренней коллекции.
Что касается влияния на производительность, всегда полезно сначала оптимизировать код для удобства чтения, а если есть подозрения на влияние на производительность, профилировать код для сравнения двух реализаций.
источник
+1 к другим ответам здесь. Я также добавлю, что это действительно считается очень хорошей практикой в сообществе Domain Driven Design (DDD). Они утверждают, что ваш домен и взаимодействия с ним должны иметь смысловой домен, а не основную структуру данных. A
Map<String, Widget>
может быть кешем, но это может быть и что-то еще, что вы правильно сделали. В моем не столь скромном мнении (IMNSHO) нужно смоделировать то, что представляет коллекция, в данном случае кеш.Я добавлю важное изменение в том, что обертка класса домена вокруг базовой структуры данных, вероятно, должна также иметь другие переменные-члены или функции, которые действительно делают его классом домена с взаимодействиями, а не просто структурой данных (если бы только в Java были типы значений мы их получим на Java 10 - обещаю!)
Будет интересно посмотреть, как потоки Java 8 будут оказывать на все это, я могу представить, что, возможно, некоторые общедоступные интерфейсы предпочтут иметь дело с потоком (вставьте общий Java-примитив или String), а не с объектом Java.
источник