Scala поддерживает как функциональное программирование (ФП), так и объектно-ориентированное программирование (ООП), а также слияние этих двух парадигм. В этом разделе представлен краткий обзор моделирования данных в ООП и ФП.
Моделирование данных в ООП
При написании кода в стиле ООП двумя вашими основными инструментами для инкапсуляции данных будут трейты и классы.
Трейты
Трейты Scala можно использовать как простые интерфейсы, но они также могут содержать абстрактные и конкретные методы и поля, а также параметры, как и классы. Они предоставляют вам отличный способ организовать поведение в небольшие модульные блоки. Позже, когда вы захотите создать конкретные реализации атрибутов и поведения, классы и объекты могут расширять трейты, смешивая столько трейтов, сколько необходимо для достижения желаемого поведения.
В качестве примера того, как использовать трейты в качестве интерфейсов, вот три трейта, которые определяют хорошо организованное и модульное поведение для животных, таких как собаки и кошки:
trait Speaker {
  def speak(): String  // тело метода отсутствует, поэтому метод абстрактный
}
trait TailWagger {
  def startTail(): Unit = println("tail is wagging")
  def stopTail(): Unit = println("tail is stopped")
}
trait Runner {
  def startRunning(): Unit = println("I’m running")
  def stopRunning(): Unit = println("Stopped running")
}
trait Speaker:
  def speak(): String  // тело метода отсутствует, поэтому метод абстрактный
trait TailWagger:
  def startTail(): Unit = println("tail is wagging")
  def stopTail(): Unit = println("tail is stopped")
trait Runner:
  def startRunning(): Unit = println("I’m running")
  def stopRunning(): Unit = println("Stopped running")
Учитывая эти трейты, вот класс Dog, который их все расширяет, 
обеспечивая при этом поведение для абстрактного метода speak:
class Dog(name: String) extends Speaker with TailWagger with Runner {
  def speak(): String = "Woof!"
}
class Dog(name: String) extends Speaker, TailWagger, Runner:
  def speak(): String = "Woof!"
Обратите внимание, как класс расширяет трейты с помощью ключевого слова extends.
Точно так же вот класс Cat, реализующий те же трейты, 
а также переопределяющий два конкретных метода, которые он наследует:
class Cat(name: String) extends Speaker with TailWagger with Runner {
  def speak(): String = "Meow"
  override def startRunning(): Unit = println("Yeah ... I don’t run")
  override def stopRunning(): Unit = println("No need to stop")
}
class Cat(name: String) extends Speaker, TailWagger, Runner:
  def speak(): String = "Meow"
  override def startRunning(): Unit = println("Yeah ... I don’t run")
  override def stopRunning(): Unit = println("No need to stop")
Примеры ниже показывают, как используются эти классы:
val d = new Dog("Rover")
println(d.speak())      // печатает "Woof!"
val c = new Cat("Morris")
println(c.speak())      // "Meow"
c.startRunning()        // "Yeah ... I don’t run"
c.stopRunning()         // "No need to stop"
val d = Dog("Rover")
println(d.speak())      // печатает "Woof!"
val c = Cat("Morris")
println(c.speak())      // "Meow"
c.startRunning()        // "Yeah ... I don’t run"
c.stopRunning()         // "No need to stop"
Если этот код имеет смысл — отлично, вам удобно использовать трейты в качестве интерфейсов. Если нет, не волнуйтесь, они более подробно описаны в главе “Моделирование предметной области”.
Классы
Классы Scala используются в программировании в стиле ООП. 
Вот пример класса, который моделирует “человека”. 
В ООП поля обычно изменяемы, поэтому оба, firstName и lastName объявлены как var параметры:
class Person(var firstName: String, var lastName: String) {
  def printFullName() = println(s"$firstName $lastName")
}
val p = new Person("John", "Stephens")
println(p.firstName)   // "John"
p.lastName = "Legend"
p.printFullName()      // "John Legend"
class Person(var firstName: String, var lastName: String):
  def printFullName() = println(s"$firstName $lastName")
val p = Person("John", "Stephens")
println(p.firstName)   // "John"
p.lastName = "Legend"
p.printFullName()      // "John Legend"
Обратите внимание, что объявление класса создает конструктор:
// код использует конструктор из объявления класса
val p = new Person("John", "Stephens")
// код использует конструктор из объявления класса
val p = Person("John", "Stephens")
Конструкторы и другие темы, связанные с классами, рассматриваются в главе “Моделирование предметной области”.
Моделирование данных в ФП
При написании кода в стиле ФП вы будете использовать следующие понятия:
- Алгебраические типы данных для определения данных.
- Трейты для функциональности данных.
Перечисления и суммированные типы
Суммированные типы (sum types) — это один из способов моделирования алгебраических типов данных (ADT) в Scala.
Они используются, когда данные могут быть представлены с различными вариантами.
Например, у пиццы есть три основных атрибута:
- Размер корки
- Тип корки
- Начинки
- Они кратко смоделированы с помощью перечислений, которые представляют собой суммированные типы, содержащие только одноэлементные значения:
В Scala 2 sealed классы и case object объединяются для определения перечисления:
sealed abstract class CrustSize
object CrustSize {
  case object Small extends CrustSize
  case object Medium extends CrustSize
  case object Large extends CrustSize
}
sealed abstract class CrustType
object CrustType {
  case object Thin extends CrustType
  case object Thick extends CrustType
  case object Regular extends CrustType
}
sealed abstract class Topping
object Topping {
  case object Cheese extends Topping
  case object Pepperoni extends Topping
  case object BlackOlives extends Topping
  case object GreenOlives extends Topping
  case object Onions extends Topping
}
Scala 3 предлагает конструкцию enum для определения перечислений:
enum CrustSize:
  case Small, Medium, Large
enum CrustType:
  case Thin, Thick, Regular
enum Topping:
  case Cheese, Pepperoni, BlackOlives, GreenOlives, Onions
Когда у вас есть перечисление, вы можете импортировать его элементы как обычные значения:
import CrustSize._
val currentCrustSize = Small
// перечисления в сопоставлении с шаблоном
currentCrustSize match {
  case Small => println("Small crust size")
  case Medium => println("Medium crust size")
  case Large => println("Large crust size")
}
// перечисления в операторе `if`
if (currentCrustSize == Small) println("Small crust size")
import CrustSize.*
val currentCrustSize = Small
// перечисления в сопоставлении с шаблоном
currentCrustSize match
  case Small => println("Small crust size")
  case Medium => println("Medium crust size")
  case Large => println("Large crust size")
// перечисления в операторе `if`
if currentCrustSize == Small then println("Small crust size")
Вот еще один пример того, как создать суммированные типы с помощью Scala, 
это не будет называться перечислением, потому что у случая Succ есть параметры:
sealed abstract class Nat
object Nat {
  case object Zero extends Nat
  case class Succ(pred: Nat) extends Nat
}
Суммированные типы подробно рассматриваются в разделе “Моделирование предметной области” этой книги.
enum Nat:
  case Zero
  case Succ(pred: Nat)
Перечисления подробно рассматриваются в разделе “Моделирование предметной области” этой книги и в справочной документации.
Продуктовые типы
Тип продукта — это алгебраический тип данных (ADT), который имеет только одну форму, 
например, одноэлементный объект, представленный в Scala case object; 
или неизменяемая структура с доступными полями, представленная case class.
case class обладает всеми функциями класса, а также содержит встроенные дополнительные функции, 
которые делают его полезным для функционального программирования. 
Когда компилятор видит ключевое слово case перед class, то применяет следующие эффекты и преимущества:
- Параметры конструктора case classпо умолчанию являются общедоступными полямиval, поэтому поля неизменяемы, а методы доступа генерируются для каждого параметра.
- Генерируется метод unapply, который позволяет использоватьcase classв выражениях match различными способами.
- В классе создается метод copy. Он позволяет создавать копии объекта без изменения исходного.
- Создаются методы equalsиhashCodeдля реализации структурного равенства.
- Генерируется метод по умолчанию toString, полезный для отладки.
Вы можете вручную добавить все эти методы в класс самостоятельно, но, поскольку эти функции так часто используются в функциональном программировании, использование case класса гораздо удобнее.
Этот код демонстрирует несколько функций case class:
// определение case class
case class Person(
  name: String,
  vocation: String
)
// создание экземпляра case class
val p = Person("Reginald Kenneth Dwight", "Singer")
// полезный метод toString
p                // : Person = Person(Reginald Kenneth Dwight,Singer)
// можно получить доступ к неизменяемым полям
p.name           // "Reginald Kenneth Dwight"
p.name = "Joe"   // error: can’t reassign a val field
// при необходимости внести изменения используйте метод `copy`
// для “update as you copy”
val p2 = p.copy(name = "Elton John")
p2               // : Person = Person(Elton John,Singer)
Дополнительные сведения о case классах см. в разделах “Моделирование предметной области”.
Contributors to this page:
Contents
- Введение
- Возможности Scala
- Почему Scala 3?
- Почувствуй Scala
- Пример 'Hello, World!'
- REPL
- Переменные и типы данных
- Структуры управления
- Моделирование данных
- Методы
- Функции первого класса
- Одноэлементные объекты
- Коллекции
- Контекстные абстракции
- Верхнеуровневые определения
- Обзор
- Первый взгляд на типы
- Интерполяция строк
- Структуры управления
- Моделирование предметной области
- Инструменты
- Моделирование ООП
- Моделирование ФП
- Методы
- Особенности методов
- Main методы в Scala 3
- Обзор
- Функции
- Анонимные функции
- Параметры функции
- Eta расширение
- Функции высшего порядка
- Собственный map
- Создание метода, возвращающего функцию
- Обзор
- Пакеты и импорт
- Коллекции в Scala
- Типы коллекций
- Методы в коллекциях
- Обзор
- Функциональное программирование
- Что такое функциональное программирование?
- Неизменяемые значения
- Чистые функции
- Функции — это значения
- Функциональная обработка ошибок
- Обзор
- Типы и система типов
- Определение типов
- Параметризованные типы
- Пересечение типов
- Объединение типов
- Алгебраические типы данных
- Вариантность
- Непрозрачные типы
- Структурные типы
- Зависимые типы функций
- Другие типы
- Контекстные абстракции
- Методы расширения
- Параметры контекста
- Контекстные границы
- Given импорты
- Классы типов
- Многостороннее равенство
- Неявное преобразование типов
- Обзор
- Параллелизм
- Scala утилиты
- Сборка и тестирование проектов Scala с помощью Sbt
- Рабочие листы
- Взаимодействие с Java