Основы модели ООП в Scala
Scala имеет гибкую ООП-модель, которая состоит из трейтов, обжектов, классов и кейс-классов, то есть модель чем-то напоминает аналогичную в Java. Вот, например, объявления класса:
class ClassName(field1: Type1, field2: Type2, … , fieldN: TypeN) { // class body }
Объявленный таким образом класс инстанцируется посредством конструкции new ClassName(field1, field2, … , fieldN). Параметры, которые вводятся в скобках после имени класса, — private-поля класса, но мы можем изменить область их видимости. К примеру, если нам надо обеспечить доступ на чтение извне, то перед именем поля следует поставить модификатор val. Если надо обеспечить запись и чтение — модификатор var. Классы в Scala являются ссылочными типами и наследуют базовый класс AnyRef (неявно). Таким образом, присваивание объекта с мутирующими (var) полями другой переменной лишь копирует ссылку.
Внутри класса мы можем вводить многие вложенные структуры, необходимые для внутреннего использования. При этом по умолчанию все переменные, методы и постоянные, которые объявлены в теле класса, являются публичными. Мы можем их защитить посредством модификаторов private и protected. Что касается вложенных классов, то они извне недоступны.
Вторая ООП-сущность — trait. Trait, в отличие от класса, не может иметь конструктора, следовательно, не может быть инстанцирован. Вдобавок к этому, он способен содержать абстрактные методы, тогда как класс — лишь в том случае, если помечен модификатором abstract.
Рассмотрим синтаксис трейта:
trait TraitName { }
Существует ещё одна ООП-сущность, которую мы рассматривали в этой статье. Речь идёт об object. Его отличительная черта — наличие единственного экземпляра, создаваемого автоматически, то есть он является синглтоном. Лишь object может быть точкой входа в программу. Вдобавок к этому, можно импортировать его вложенные типы, значения и методы в область видимости другого файла. Всё это даёт возможность применять object в качестве средства структурной организации программы, что в свою очередь даёт возможность группировать статические методы и константы.
Особенности наследования в Scala
Особенностей несколько: во-первых, почти всё можно наследовать от нескольких trait посредством синтаксиса extends trait1 with trait2 with trait3 ... with traitN.
object HelloWorld extends App
В нашем случае App является предопределённым трейтом, оборачивающим содержимое внутрь метода main.
Зачем нам нужен синтаксис extends … with … with …? Может, мы можем обойтись одним ключевым словом? К сожалению, не можем. Если же мы разрешим множественное наследование, то столкнёмся с проблемой ромбовидного наследования. В языке программирования Scala данная проблема решается путём определения главного трейта, реализация которого и наследуется. Речь идёт о трейте, который идёт первым после extends (кстати, это может быть и не трейт вовсе, а класс либо абстрактный класс).
Также мы не можем наследовать типы от объекта.
По правде говоря, у нас есть возможность создать анонимный класс-наследник трейта и реализовать все его абстрактные методы с помощью функционала анонимных классов, а потом создать его экземпляр:
trait T {} val t = new T { // имплементация абстрактных методов }
Однако писать данный код бывает весьма громоздким занятием. Для этого в Scala есть companion object — объекты-компаньоны. Для них не существует специального ключевого слова, поэтому для их определения надо создать в файле с классом либо трейтом object с тем же именем, что и у класса. У такого обжекта-компаньона вы сможете определить метод apply:
trait Foo { def bar: Int } object Foo { def apply(int: Int) = new Foo { override def bar = int } }
Тогда появится возможность создавать «экземпляры» трейта посредством синтаксиса
Так как этот код громоздкий, специально для метода apply разрешили не писать его название. Данная «магия» работает для всего, что называют apply в любой сущности. Вот, как это выглядит:
Foo(1)
То же самое мы можем делать и с классами. Также следует заметить, что семантика метода apply может существенно отличаться от вышеприведённого примера, ведь никто не ограничивает в том, что именно будет делать этот метод. Данная особенность активно применяется и в стандартных, и в сторонних библиотеках.
Материал написан на основании статьи Ивана Камышана"Основы функционального программирования с примерами на Scala — часть 2".