Эта тема сегодняшнего дня сосредоточена на гибкости. При определении параметров, возвращаемых типов или переменных рекомендуется определять их как интерфейс, реализуемый конкретными типами. Так что вместо чего-то вроде:
LinkedHashSetstringSet = new LinkedHashSet();
вместо этого мы должны реализовать что-то вроде:
SetstringSet = new LinkedHashSet();
Что позволяет нам писать наш код подобным образом, так это изменять реализацию наших переменных до тех пор, пока они соответствуют одним и тем же интерфейсам. Так что, возможно, вместо использования LinkedHashSet
, как определено в первом примере, мы могли бы изменить его на HashSet
без необходимости изменять какой-либо другой код. При этом нам действительно нужно знать, существуют ли атрибуты предыдущей реализации, на которые опирается код (например, гарантии упорядочения LinkedHashSet
vs HashSet
).
Каковы некоторые причины, по которым мы могли бы принять решение об изменении реализации? Некоторыми примерами могут быть:
- Повышение производительности
- Экономия памяти
- Желаемая дополнительная функциональность (например, гарантии заказа)
- и т.д.
Хотя в некоторых ситуациях может оказаться возможным изменить все объявления типов, если мы захотим изменить конкретный тип переменной, это будет гораздо более болезненно, и эти дополнительные затраты могут оказаться достаточным препятствием, чтобы вносить изменения не стоило.
Давайте рассмотрим некоторые из причин, по которым мы можем не захотеть следовать этому совету или быть в состоянии следовать этому совету.
Если типы, которые мы используем, не имеют никаких ожиданий альтернативных реализаций, таких как String
, BigInteger
и т.д. В этих случаях мы предпочитаем не использовать интерфейсы (на самом деле у нас нет возможности использовать интерфейс, поскольку его не существует).
Некоторые иерархии классов основаны не на интерфейсах, а на наследовании. В этих случаях альтернативой часто является использование базового класса для типа (часто абстрактного типа). Примером этого может быть ByteArrayOutputStream
к Выходной поток
.
Последняя идея заключалась бы в том, что, несмотря на то, что интерфейс существует, мы решили не использовать его из-за желания вызвать определенную функцию, доступную только для уникального конкретного типа, которого не существует в интерфейсе. Примером этого может быть Приоритетная очередь
интерфейс comparator
, который отсутствует в интерфейсе Queue
.
Даже помимо того, что основное внимание в этой главе уделяется использованию интерфейсов поверх конкретных классов, в этой главе действительно говорится о том, чтобы сосредоточиться на использовании наиболее универсального типа, который выполняет эту работу. Конечным универсальным типом будет тип Object
но этот тип часто не предоставляет нам метод, необходимый для выполнения нашей работы, поэтому мы продолжаем двигаться вниз по иерархии классов, пока не найдем что-то достаточно хорошее, чтобы его можно было использовать. Примером этого, с которым я сталкиваюсь довольно часто, является передача коллекции объектов методу и повторение по нему. Вместо того, чтобы принимать параметр типа List
например, я мог бы вместо этого взять Iterable
что оставляет мои варианты более открытыми. Гибкость – это сверхспособность при разработке. Мы должны искать возможности для повышения нашей гибкости везде, где мы можем, и это еще одна возможность для нас достичь этого.
Оригинал: “https://dev.to/kylec32/effective-java-refer-to-objects-by-their-interfaces-1278”