Файл: Одерски Мартин, Спун Лекс, Веннерс Билл, Соммерс ФрэнкО41 Scala. Профессиональное программирование. 5е изд спб. Питер, 2022. 608 с. ил. Серия Библиотека программиста.pdf
ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 09.12.2023
Просмотров: 765
Скачиваний: 11
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
СОДЕРЖАНИЕ
Резюме 245
Суперкласс
Frog
(Лягушка)
ProfoundAnimal расширяет трейт
Philosophical и предоставляет свой параметр сообщения message
. При определении
Frog вы больше не можете указывать сообщение в качестве параметра, посколь
ку он уже был заполнен
ProfoundAnimal
. Таким образом, эта лягушка будет демонстрировать поведение, вытекающее в результате инициализации
ProfoundAnimal в
Philosophical
:
val frog = new Frog frog.philosophize // В начале было дело.
И наконец, трейты не могут передавать параметры своим родительским трейтам. Например, рассмотрим трейт
PhilosophicalAnimal
, который рас
ширяет трейт
Philosophical
:
trait PhilosophicalAnimal extends Animal with Philosophical
Вам может показаться, что думающая лягушка определяется следующим образом:
// Не компилируется class Frog extends PhilosophicalAnimal(
"Я квакаю, значит, я существую!")
Однако это не работает. Вместо этого вы должны явно указать сообщение для
Philosophical при определении класса
Frog
, например, так:
class Frog extends
Philosophical("Я квакаю, значит, я существую!"),
PhilosophicalAnimal
Или так:
class Frog extends PhilosophicalAnimal,
Philosophical("Я квакаю, значит, я существую!")
Резюме
В этой главе мы показали работу трейтов и порядок их применения в не
скольких часто встречающихся идиомах. Вы увидели, что трейты похожи на множественное наследование. Но благодаря тому, что в трейтах вызовы super интерпретируются с помощью линеаризации, удается не только избавиться от некоторых трудностей традиционного множественного наследования, но и воспользоваться наращиванием модификаций поведения программы.
246 Глава 11 • Трейты
Вдобавок мы рассмотрели трейт
Ordered и изучили порядок создания соб
ственных расширяющих трейтов.
А теперь, усвоив все эти аспекты, нам предстоит вернуться немного назад и посмотреть на трейты в целом с другого ракурса. Трейты не просто под
держивают средства выражения, рассмотренные в данной главе, — они явля
ются базовыми блоками кода, допускающими их повторное использование с помощью механизма наследования. Благодаря этому многие опытные программисты, которые работают на Scala, на ранних стадиях реализации начинают с трейтов. Любой трейт не может охватить всю концепцию, огра
ничиваясь лишь ее фрагментом. По мере того как конструкция приобретает все более четкие очертания, фрагменты путем примешивания трейтов могут объединяться в более полноценные концепции.
12
Пакеты, импорты и экспорты
В ходе работы над программой, особенно объемной, важно свести к ми
нимуму сцепление, то есть степень взаимозависимости различных частей программы. Низкое сцепление (low coupling) сокращает риск того, что не
значительное, казалось бы, безобидное изменение в одной части программы вызовет разрушительные последствия для другой части. Один из способов сведения сцепления к минимуму — написание программы в модульном стиле. Программа разбивается на несколько меньших по размеру модулей, у каждого из которых есть внутренняя и внешняя части. При работе над внутренней частью модуля, то есть над его реализацией, нужно согласовывать действия только с другими программистами, работающими над созданием того же самого модуля. И лишь в случае необходимости внести изменения в его внешнюю часть, то есть в интерфейс, приходится координировать свои действия с разработчиками других модулей.
В этой главе мы покажем конструкции, помогающие программировать в мо
дульном стиле. Мы рассмотрим вопросы помещения кода в пакеты, создания имен, видимых при импортировании, и управления видимостью определений с помощью модификаторов доступа. Эти конструкции сходны по духу с кон
струкциями, имеющимися в Java, за исключением некоторых различий, ко
торые, как правило, выражаются в их более последовательном характере. По
этому данную главу стоит прочитать даже тем, кто хорошо разбирается в Java.
12 .1 . Помещение кода в пакеты
Код Scala размещается в глобальной иерархии пакетов платформы Java. Все показанные до сих пор в этой книге примеры кода размещались в безымян-
ных пакетах. Поместить код в именованные пакеты в Scala можно двумя
248 Глава 12 • Пакеты, импорты и экспорты способами. Первый — поместить содержимое всего файла в пакет, указав директиву package в самом начале файла (листинг 12.1).
Листинг 12.1. Помещение в пакет всего содержимого файла package bobsrockets.navigation class Navigator
Указание директивы package в листинге 12.1 приводит к тому, что класс
Navi- gator помещается в пакет по имени bobsrockets.navigation
. Повидимому, это программные средства навигации, разработанные корпорацией Bob’s
Rockets.
ПРИМЕЧАНИЕ
Поскольку код Scala — часть экосистемы Java, то тем пакетам Scala, которые выпускаются открытыми, рекомендуется соответствовать принятому в Java соглашению по присваиванию имени с обратным порядком следования доменных имен . Поэтому наиболее подходящим именем пакета для класса
Navigator может быть com .bobsrockets .navigation . Но в данной главе мы от- бросим com ., чтобы было легче разобраться в примерах .
Другой способ, позволяющий в Scala помещать код в пакеты, больше по
хож на использование пространств имен C#. За пакетом следует двоеточие и раздел кода с отступом, содержащий определения, которые входят в пакет.
Такой синтаксис называется пакетированием. Показанное в листинге 12.2 пакетирование имеет тот же эффект, что и код в листинге 12.1.
Листинг 12.2. Длинная форма простого объявления пакетирования package bobsrockets.navigation:
class Navigator
Кроме того, для таких простых примеров можно воспользоваться «синта
ксическим сахаром», показанным в листинге 12.1. Но лучше все же реализо
вать один из вариантов более универсальной системы записи, позволяющей разместить разные части файла в разных пакетах. Например, если вы хотите отправить по электронной почте или опубликовать на дискуссионном фо
руме фрагмент кода Scala, включающий несколько пакетов, вы можете ис
пользовать пакетирование, как показано в листинге 12.3.
Листинг 12.3. Несколько пакетов в одном и том же файле package bobsrockets:
package navigation:
// в пакете bobsrockets.navigation class Navigator
12 .2 . Краткая форма доступа к родственному коду 249
package launch:
// в пакете bobsrockets.navigation.launch class Booster
12 .2 . Краткая форма доступа к родственному коду
Представление кода в виде иерархии пакетов не только помогает просма
тривать код, но и сообщает компилятору, что части кода в одном и том же пакете както связаны между собой. В Scala эта родственность позволяет при доступе к коду, находящемуся в одном и том же пакете, применять краткие имена.
В листинге 12.4 приведено три примера. В первом, согласно ожиданиям, к классу можно обращаться из его собственного пакета, не указывая пре
фикс. Именно поэтому new
StarMap проходит компиляцию. Класс
StarMap находится в том же самом пакете по имени bobsrockets.navigation
, что и выражение new
, которое к нему обращается, поэтому указывать префикс в виде имени пакета не нужно.
Листинг 12.4. Краткая форма обращения к классам и пакетам package bobsrockets:
package navigation:
class Navigator:
// Указывать bobsrockets.navigation.StarMap не нужно val map = new StarMap class StarMap class Ship:
// Указывать bobsrockets.navigation.Navigator не нужно val nav = new navigation.Navigator package fleets:
class Fleet:
// Указывать bobsrockets.Ship не нужно def addShip = new Ship
Во втором примере обращение к самому пакету может производиться из того же пакета, в котором он находится, без указания префикса. В листинге 12.4 показано, как создается экземпляр класса
Navigator
. Выражение new появ
ляется в пакете bobsrockets
, который, в свою очередь, содержится в пакете bobsrockets.navigation
. Поэтому обращение к последнему можно указывать просто как navigation
250 Глава 12 • Пакеты, импорты и экспорты
В третьем примере показано, что при использовании синтаксиса вложенного пакетирования все имена, доступные в пространстве имен вне пакета, до
ступны также и внутри него. Это обстоятельство позволяет в листинге 12.4 в addShip()
создать новый экземпляр класса, воспользовавшись выражени
ем new
Ship
. Метод определен внутри двух пакетов: внешнего bobsrockets и внутреннего bobsrockets.fleets
. Поскольку к объекту
Ship доступ можно получить во внешнем пакете, то из addShip()
можно воспользоваться ссыл
кой на него.
Следует заметить, что такая форма доступа применима только в том случае, если пакеты вложены друг в друга явным образом. Если в каждый файл будет помещаться лишь один пакет, то, как и в Java, доступны будут толь
ко те имена, которые определены в текущем пакете. В листинге 12.5 пакет bobsrockets.fleets был перемещен на самый верхний уровень. Он больше не заключен в пакет bobsrockets
, поэтому имена из bobsrockets в его про
странстве имен отсутствуют. В результате использование выражения new
Ship вызовет ошибку компиляции.
Листинг 12.5. Обозначения, заключенные в пакеты, не доступны автоматически package bobsrockets:
class Ship package bobsrockets.fleets:
class Fleet:
// Не пройдет компиляцию! Ship вне области видимости.
def addShip = new Ship
Если обозначение вложенности пакетов с отступами приводит к неудобному для вас сдвигу кода вправо, то можно воспользоваться несколькими указа
ниями директивы package без отступа
1
. Например, в показанном далее коде класс
Fleet определяется в двух вложенных пакетах, bobsrockets и fleets
, точно так же, как это было сделано в листинге 12.4:
package bobsrockets package fleets class Fleet:
// Указывать bobsrockets.Ship не нужно def addShip = new Ship
1
Этот стиль, в котором используются несколько директив package без фигурных скобок, называется объявлением цепочки пакетов.
12 .2 . Краткая форма доступа к родственному коду 251
Важно знать еще об одной, последней особенности. Иногда в области ви
димости оказывается слишком много всего и имена пакетов скрывают друг друга. В листинге 12.6 пространство имен класса
MissionControl включа
ет три отдельных пакета по имени launch
! Один пакет launch находится в bobsrockets.navigation
, один — в bobsrockets и еще один — на верхнем уровне. А как тогда ссылаться на
Booster1
,
Booster2
и
Booster3
?
Листинг 12.6. Доступ к скрытым именам пакетов
// в файле launch.scala package launch:
class Booster3
// в файле bobsrockets.scala package bobsrockets:
package launch:
class Booster2
package navigation:
package launch:
class Booster1
class MissionControl:
val booster1 = new launch.Booster1
val booster2 = new bobsrockets.launch.Booster2
val booster3 = new _root_.launch.Booster3
Проще всего обратиться к первому из них. Ссылка на само имя launch при
ведет вас к пакету bobsrockets.navigation.launch
, поскольку этот пакет launch определен в ближайшей области видимости. Поэтому к первому из booster
классов можно обратиться просто как к launch.Booster1
. Ссылка на второй подобный класс также указывается без какихлибо особенных приемов. Можно указать bobsrockets.launch.Booster2
и не оставить ни малейших сомнений о том, к какому из трех классов происходит обраще
ние. Открытым остается лишь вопрос по поводу третьего класса booster
: как обратиться к
Booster3
при условии, что пакет на самом верхнем уровне перекрывается вложенными пакетами?
Чтобы помочь справиться с подобной ситуацией, Scala предоставляет имя пакета
_root_
, являющегося внешним по отношению к любым другим соз
даваемым пользователем пакетам. Иначе говоря, каждый пакет верхнего уровня, который может быть создан, рассматривается как члены пакета
_root_
. Например, и launch
, и bobsrockets в листинге 12.6 являются членами пакета
_root_
. В результате этого
_root_.launch позволяет обратиться к па
кету launch самого верхнего уровня, а
_root_.launch.Booster3
обозначает внешний класс booster
1 ... 23 24 25 26 27 28 29 30 ... 64
252 Глава 12 • Пакеты, импорты и экспорты
12 .3 . Импортирование кода
В Scala пакеты и их члены могут импортироваться с использованием дирек
тивы import
. Затем ко всему, что было импортировано, можно получить до
ступ, указав простое имя, такое как
File
, не используя такое развернутое имя, как java.io.File
. Рассмотрим, к примеру, код, показанный в листинге 12.7.
Листинг 12.7. Превосходные фрукты от Боба, готовые к импорту package bobsdelights abstract class Fruit(
val name: String,
val color: String
)
object Fruits:
object Apple extends Fruit("apple", "red")
object Orange extends Fruit("orange", "orange")
object Pear extends Fruit("pear", "yellowish")
val menu = List(Apple, Orange, Pear)
Указание директивы import делает члены пакета или объект доступными по их именам, исключая необходимость ставить перед ними префикс с именем пакета или объекта. Рассмотрим ряд простых примеров:
// простая форма доступа к Fruit import bobsdelights.Fruit
// простая форма доступа ко всем членам bobsdelights import bobsdelights.*
// простая форма доступа ко всем членам Fruits import bobsdelights.Fruits.*
Первый пример относится к импортированию отдельно взятого Javaтипа, а во втором показан Javaимпорт до востребования (ondemand). Если в Sca
la 2 импорты до востребования записывались с замыкающим знаком подчер
кивания (
_
), то в Scala 3 он был заменен знаком звездочки (
*
), чтобы соот
вествовать другим языкам. Третья из показанных директив import относится к Javaимпорту статических полей класса.
Эти три директивы import дают представление о том, что можно делать с помощью импортирования, но в Scala импортирование носит более уни
версальный характер. В частности, оно может быть где угодно, а не только в начале компилируемого модуля. К тому же при импортировании можно
12 .3 . Импортирование кода 253
ссылаться на произвольные значения. Например, возможен импорт, пока
занный в листинге 12.8.
Листинг 12.8. Импортирование членов обычного объекта (не одиночки)
def showFruit(fruit: Fruit) =
import fruit.*
s"${name}s are $color"
Метод showFruit импортирует все члены параметра fruit
, относящего
ся к типу
Fruit
. Следующая инструкция println может непосредственно ссылать ся на name и color
. Эти две ссылки — эквиваленты ссылок fruit.name и fruit.color
. Такой синтаксис пригодится, в частности, при использовании объектов в качестве модулей. Соответствующее описание будет дано в главе 7.
Гибкость импортирования в Scala
Директива import работает в Scala намного более гибко, чем в Java.
Эта гибкость характеризуется тремя принципиальными отличиями.
Импорт кода в Scala:
• может появляться где угодно;
• позволяет, помимо пакетов, ссылаться на объекты (одиночки или обычные);
• позволяет изменять имена или скрывать некоторые из импорти
рованных членов.
Еще один из факторов гибкости импорта кода в Scala заключается в воз
можности импортировать пакеты как таковые, без их непакетированного наполнения. Смысл в этом будет только в том случае, если предполагается, что в пакете заключены другие пакеты. Например, в листинге 12.9 импорти
руется пакет java.util.regex
. Благодаря этому regex можно использовать с указанием его простого имени. Чтобы обратиться к объектуодиночке
Pattern из пакета java.util.regex
, можно, как показано в данном листинге, просто воспользоваться идентификатором regex.Pattern
Листинг 12.9. Импортирование имени пакета import java.util.regex class AStarB:
// обращение к java.util.regex.Pattern val pat = regex.Pattern.compile("a*b")