Объекты-одиночки и companion-объекты
В Scala нет статических членов — вместо них есть нечто более чистое: объекты-одиночки.
«Один объект на всю программу — это не ограничение, а гарантия: общее состояние живёт в одном месте.»
Иногда нужен не шаблон для множества объектов, а ровно один экземпляр — например, для утилит или конфигурации. В Scala его создают словом object. Такой объект существует в единственном числе и создаётся лениво при первом обращении.
object MathUtils:
val pi = 3.14159
def square(x: Int): Int = x * x
println(MathUtils.pi) // 3.14159
println(MathUtils.square(5)) // 25Это замена статическим методам и полям из Java: вместо static вы просто кладёте всё в object.
Companion-объект
Если object назван так же, как класс, и лежит в том же файле, он становится companion-объектом (компаньоном). Класс и его компаньон видят приватные члены друг друга. Это идеальное место для фабричных методов.
class Circle(val radius: Double):
def area: Double = math.Pi * radius * radius
object Circle:
def fromDiameter(d: Double): Circle =
Circle(d / 2) // фабричный метод
val c = Circle.fromDiameter(10)
println(c.radius) // 5.0Метод apply — магия вызова
Если в объекте определить метод apply, его можно вызывать, просто написав имя объекта со скобками. Так Scala создаёт «конструкторы-фабрики».
object User:
def apply(name: String): String = s"Пользователь: $name"
println(User("Аня")) // вызов apply -> Пользователь: АняТа же идея на Python ▶
# В Python роль object играет модуль или класс со staticmethod
import math
class MathUtils:
pi = 3.14159
@staticmethod
def square(x):
return x * x
print(MathUtils.pi)
print(MathUtils.square(5))
# Аналог apply — метод __call__ на экземпляре-одиночке
class UserFactory:
def __call__(self, name):
return f"Пользователь: {name}"
User = UserFactory()
print(User("Аня")) # вызов как функцииclass Circle ... (много экземпляров)
object Circle ... (ОДИН экземпляр-компаньон)
|
видит приватное класса
хранит фабрики: Circle.fromDiameter(...)Как работает под капотом (JVM)
JVM не знает про объекты-одиночки. Scala реализует object как класс с приватным конструктором и единственным статическим экземпляром в поле MODULE$. Companion-объект и класс компилируются в два отдельных .class-файла (Circle.class и Circle$.class), которые специально получают доступ к приватным членам друг друга. Метод apply — обычный метод; компилятор просто разрешает звать его без имени apply.
Частые ошибки
- Искать
static. В Scala его нет — используйтеobject. - Класть companion в другой файл. Он должен быть в том же файле, что и класс, иначе не получит доступ к приватным членам.
- Хранить изменяемое состояние в
object. Один экземпляр на всю программу — это глобальное состояние, источник багов в многопоточности.
Best practices
- Используйте
objectдля утилит, констант и фабрик. - Размещайте фабричные методы (
apply,fromXxx) в companion-объекте. - Избегайте изменяемого состояния в одиночках, чтобы не плодить глобальные переменные.
Одиночки и чистота глобального состояния
Объекты-одиночки решают реальную проблему: иногда нужна именно одна точка для утилит, констант или фабрик. Scala делает это чище, чем статические члены Java, потому что object — полноценный объект, который может наследовать трейты и участвовать в паттерн-матчинге. Это не «особый случай» языка, а естественная часть его модели.
Companion-объект — это паттерн, который вы будете встречать постоянно. Стандартная библиотека построена на нём: List(1, 2, 3) работает потому, что у List есть companion с методом apply. Размещая фабрики в компаньоне, вы держите «как создать объект» рядом с самим классом, но отдельно от его экземпляров. Главное правило гигиены — не превращать одиночку в свалку изменяемого глобального состояния, иначе вернутся все проблемы глобальных переменных.
Companion-объекты вы будете встречать в стандартной библиотеке буквально на каждом шагу, поэтому понимание их роли окупается сразу. Каждый раз, когда вы пишете List(...), Some(...) или Map(...), за кулисами вызывается apply соответствующего companion-объекта. Узнав этот паттерн один раз, вы перестаёте видеть в подобных вызовах магию и начинаете понимать устройство библиотеки изнутри.
Итоги. object — одиночка, заменяющий static; companion-объект делит имя с классом и хранит фабрики; apply позволяет звать объект как функцию. Дальше — наследование и трейты.