Основы модели ООП в Scala | OTUS

Основы модели ООП в 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
}
}

Тогда появится возможность создавать «экземпляры» трейта посредством синтаксиса Foo.apply(1).

Так как этот код громоздкий, специально для метода apply разрешили не писать его название. Данная «магия» работает для всего, что называют apply в любой сущности. Вот, как это выглядит:

Foo(1)

То же самое мы можем делать и с классами. Также следует заметить, что семантика метода apply может существенно отличаться от вышеприведённого примера, ведь никто не ограничивает в том, что именно будет делать этот метод. Данная особенность активно применяется и в стандартных, и в сторонних библиотеках.

Материал написан на основании статьи Ивана Камышана"Основы функционального программирования с примерами на Scala — часть 2".

Не пропустите новые полезные статьи!

Спасибо за подписку!

Мы отправили вам письмо для подтверждения вашего email.
С уважением, OTUS!

Автор
1 комментарий
1

Краткость сестра таланта...

Для комментирования необходимо авторизоваться
Популярное
Сегодня тут пусто