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