Главная страница

Scala. Профессиональное программирование 2022. Одерски Мартин, Спун Лекс, Веннерс Билл, Соммерс ФрэнкО41 Scala. Профессиональное программирование. 5е изд спб. Питер, 2022. 608 с. ил. Серия Библиотека программиста


Скачать 6.24 Mb.
НазваниеОдерски Мартин, Спун Лекс, Веннерс Билл, Соммерс ФрэнкО41 Scala. Профессиональное программирование. 5е изд спб. Питер, 2022. 608 с. ил. Серия Библиотека программиста
Дата27.04.2023
Размер6.24 Mb.
Формат файлаpdf
Имя файлаScala. Профессиональное программирование 2022.pdf
ТипДокументы
#1094967
страница27 из 64
1   ...   23   24   25   26   27   28   29   30   ...   64
ПРИМЕЧАНИЕ
Далее в разделе описываются подробности линеаризации . Вы можете про- пустить этот материал, если сейчас он вам неинтересен .
Главные свойства выполняющейся в Scala линеаризации показаны в следу­
ющем примере. Предположим, у вас есть класс
Cat
(Кот) — наследник класса
Animal
(Животное) — и два супертрейта:
Furry
(Пушистый) и
FourLegged
(Четырехлапый). Трейт
FourLegged расширяет еще один трейт —
HasLegs
(С лапами):
class Animal trait Furry extends Animal trait HasLegs extends Animal trait FourLegged extends HasLegs class Cat extends Animal, Furry, FourLegged
Иерархия наследования и линеаризация класса
Cat показаны на рис. 11.1.
Наследование изображено с помощью традиционной нотации UML [Rum04]: стрелки, на концах которых белые треугольники, обозначают наследование,

242 Глава 11 • Трейты
Рис. 11.1. Иерархия наследования и линеаризации класса Cat указывая на супертип. Стрелки с затемненными нетреугольными концами показывают линеаризацию. Они указывают направление, в котором будут разрешаться вызовы super
Линеаризация
Cat вычисляется от конца к началу следующим образом. По­
следняя часть линеаризации
Cat
— линеаризация его суперкласса
Animal
. Эта линеаризация копируется без каких­либо изменений. (Линеаризация каждого из этих типов показана в табл. 11.1.) Поскольку класс
Animal не расширяет явным образом какой­либо суперкласс и в него не примешаны никакие су­
пертрейты, то по умолчанию он расширяет класс
AnyRef
, который расширяет класс
Any
. Поэтому линеаризация класса
Animal имеет следующий вид:
Animal
→ AnyRef → Any
Предпоследней является линеаризация первой примеси, трейта
Furry
, но все классы, которые уже присутствуют в линеаризации класса
Animal
, теперь не учитываются, поэтому каждый класс в линеаризации
Cat появляется только раз. Результат выглядит следующим образом:
Furry
→ Animal → AnyRef → Any
Всему этому предшествует линеаризация
FourLegged
, в которой также не учитываются любые классы, которые уже были скопированы в линеариза­
циях суперкласса или первого примешанного трейта:
FourLegged
→ HasLegs → Furry → Animal → AnyRef → Any
И наконец, первым в линеаризации класса
Cat фигурирует сам этот класс:
Cat
→ FourLegged → HasLegs → Furry → Animal → AnyRef → Any

11 .5 . Параметры трейтов 243
Когда любой из этих классов и трейтов вызывает метод через вызов super
, вызываться будет первая реализация, которая в линеаризации расположена справа от него.
Таблица 11.1. Линеаризация типов в иерархии класса Cat
Тип
Линеаризация
Animal
Animal
,
AnyRef
,
Any
Furry
Furry
,
Animal
,
AnyRef
,
Any
FourLegged
FourLegged
,
HasLegs
,
Animal
,
AnyRef
,
Any
HasLegs
HasLegs
,
Animal
,
AnyRef
,
Any
Cat
Cat
,
FourLegged
,
HasLegs
,
Furry
,
Animal
,
AnyRef
,
Any
11 .5 . Параметры трейтов
Начиная со Scala 3, трейты могут принимать параметры значений. Вы определяете их так же, как и классы: помещая их список через запятую рядом с именем трейта. Например, вы можете передать философское высказывание в качестве параметра трейту
Philosophical
, показанному в листинге 11.10.
Листинг 11.10. Определение параметра трейта trait Philosophical(message: String):
def philosophize = message
Теперь, когда трейт
Philosophical принимает параметр, каждый подкласс должен передать свое собственное философское сообщение в качестве па­
раметра для трейта, например, таким образом:
class Frog extends Animal,
Philosophical("Я квакаю, значит, я существую!")
class Duck extends Animal,
Philosophical("Я крякаю, значит, я существую!")
Если сказать кратко, вы должны указать значение параметра трейта при определении класса, который примешивается к трейту. Философия каждого класса
Philosophical теперь будет определяться переданным параметром message
:

244 Глава 11 • Трейты val frog = new Frog frog.philosophize // Я квакаю, значит, я существую!
val duck = new Duck duck.philosophize // Я крякаю, значит, я существую!
Параметры трейта оцениваются непосредственно перед его инициализацией
1
и по умолчанию
2
доступны только его телу. Поэтому, чтобы использовать параметр сообщения в классе, реализующем трейт, необходимо захватить параметр, сделав его доступным из поля. Это поле гарантированно будет инициализировано и доступно для реализующего класса во время инициа­
лизации класса.
При использовании параметризованных трейтов вы можете заметить, что правила для параметров трейтов и классов немного отличаются. В обоих случаях вы можете выполнить инициализацию только один раз. Однако, хотя каждый класс может быть расширен только одним подклассом в иерархии, трейт может быть смешан несколькими подклассами. В этом случае вы долж­
ны его инициализировать при определении класса, который смешивается с трейтом, находящимся на самом высоком уровне в иерархии. Для примера рассмотрим суперкласс для любого «сознательного» животного, показанный в листинге 11.11.
Листинг 11.11. Указание параметра трейта class ProfoundAnimal extends Animal,
Philosophical("В начале было дело.")
Если суперкласс класса сам по себе не расширяет трейт, вы должны указать параметр трейта при определении класса. Суперклассом
ProfoundAnimal является
Animal
, а
Animal не расширяет
Philosophical
. Поэтому вы должны указать параметр трейта при определении
ProfoundAnimal
С другой стороны, если суперкласс класса также расширяет трейт, то вам больше не нужно указывать параметр трейта при определении класса. Это показано в листинге 11.12.
Листинг 11.12. Без указания параметра трейта class Frog extends ProfoundAnimal, Philosophical
1
Параметры трейта в Scala 3 заменяют ранние инициализаторы в Scala 2.
2
Как и в случае с параметрами класса, вы можете использовать параметрическое поле для определения общедоступного поля, инициализированного переданным параметром трейта. Это будет продемонстрировано в разделе 20.5.

Резюме 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


написать администратору сайта