Классы, объекты и состояние
Класс — это чертёж, объект — изделие по нему. Ruby объектно-ориентирован до мозга костей, и собственные классы — естественный способ моделировать предметную область.
Суть: класс описывается черезclass; методinitialize— конструктор, вызываемый при.new; переменные экземпляра с@хранят состояние объекта;attr_accessorгенерирует методы доступа к ним.
До сих пор мы пользовались готовыми классами (String, Array, Hash). Теперь создадим свои. Класс группирует данные (состояние) и поведение (методы) в одну сущность. Каждый объект класса хранит собственное состояние в переменных экземпляра — они начинаются с @ и невидимы снаружи напрямую.
class Dog
def initialize(name, age)
@name = name # переменная экземпляра
@age = age
end
def bark
"#{@name} говорит: Гав!"
end
end
rex = Dog.new("Рекс", 3) # вызывает initialize
puts rex.bark # => Рекс говорит: Гав!
Разбор: доступ к состоянию через attr_accessor
Переменные @name закрыты снаружи. Чтобы их читать и менять, нужны методы. Писать их вручную утомительно, поэтому Ruby даёт макросы: attr_reader (только чтение), attr_writer (только запись), attr_accessor (и то, и другое). Они генерируют методы за вас.
class Account
attr_accessor :balance # создаёт balance и balance=
attr_reader :owner # только чтение
def initialize(owner)
@owner = owner
@balance = 0
end
end
acc = Account.new("Аня")
acc.balance = 100 # работает методом balance=
puts acc.balance # => 100
puts acc.owner # => Аня
Как работает под капотом
Когда вы пишете Dog.new("Рекс", 3), Ruby создаёт пустой объект, затем вызывает на нём initialize с вашими аргументами и возвращает готовый объект. Состояние живёт внутри: у каждого объекта своя копия @name и @age. А attr_accessor :balance — это не «свойство», а сокращение, которое определяет два метода: balance (возвращает @balance) и balance= (присваивает).
класс Dog (чертёж)
|
Dog.new("Рекс",3)
|
1. создать пустой объект
2. вызвать initialize(name, age)
| @name="Рекс" @age=3
v
объект rex [ @name, @age ] <- своё состояние
объект bob [ @name, @age ] <- другое состояние
|
у каждого объекта СВОИ @-переменные
Та же идея «класс с конструктором и свойствами» на Python:
# Та же логика на Python ▶
class Dog:
def __init__(self, name, age):
self.name = name # аналог @name
self.age = age
def bark(self):
return f"{self.name} говорит: Гав!"
rex = Dog("Рекс", 3)
print(rex.bark()) # Рекс говорит: Гав!
Частые ошибки
- Забыть @ у переменной экземпляра. Без
@это локальная переменная метода, она исчезнет после выхода и не сохранит состояние. - Открывать всё через attr_accessor. Не каждое поле должно быть изменяемым снаружи. Чаще достаточно
attr_reader. - Логика в initialize. Конструктор должен только инициализировать состояние, а не выполнять тяжёлую работу.
Best practices
- По умолчанию открывайте только чтение (
attr_reader); запись добавляйте, лишь когда она действительно нужна. - Имена классов пишите в
CamelCase:BankAccount, а неbank_account. - Держите состояние объекта согласованным: проверяйте входные данные в
initialize, чтобы объект не родился в «битом» виде.
Глубже: методы класса и переменные класса
До сих пор мы говорили о методах экземпляра — тех, что вызываются на конкретном объекте. Но у класса есть и собственные методы и данные, общие для всех экземпляров. Метод класса объявляется через def self.method_name и вызывается прямо на классе: классический пример — фабричные методы вроде User.create или Time.now, которые создают и возвращают объекты. Такие методы удобны, когда действие логически принадлежит классу в целом, а не отдельному экземпляру. Рядом существуют переменные класса (@@count), общие для всех объектов, — например, счётчик созданных экземпляров. Однако к ним стоит относиться осторожно: они разделяются по всей иерархии наследования и легко приводят к неожиданному поведению, поэтому в современном коде их часто заменяют переменными экземпляра самого класса. Понимание разделения «уровень класса против уровня экземпляра» — важная веха: оно объясняет, почему Array.new вызывается на классе, а arr.push — на объекте, и готовит вас к пониманию того, как устроены константы, конфигурация и фабрики в больших Ruby-проектах и в Rails.
Итог. Класс — чертёж, объект — экземпляр со своим состоянием в @-переменных. initialize — конструктор, а attr_accessor и его собратья генерируют методы доступа, избавляя от рутины.