Scala for Backend Engineering & Functional Programming · Урок

Умные конструкторы

Проверяемое создание

Урок 2 из 413 шагов

«Умные конструкторы» — бесплатный урок Scala for Backend Engineering & Functional Programming на CoddyKit. Это урок 2 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Scala for Backend Engineering & Functional Programming, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Scala for Backend Engineering & Functional Programming содержит 4 уроков всего.

Создание с проверкой

Умный конструктор — это фабрика, которая проверяет входные данные перед созданием значения. Он гарантирует, что любой имеющийся у Вас экземпляр удовлетворяет инвариантам типа.

  • Необработанный конструктор скрыт.
  • Публично доступно только создание с проверкой.
class Age private (val value: Int)
object Age:
  def of(v: Int): Option[Age] =
    if v >= 0 then Some(new Age(v)) else None

object Main:
  def main(args: Array[String]): Unit =
    println(Age.of(30).map(_.value))

Закрытый конструктор

Пометка основного конструктора как private не позволяет вызывающим обойти проверку. Сопутствующий объект становится единой точкой входа.

class Percentage private (val value: Int)
object Percentage:
  def of(v: Int): Option[Percentage] =
    if v >= 0 && v <= 100 then Some(new Percentage(v)) else None

object Main:
  def main(args: Array[String]): Unit =
    println(Percentage.of(50).map(_.value))
    println(Percentage.of(150))

Возврат Option

Возврат Option показывает, что создание может завершиться неудачей. Вызывающий код должен обработать None, поэтому недопустимые данные не проходят незаметно.

class NonEmpty private (val value: String)
object NonEmpty:
  def of(s: String): Option[NonEmpty] =
    if s.nonEmpty then Some(new NonEmpty(s)) else None

object Main:
  def main(args: Array[String]): Unit =
    println(NonEmpty.of("").map(_.value))
    println(NonEmpty.of("hi").map(_.value))

Возврат Either с подробностями ошибки

Если нужно объяснить, почему проверка не пройдена, возвращайте Either[Error, T]. Левая часть содержит описательное сообщение.

class Username private (val value: String)
object Username:
  def of(s: String): Either[String, Username] =
    if s.isEmpty then Left("empty")
    else if s.length > 10 then Left("too long")
    else Right(new Username(s))

object Main:
  def main(args: Array[String]): Unit =
    println(Username.of(""))
    println(Username.of("ada").map(_.value))

Умные конструкторы с case class

case class с закрытым конструктором также подходит. Учтите, что сгенерированные apply и copy тоже нужно контролировать, поэтому определите собственную фабрику и оставьте конструктор закрытым.

case class Email private (value: String)
object Email:
  def of(s: String): Option[Email] =
    if s.contains("@") then Some(Email(s)) else None

object Main:
  def main(args: Array[String]): Unit =
    println(Email.of("a@b.com"))
    println(Email.of("bad"))

Объединение проверенных значений

Поскольку умные конструкторы возвращают Option или Either, их можно объединять с помощью выражений for для создания более крупных проверенных объектов.

case class Name private (value: String)
object Name:
  def of(s: String): Option[Name] =
    if s.nonEmpty then Some(Name(s)) else None

case class Person(name: Name, age: Int)

object Main:
  def make(n: String): Option[Person] =
    for nm <- Name.of(n) yield Person(nm, 20)

  def main(args: Array[String]): Unit =
    println(Main.make("Bob"))

Нормализация входных данных

Умный конструктор также может нормализовать данные, например удалять пробелы по краям или переводить текст в нижний регистр, чтобы все экземпляры имели каноническую форму.

case class Tag private (value: String)
object Tag:
  def of(s: String): Option[Tag] =
    val clean = s.trim.toLowerCase
    if clean.nonEmpty then Some(Tag(clean)) else None

object Main:
  def main(args: Array[String]): Unit =
    println(Tag.of("  Scala  "))

Инварианты сохраняются навсегда

После того как значение прошло умный конструктор, его инвариант гарантирован на протяжении всего времени существования. Последующий код может доверять значению и не проверять его повторно.

case class PositiveInt private (value: Int)
object PositiveInt:
  def of(n: Int): Option[PositiveInt] =
    if n > 0 then Some(PositiveInt(n)) else None

object Main:
  def doubleIt(p: PositiveInt): Int = p.value * 2 // always positive

  def main(args: Array[String]): Unit =
    PositiveInt.of(5).foreach(p => println(Main.doubleIt(p)))

Сочетание с непрозрачными типами

Чтобы получить проверенные значения без затрат, сочетайте умный конструктор с непрозрачным типом. Объект-обёртка не выделяется, но проверка по-прежнему контролирует создание.

object Domain:
  opaque type Score = Int
  def of(n: Int): Option[Score] =
    if n >= 0 && n <= 100 then Some(n) else None
  extension (s: Score) def value: Int = s

object Main:
  def main(args: Array[String]): Unit =
    println(Domain.of(88).map(_.value))

Несколько правил проверки

Применяйте несколько проверок последовательно. Первое неудачное правило останавливает дальнейшую обработку и возвращает понятную ошибку.

object Password:
  def of(s: String): Either[String, String] =
    if s.length < 8 then Left("too short")
    else if !s.exists(_.isDigit) then Left("need a digit")
    else Right(s)

object Main:
  def main(args: Array[String]): Unit =
    println(Password.of("abc"))
    println(Password.of("abcdef12"))

Когда использовать умные конструкторы

Используйте их всегда, когда инварианты типа могут быть нарушены при необработанном создании.

  • Ограниченные числа, непустые строки, идентификаторы заданного формата.
  • Возвращайте Option для простого ответа да/нет и Either для указания причины.
  • Оставляйте необработанный конструктор закрытым.
case class Port private (value: Int)
object Port:
  def of(n: Int): Option[Port] =
    if n >= 1 && n <= 65535 then Some(Port(n)) else None

object Main:
  def main(args: Array[String]): Unit =
    println(Port.of(8080))
    println(Port.of(70000))

Быстрая проверка

Проверьте, насколько хорошо Вы поняли умные конструкторы.

Итоги

Вы изучили умные конструкторы.

  • Проверяйте входные данные в фабрике сопутствующего объекта.
  • Сделайте необработанный конструктор закрытым.
  • Возвращайте Option или Either, чтобы заставить обработать ошибку.
  • При необходимости нормализуйте входные данные до канонической формы.
  • После этого инварианты сохраняются на протяжении всего времени существования значения.
case class Even private (value: Int)
object Even:
  def of(n: Int): Option[Even] =
    if n % 2 == 0 then Some(Even(n)) else None

object Main:
  def main(args: Array[String]): Unit =
    println(Even.of(4))
    println(Even.of(5))
Можно начать бесплатно

Изучай Scala с ИИ-репетитором — бесплатно

Пиши и запускай код прямо в браузере, получай мгновенную помощь от ИИ-репетитора 24/7 и продолжи учиться на сайте или в приложении.

Курсы
39
Уроки
143

Часто задаваемые вопросы

Урок «Умные конструкторы» бесплатный?

Да — полный текст урока «Умные конструкторы» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Scala for Backend Engineering & Functional Programming, подпишись на CoddyKit PRO. Курс Scala for Backend Engineering & Functional Programming содержит 4 уроков всего.

Чему я научусь в уроке «Умные конструкторы»?

Проверяемое создание Ты практикуешь Scala for Backend Engineering & Functional Programming с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать Scala for Backend Engineering & Functional Programming?

Предыдущий опыт не требуется. Scala for Backend Engineering & Functional Programming на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 4.

Сколько времени занимает урок «Умные конструкторы»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке Scala for Backend Engineering & Functional Programming?

Да. Каждый урок Scala for Backend Engineering & Functional Programming включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Моделирование с помощью ADT
  2. Умные конструкторы
  3. Newtype-типы
  4. Составные предметные области
← Назад к Scala for Backend Engineering & Functional Programming