Перейти к основному содержимому

5.11. ООП в Ruby

Разработчику Архитектору

ООП в Ruby

Пример класса

class Unit
attr_accessor :name, :intel, :agility, :strength, :health, :mana, :level

def initialize
@name = "Имя"
@intel = 10
@agility = 10
@strength = 10
@health = 100
@mana = 50
@level = 1
end

def damage
(@intel + @agility + @strength) + (@level * 2)
end

def attack(target)
puts "#{@name} атакует #{target.name} и наносит #{damage} единиц урона."
target.health -= damage
puts "#{target.name} теперь имеет #{target.health} здоровья."
end
end

warrior = Unit.new
warrior.name = "Воин"
warrior.intel = 5
warrior.agility = 15
warrior.strength = 30

mage = Unit.new
mage.name = "Маг"
mage.intel = 35
mage.agility = 10
mage.strength = 5

warrior.attack(mage)
mage.attack(warrior)

Ключевое слово class открывает определение нового класса. Имя класса начинается с заглавной буквы в соответствии со стилем кодирования Ruby. Тело класса завершается ключевым словом end. Класс представляет шаблон для создания объектов с единым набором свойств и поведения.

Метод attr_accessor автоматически создаёт геттеры и сеттеры для указанных свойств. Геттер возвращает значение переменной экземпляра, сеттер устанавливает новое значение. Такой подход устраняет необходимость ручного написания методов доступа и делает код более лаконичным. Каждое свойство связывается с соответствующей переменной экземпляра через символ @.

Метод initialize вызывается автоматически при создании объекта через Unit.new. В конструкторе устанавливаются начальные значения всех переменных экземпляра. Переменные экземпляра помечаются символом @ в начале имени и существуют в течение всего жизненного цикла объекта. Конструктор обеспечивает корректное начальное состояние каждого вновь созданного объекта.

Метод damage возвращает результат арифметического выражения без сохранения промежуточного значения. При каждом вызове метода происходит пересчёт суммы характеристик с учётом текущего уровня персонажа. Такой подход гарантирует актуальность урона при изменении любых характеристик объекта без дополнительных операций синхронизации.

Метод attack принимает целевой объект в качестве параметра. Внутри метода используется строковая интерполяция: выражения внутри конструкции #{} вычисляются и подставляются в строку. Оператор -= уменьшает здоровье цели на величину урона. Последовательные вызовы puts выводят информационные сообщения в консоль с автоматическим добавлением символа новой строки.

Вызов Unit.new создаёт новый экземпляр класса и вызывает метод initialize. После создания объекта значения его свойств изменяются через сеттеры, предоставляемые attr_accessor. Каждый объект сохраняет собственные значения характеристик независимо от других экземпляров. Изменение свойств одного объекта не влияет на состояние других объектов того же класса.

Объекты взаимодействуют через вызов методов друг друга. Выражение warrior.attack(mage) передаёт объект mage в метод attack объекта warrior. Внутри метода происходит изменение состояния целевого объекта через его публичный интерфейс. Такой механизм демонстрирует принцип инкапсуляции: объекты взаимодействуют через методы, не имея прямого доступа к внутренней реализации друг друга.

Интерпретатор Ruby последовательно выполняет инструкции файла. Создаются два объекта с различными характеристиками. Первый вызов метода attack уменьшает здоровье мага. Второй вызов метода attack уменьшает здоровье воина с учётом его текущих характеристик. Каждое действие сопровождается выводом информационного сообщения, отражающего текущее состояние объектов.

Ruby использует символ @ для обозначения переменных экземпляра внутри класса. Внешний код обращается к этим переменным через геттеры и сеттеры без символа @. Строковая интерполяция работает только внутри двойных кавычек. Методы возвращают значение последнего вычисленного выражения без необходимости явного использования ключевого слова return. Отсутствие скобок при вызове методов без аргументов является допустимым и распространённым стилем кодирования.


Классы и объекты

Определение класса

Класс в языке Ruby представляет собой шаблон или конструктор для создания объектов. Класс определяет структуру данных и поведение объектов, которые будут созданы на его основе. Для определения класса используется ключевое слово class, за которым следует имя класса.

class Person
def initialize(name, age)
@name = name
@age = age
end

def greet
puts "Привет, меня зовут #{@name}"
end
end

Имя класса в языке Ruby начинается с заглавной буквы и следует соглашению об именовании в стиле CamelCase. Класс может содержать методы, переменные экземпляра и константы. Каждый класс в языке наследуется от базового класса Object, что обеспечивает единообразие всей объектной системы.

Создание объектов

Объекты создаются путем вызова метода new на классе. Этот метод создает новый экземпляр класса и вызывает конструктор initialize для настройки начального состояния объекта.

person1 = Person.new("Алексей", 30)
person2 = Person.new("Мария", 25)

person1.greet # Вывод: Привет, меня зовут Алексей
person2.greet # Вывод: Привет, меня зовут Мария

Каждый созданный объект является уникальным экземпляром класса и имеет собственное состояние. Объекты могут взаимодействовать друг с другом через вызов методов и обмен данными.

Переменные экземпляра

Переменные экземпляра в языке Ruby обозначаются символом @ в начале имени. Эти переменные принадлежат конкретному объекту и хранят его состояние. Каждый экземпляр класса имеет собственные копии переменных экземпляра.

class BankAccount
def initialize(account_number, balance)
@account_number = account_number
@balance = balance
@transactions = []
end

def deposit(amount)
@balance += amount
@transactions << { type: :deposit, amount: amount, date: Time.now }
end

def withdraw(amount)
if @balance >= amount
@balance -= amount
@transactions << { type: :withdrawal, amount: amount, date: Time.now }
true
else
false
end
end
end

account = BankAccount.new("12345", 1000)
account.deposit(500)
account.withdraw(200)

Переменные экземпляра доступны во всех методах экземпляра класса и сохраняют свои значения между вызовами методов.

Методы экземпляра

Методы экземпляра определяют поведение объектов. Они вызываются на конкретных экземплярах класса и имеют доступ к переменным экземпляра.

class Rectangle
def initialize(width, height)
@width = width
@height = height
end

def area
@width * @height
end

def perimeter
2 * (@width + @height)
end

def square?
@width == @height
end
end

rect = Rectangle.new(10, 20)
puts rect.area # 200
puts rect.perimeter # 60
puts rect.square? # false

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

Инициализация объектов

Метод initialize является конструктором класса. Он вызывается автоматически при создании нового объекта с помощью new. Этот метод устанавливает начальное состояние объекта.

class User
def initialize(username, email, age = 18)
@username = username
@email = email
@age = age
@created_at = Time.now
@active = true
end
end

user1 = User.new("john_doe", "john@example.com")
user2 = User.new("jane_smith", "jane@example.com", 25)

Метод initialize может иметь параметры со значениями по умолчанию, что позволяет создавать объекты с различными конфигурациями. Конструктор может выполнять любую необходимую настройку объекта при его создании.

Инкапсуляция

Переменные экземпляра и их защита

Инкапсуляция в языке Ruby обеспечивается через использование переменных экземпляра. Эти переменные недоступны напрямую извне объекта, что защищает внутреннее состояние объекта от несанкционированного доступа.

class TemperatureSensor
def initialize(initial_temperature)
@temperature = initial_temperature
@calibration_factor = 1.0
@last_reading_time = nil
end

def read_temperature
@last_reading_time = Time.now
@temperature * @calibration_factor
end

def calibrate(factor)
@calibration_factor = factor
end
end

sensor = TemperatureSensor.new(25.5)
puts sensor.read_temperature # 25.5
sensor.calibrate(1.05)
puts sensor.read_temperature # 26.775

Прямой доступ к переменным @temperature, @calibration_factor и @last_reading_time извне объекта невозможен. Это обеспечивает контроль над изменением состояния объекта.

Методы доступа

Для доступа к переменным экземпляра извне объекта используются методы доступа. Геттеры возвращают значения переменных, а сеттеры устанавливают новые значения.

class Product
def initialize(name, price)
@name = name
@price = price
end

# Геттеры
def name
@name
end

def price
@price
end

# Сеттеры
def name=(new_name)
@name = new_name
end

def price=(new_price)
@price = new_price if new_price >= 0
end
end

product = Product.new("Книга", 500)
puts product.name # Книга
puts product.price # 500

product.name = "Новая книга"
product.price = 600
puts product.name # Новая книга
puts product.price # 600

Методы доступа позволяют добавлять логику валидации и обработки при чтении и записи значений.

Сокращения для методов доступа

Язык предоставляет специальные методы для автоматической генерации методов доступа: attr_reader, attr_writer и attr_accessor.

class Employee
attr_reader :id, :hire_date
attr_writer :department
attr_accessor :name, :position, :salary

def initialize(id, name, position, salary)
@id = id
@name = name
@position = position
@salary = salary
@hire_date = Time.now
@department = "IT"
end
end

emp = Employee.new(101, "Иван Петров", "Разработчик", 100000)
puts emp.id # 101
puts emp.name # Иван Петров
puts emp.position # Разработчик
puts emp.hire_date # 2026-02-11 12:00:00 +0300

emp.name = "Иван Сидоров"
emp.position = "Старший разработчик"
emp.salary = 150000
emp.department = "DevOps"

attr_reader создает только геттеры, attr_writer создает только сеттеры, а attr_accessor создает и геттеры, и сеттеры для указанных атрибутов.

Модификаторы доступа

Язык предоставляет три уровня доступа для методов: public, protected и private.

class BankAccount
attr_reader :account_number

def initialize(account_number, initial_balance)
@account_number = account_number
@balance = initial_balance
@pin_code = generate_pin
end

public

def deposit(amount)
@balance += amount
record_transaction(:deposit, amount)
end

def withdraw(amount, pin)
if verify_pin(pin) && @balance >= amount
@balance -= amount
record_transaction(:withdrawal, amount)
true
else
false
end
end

protected

def verify_pin(pin)
@pin_code == pin
end

private

def generate_pin
rand(1000..9999)
end

def record_transaction(type, amount)
puts "#{type.capitalize} of #{amount} at #{Time.now}"
end
end

account = BankAccount.new("ACC123", 1000)
account.deposit(500) # Работает
account.withdraw(200, 1234) # Работает
# account.verify_pin(1234) # Ошибка: метод защищенный
# account.generate_pin # Ошибка: метод приватный

Публичные методы доступны извне объекта. Защищенные методы доступны только внутри класса и его наследников. Приватные методы доступны только внутри класса, где они определены.

Наследование

Одиночное наследование

Язык поддерживает одиночное наследование, где класс может наследовать от одного родительского класса. Для указания наследования используется оператор <.

class Animal
def initialize(name)
@name = name
end

def speak
"Some sound"
end

def info
"I am #{@name}"
end
end

class Dog < Animal
def speak
"Woof!"
end

def fetch
"Fetching the ball!"
end
end

class Cat < Animal
def speak
"Meow!"
end

def purr
"Purrrring..."
end
end

dog = Dog.new("Рекс")
cat = Cat.new("Мурка")

puts dog.speak # Woof!
puts cat.speak # Meow!
puts dog.info # I am Рекс
puts cat.info # I am Мурка
puts dog.fetch # Fetching the ball!
puts cat.purr # Purrrring...

Дочерние классы наследуют все методы родительского класса и могут добавлять новые методы или переопределять существующие.

Переопределение методов

Дочерние классы могут переопределять методы родительского класса, предоставляя собственную реализацию.

class Shape
def area
0
end

def perimeter
0
end

def description
"Это геометрическая фигура"
end
end

class Circle < Shape
def initialize(radius)
@radius = radius
end

def area
Math::PI * @radius ** 2
end

def perimeter
2 * Math::PI * @radius
end

def description
"Это круг с радиусом #{@radius}"
end
end

class Rectangle < Shape
def initialize(width, height)
@width = width
@height = height
end

def area
@width * @height
end

def perimeter
2 * (@width + @height)
end

def description
"Это прямоугольник #{@width}x#{@height}"
end
end

circle = Circle.new(5)
rectangle = Rectangle.new(4, 6)

puts circle.area # 78.53981633974483
puts circle.perimeter # 31.41592653589793
puts circle.description # Это круг с радиусом 5

puts rectangle.area # 24
puts rectangle.perimeter # 20
puts rectangle.description # Это прямоугольник 4x6

Переопределение методов позволяет создавать специализированные реализации для разных типов объектов.

Метод super

Метод super позволяет вызывать метод родительского класса из переопределенного метода дочернего класса.

class Vehicle
def initialize(make, model, year)
@make = make
@model = model
@year = year
end

def info
"#{@year} #{@make} #{@model}"
end

def start_engine
"Двигатель запущен"
end
end

class Car < Vehicle
def initialize(make, model, year, doors)
super(make, model, year)
@doors = doors
end

def info
"#{super}, #{@doors} дверей"
end

def start_engine
"#{super}. Автомобиль готов к движению!"
end
end

class Motorcycle < Vehicle
def initialize(make, model, year, has_sidecar)
super(make, model, year)
@has_sidecar = has_sidecar
end

def info
sidecar_info = @has_sidecar ? " с коляской" : ""
"#{super}#{sidecar_info}"
end

def start_engine
"#{super}. Мотоцикл ревет!"
end
end

car = Car.new("Toyota", "Camry", 2020, 4)
motorcycle = Motorcycle.new("Harley", "Sportster", 2019, true)

puts car.info # 2020 Toyota Camry, 4 дверей
puts motorcycle.info # 2019 Harley Sportster с коляской
puts car.start_engine # Двигатель запущен. Автомобиль готов к движению!
puts motorcycle.start_engine # Двигатель запущен. Мотоцикл ревет!

Метод super может вызываться без аргументов, с теми же аргументами, что и текущий метод, или с указанием конкретных аргументов.

Иерархия классов

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

class LivingBeing
def initialize(name)
@name = name
end

def breathe
"Дышит"
end
end

class Animal < LivingBeing
def initialize(name, species)
super(name)
@species = species
end

def move
"Передвигается"
end
end

class Mammal < Animal
def initialize(name, species)
super(name, species)
end

def feed_young
"Кормит детенышей молоком"
end
end

class Dog < Mammal
def initialize(name, breed)
super(name, "Canis lupus familiaris")
@breed = breed
end

def bark
"Гавкает"
end

def info
"#{@name} - это собака породы #{@breed}, вид #{@species}"
end
end

class Cat < Mammal
def initialize(name, color)
super(name, "Felis catus")
@color = color
end

def meow
"Мяукает"
end

def info
"#{@name} - это кошка цвета #{@color}, вид #{@species}"
end
end

dog = Dog.new("Бобик", "Овчарка")
cat = Cat.new("Мурка", "Рыжая")

puts dog.info # Бобик - это собака породы Овчарка, вид Canis lupus familiaris
puts cat.info # Мурка - это кошка цвета Рыжая, вид Felis catus
puts dog.breathe # Дышит
puts dog.move # Передвигается
puts dog.feed_young # Кормит детенышей молоком
puts dog.bark # Гавкает
puts cat.meow # Мяукает

Иерархия классов позволяет повторно использовать код и создавать специализированные классы на основе общих концепций.

Полиморфизм

Полиморфизм через наследование

Полиморфизм позволяет объектам разных классов реагировать на один и тот же метод по-разному. Это достигается через переопределение методов в дочерних классах.

class PaymentMethod
def process_payment(amount)
"Обработка платежа #{amount} рублей"
end

def refund(amount)
"Возврат #{amount} рублей"
end
end

class CreditCard < PaymentMethod
def initialize(card_number)
@card_number = card_number
end

def process_payment(amount)
"Обработка платежа #{amount} рублей с карты #{@card_number}"
end

def refund(amount)
"Возврат #{amount} рублей на карту #{@card_number}"
end
end

class PayPal < PaymentMethod
def initialize(email)
@email = email
end

def process_payment(amount)
"Обработка платежа #{amount} рублей через PayPal аккаунт #{@email}"
end

def refund(amount)
"Возврат #{amount} рублей на аккаунт #{@email}"
end
end

class Cash < PaymentMethod
def process_payment(amount)
"Принят наличный платеж #{amount} рублей"
end

def refund(amount)
"Выдан наличный возврат #{amount} рублей"
end
end

def complete_purchase(payment_method, amount)
puts payment_method.process_payment(amount)
puts payment_method.refund(amount * 0.1)
end

credit_card = CreditCard.new("4111-1111-1111-1111")
paypal = PayPal.new("user@example.com")
cash = Cash.new

complete_purchase(credit_card, 1000)
# Обработка платежа 1000 рублей с карты 4111-1111-1111-1111
# Возврат 100.0 рублей на карту 4111-1111-1111-1111

complete_purchase(paypal, 1500)
# Обработка платежа 1500 рублей через PayPal аккаунт user@example.com
# Возврат 150.0 рублей на аккаунт user@example.com

complete_purchase(cash, 500)
# Принят наличный платеж 500 рублей
# Выдан наличный возврат 50.0 рублей

Полиморфизм через наследование позволяет писать код, который работает с объектами разных типов через общий интерфейс.

Утиная типизация

Язык использует утиную типизацию, где важны методы объекта, а не его конкретный тип. Если объект имеет нужные методы, он может использоваться в соответствующем контексте.

class Duck
def quack
"Quack!"
end

def walk
"Waddle waddle"
end

def swim
"Paddle paddle"
end
end

class Person
def quack
"Человек подражает утке: Кря-кря!"
end

def walk
"Человек идет"
end

def swim
"Человек плавает"
end
end

class Robot
def quack
"Механическое кря-кря"
end

def walk
"Робот шагает"
end

def swim
"Робот не умеет плавать"
end
end

def make_it_duck(duck_like)
puts duck_like.quack
puts duck_like.walk
puts duck_like.swim
puts "---"
end

duck = Duck.new
person = Person.new
robot = Robot.new

make_it_duck(duck)
# Quack!
# Waddle waddle
# Paddle paddle
# ---

make_it_duck(person)
# Человек подражает утке: Кря-кря!
# Человек идет
# Человек плавает
# ---

make_it_duck(robot)
# Механическое кря-кря
# Робот шагает
# Робот не умеет плавать
# ---

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

Полиморфизм в коллекциях

Полиморфизм особенно полезен при работе с коллекциями объектов разных типов.

class Notification
def send
raise NotImplementedError, "Метод должен быть переопределен"
end
end

class EmailNotification < Notification
def initialize(email, subject, body)
@email = email
@subject = subject
@body = body
end

def send
"Отправка письма на #{@email}: #{@subject}"
end
end

class SMSNotification < Notification
def initialize(phone, message)
@phone = phone
@message = message
end

def send
"Отправка SMS на #{@phone}: #{@message[0..50]}"
end
end

class PushNotification < Notification
def initialize(device_token, title, content)
@device_token = device_token
@title = title
@content = content
end

def send
"Отправка push на устройство #{@device_token}: #{@title}"
end
end

class SlackNotification < Notification
def initialize(webhook_url, channel, text)
@webhook_url = webhook_url
@channel = channel
@text = text
end

def send
"Отправка в Slack канал #{@channel}: #{@text[0..30]}"
end
end

notifications = [
EmailNotification.new("user@example.com", "Добро пожаловать", "Спасибо за регистрацию..."),
SMSNotification.new("+79001234567", "Ваш код подтверждения: 123456"),
PushNotification.new("abc123def456", "Новое сообщение", "У вас новое сообщение..."),
SlackNotification.new("https://hooks.slack.com/...", "#general", "Важное объявление...")
]

notifications.each do |notification|
puts notification.send
end

# Отправка письма на user@example.com: Добро пожаловать
# Отправка SMS на +79001234567: Ваш код подтверждения: 123456
# Отправка push на устройство abc123def456: Новое сообщение
# Отправка в Slack канал #general: Важное объявление...

Полиморфизм позволяет обрабатывать разнородные объекты единообразно через общий интерфейс.

Модули и миксины

Определение модулей

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

module Loggable
def log(message)
timestamp = Time.now.strftime("%Y-%m-%d %H:%M:%S")
puts "[#{timestamp}] #{message}"
end

def log_error(error)
log("ERROR: #{error}")
end

def log_info(info)
log("INFO: #{info}")
end
end

module Validatable
def validate_presence(field, value)
unless value && !value.to_s.strip.empty?
errors << "#{field} не может быть пустым"
end
end

def validate_length(field, value, min:, max:)
length = value.to_s.length
if length < min
errors << "#{field} должен быть не менее #{min} символов"
elsif length > max
errors << "#{field} должен быть не более #{max} символов"
end
end

def errors
@errors ||= []
end

def valid?
errors.empty?
end
end

module Timestampable
attr_reader :created_at, :updated_at

def touch
@updated_at = Time.now
end

private

def set_timestamps
@created_at = Time.now
@updated_at = Time.now
end
end

Модули группируют связанные методы и могут использоваться для организации кода и повторного использования функциональности.

Включение модулей

Модули включаются в классы с помощью ключевого слова include. Методы модуля становятся методами экземпляра класса.

class User
include Loggable
include Validatable

attr_accessor :username, :email, :password

def initialize(username, email, password)
@username = username
@email = email
@password = password
log_info("Создан новый пользователь: #{username}")
end

def validate
errors.clear
validate_presence(:username, username)
validate_presence(:email, email)
validate_presence(:password, password)
validate_length(:username, username, min: 3, max: 20)
validate_length(:password, password, min: 6, max: 100)
valid?
end

def save
if validate
log_info("Пользователь #{username} сохранен")
true
else
log_error("Ошибка валидации для #{username}: #{errors.join(', ')}")
false
end
end
end

user = User.new("john_doe", "john@example.com", "secret123")
user.save
# [2026-02-11 12:00:00] INFO: Создан новый пользователь: john_doe
# [2026-02-11 12:00:00] INFO: Пользователь john_doe сохранен

invalid_user = User.new("", "", "123")
invalid_user.save
# [2026-02-11 12:00:00] INFO: Создан новый пользователь:
# [2026-02-11 12:00:00] ERROR: Ошибка валидации для : username не может быть пустым, email не может быть пустым, username должен быть не менее 3 символов, password должен быть не менее 6 символов

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

Расширение классов

Ключевое слово extend добавляет методы модуля как методы класса, а не методы экземпляра.

module ClassMethods
def find_by_id(id)
log_info("Поиск по ID: #{id}")
# Логика поиска
end

def all
log_info("Получение всех записей")
# Логика получения всех записей
end

def count
log_info("Подсчет записей")
# Логика подсчета
end
end

class Product
extend ClassMethods
include Loggable

attr_accessor :name, :price

def initialize(name, price)
@name = name
@price = price
log_info("Создан продукт: #{name}")
end

def self.log_info(message)
puts "[CLASS] #{message}"
end
end

Product.find_by_id(1)
# [CLASS] Поиск по ID: 1

Product.all
# [CLASS] Получение всех записей

Product.count
# [CLASS] Подсчет записей

product = Product.new("Книга", 500)
# [2026-02-11 12:00:00] INFO: Создан продукт: Книга

extend добавляет методы модуля на уровне класса, делая их доступными без создания экземпляра.

Комбинированное использование модулей

Модули могут включаться и расширять классы одновременно, обеспечивая гибкость в организации кода.

module ActiveRecord
def self.included(base)
base.extend(ClassMethods)
end

module ClassMethods
def has_many(name)
log_info("Определена связь has_many: #{name}")
# Логика создания связи
end

def belongs_to(name)
log_info("Определена связь belongs_to: #{name}")
# Логика создания связи
end
end

def save
log_info("Сохранение записи")
# Логика сохранения
end

def destroy
log_info("Удаление записи")
# Логика удаления
end
end

class Post
include Loggable
include ActiveRecord

has_many :comments
belongs_to :author

attr_accessor :title, :content

def initialize(title, content)
@title = title
@content = content
end
end

class Comment
include Loggable
include ActiveRecord

belongs_to :post
belongs_to :author

attr_accessor :body

def initialize(body)
@body = body
end
end

# [2026-02-11 12:00:00] INFO: Определена связь has_many: comments
# [2026-02-11 12:00:00] INFO: Определена связь belongs_to: author
# [2026-02-11 12:00:00] INFO: Определена связь belongs_to: post
# [2026-02-11 12:00:00] INFO: Определена связь belongs_to: author

post = Post.new("Заголовок", "Содержание")
post.save
# [2026-02-11 12:00:00] INFO: Сохранение записи

comment = Comment.new("Отличная статья!")
comment.save
# [2026-02-11 12:00:00] INFO: Сохранение записи

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

Особенности объектно-ориентированного программирования в языке Ruby

Всё есть объект

В языке каждое значение является объектом, включая числа, строки, символы и даже классы.

# Числа как объекты
5.class # Integer
5.even? # true
5.odd? # false
5.times { puts "Hello" }

# Строки как объекты
"hello".class # String
"hello".upcase # "HELLO"
"hello".length # 5
"hello".reverse # "olleh"

# Символы как объекты
:hello.class # Symbol
:hello.to_s # "hello"
:hello.inspect # ":hello"

# Классы как объекты
String.class # Class
Array.class # Class
Class.class # Class

# Даже nil является объектом
nil.class # NilClass
nil.nil? # true
nil.to_s # ""

Объектная природа всех значений обеспечивает единообразие и мощь языка.

Динамическая природа языка

Язык позволяет изменять классы и объекты во время выполнения программы.

class Person
def greet
"Привет!"
end
end

person = Person.new
puts person.greet # Привет!

# Добавление метода в класс во время выполнения
class Person
def farewell
"До свидания!"
end
end

puts person.farewell # До свидания!

# Изменение существующего метода
class Person
def greet
"Здравствуйте!"
end
end

puts person.greet # Здравствуйте!

# Добавление метода к конкретному объекту
def person.special_greet
"Особое приветствие!"
end

puts person.special_greet # Особое приветствие!
# Person.new.special_greet # Ошибка: метод не существует для других объектов

# Удаление метода
class Person
undef_method :farewell
end

# person.farewell # Ошибка: метод удален

Динамическая природа языка позволяет создавать гибкие и адаптируемые программы.

Метапрограммирование

Метапрограммирование позволяет программам создавать и изменять другие программы или сами себя во время выполнения.

class DynamicAttributes
def self.attribute(name, default = nil)
define_method(name) do
instance_variable_get("@#{name}") || default
end

define_method("#{name}=") do |value|
instance_variable_set("@#{name}", value)
end
end
end

class Product < DynamicAttributes
attribute :name, "Безымянный продукт"
attribute :price, 0
attribute :in_stock, true
end

product = Product.new
puts product.name # Безымянный продукт
puts product.price # 0
puts product.in_stock # true

product.name = "Книга"
product.price = 500
product.in_stock = false

puts product.name # Книга
puts product.price # 500
puts product.in_stock # false

# Создание методов на лету
class Calculator
[:add, :subtract, :multiply, :divide].each do |operation|
define_method(operation) do |a, b|
case operation
when :add then a + b
when :subtract then a - b
when :multiply then a * b
when :divide then a.to_f / b
end
end
end
end

calc = Calculator.new
puts calc.add(5, 3) # 8
puts calc.subtract(10, 4) # 6
puts calc.multiply(6, 7) # 42
puts calc.divide(10, 2) # 5.0

Метапрограммирование позволяет создавать мощные абстракции и уменьшать повторение кода.

Методы доступа и атрибуты

Расширенные возможности атрибутов

Методы attr_reader, attr_writer и attr_accessor могут использоваться с дополнительными возможностями.

class AdvancedAttributes
# Базовые атрибуты
attr_accessor :name, :age

# Только для чтения
attr_reader :created_at

# Только для записи
attr_writer :password

# С валидацией
def email=(value)
if value =~ /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
@email = value
else
raise ArgumentError, "Неверный формат email"
end
end

attr_reader :email

# С преобразованием
def tags=(value)
@tags = value.is_a?(Array) ? value : value.split(',').map(&:strip)
end

attr_reader :tags

# С кэшированием
def expensive_calculation
@expensive_calculation ||= begin
sleep(1) # Имитация долгого вычисления
rand(1000)
end
end

def initialize(name, age)
@name = name
@age = age
@created_at = Time.now
end
end

obj = AdvancedAttributes.new("Test", 25)
obj.name = "New Name"
puts obj.name # New Name

obj.email = "test@example.com"
puts obj.email # test@example.com

obj.tags = "ruby, programming, oop"
puts obj.tags.inspect # ["ruby", "programming", "oop"]

obj.tags = ["ruby", "rails"]
puts obj.tags.inspect # ["ruby", "rails"]

result1 = obj.expensive_calculation # Выполняется 1 секунду
result2 = obj.expensive_calculation # Мгновенно, использует кэш
puts result1 == result2 # true

Расширенные возможности атрибутов позволяют создавать более умные и безопасные интерфейсы доступа к данным.

Защита данных через приватные методы доступа

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

class SecureAccount
attr_reader :account_number

def initialize(account_number, initial_balance, pin)
@account_number = account_number
@balance = initial_balance
@pin = pin
@transaction_history = []
end

def deposit(amount)
if amount > 0
@balance += amount
log_transaction(:deposit, amount)
true
else
false
end
end

def withdraw(amount, pin)
if verify_pin(pin) && amount > 0 && @balance >= amount
@balance -= amount
log_transaction(:withdrawal, amount)
true
else
false
end
end

def balance(pin)
verify_pin(pin) ? @balance : nil
end

def transaction_history(pin)
verify_pin(pin) ? @transaction_history.dup : []
end

private

attr_reader :balance

def verify_pin(pin)
@pin == pin
end

def log_transaction(type, amount)
@transaction_history << {
type: type,
amount: amount,
timestamp: Time.now
}
end
end

account = SecureAccount.new("ACC123", 1000, "1234")
account.deposit(500)

puts account.withdraw(200, "1234") # true
puts account.withdraw(100, "0000") # false (неверный PIN)

puts account.balance("1234") # 1300
puts account.balance("0000") # nil

history = account.transaction_history("1234")
puts history.size # 2

# account.balance # Ошибка: метод приватный

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

Исторический контекст объектно-ориентированного программирования в языке Ruby

Развитие концепций

Язык был создан Юкихиро Мацумото в 1995 году с целью объединить лучшие черты различных языков программирования. Концепция объектно-ориентированного программирования в языке была вдохновлена языком Smalltalk, который считается одним из первых чисто объектно-ориентированных языков.

# Влияние Smalltalk видно в том, что всё является объектом
5.times { puts "Hello" } # 5 - это объект класса Integer
"hello".upcase # "hello" - это объект класса String

# Влияние Perl видно в гибкости синтаксиса
array = [1, 2, 3]
array.each { |x| puts x } # Блоки кода как замыкания

# Влияние Python видно в читаемости кода
def calculate_area(width, height)
width * height
end

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

Эволюция языка

Версия 1.9, выпущенная в 2007 году, принесла значительные изменения в объектную модель, включая новую систему кодирования строк и улучшенную производительность.

# Ruby 1.8
"hello".each_byte { |b| puts b } # Работает с байтами

# Ruby 1.9+
"hello".each_char { |c| puts c } # Работает с символами

# Новые возможности в более поздних версиях
# Ruby 2.0+ - ключевые слова в методах
def create_user(name:, email:, age: 18)
{ name: name, email: email, age: age }
end

# Ruby 2.1+ - required keyword arguments
def create_user(name:, email:)
{ name: name, email: email }
end

# Ruby 2.7+ - numbered parameters
[1, 2, 3].map { _1 * 2 } # [2, 4, 6]

# Ruby 3.0+ - правый аргумент оператора присваивания
1 => x
puts x # 1

Версия 2.0, выпущенная в 2013 году, добавила ключевые слова в методах и другие улучшения. Версия 3.0, выпущенная в 2020 году, принесла значительные улучшения производительности и новые возможности языка.

Влияние на современную разработку

Язык оказал значительное влияние на современную веб-разработку через фреймворк Ruby on Rails, выпущенный в 2004 году. Rails популяризировал концепции соглашения над конфигурацией и принцип "не повторяйся".

# Пример кода в стиле Rails, демонстрирующий ООП концепции
class Post < ApplicationRecord
belongs_to :author
has_many :comments
has_many :tags, through: :post_tags

validates :title, presence: true, length: { minimum: 5 }
validates :content, presence: true

scope :published, -> { where(published: true) }
scope :recent, -> { order(created_at: :desc) }

def publish!
update(published: true, published_at: Time.now)
end

def to_param
"#{id}-#{title.parameterize}"
end
end

# Использование полиморфизма в коллекциях
posts = Post.published.recent.limit(10)
posts.each do |post|
puts "#{post.title} by #{post.author.name}"
puts "Comments: #{post.comments.count}"
end

Объектно-ориентированный подход языка способствовал созданию элегантных и выразительных фреймворков и библиотек.

Современное состояние

Современная версия языка 3.2 (на момент 2026 года) продолжает развивать объектно-ориентированные концепции, добавляя новые возможности и улучшая производительность.

# Современные возможности языка
# Паттерн-матчинг (Ruby 2.7+)
case data
in { type: "user", name: name, age: age }
puts "User: #{name}, #{age}"
in { type: "product", name: name, price: price }
puts "Product: #{name}, $#{price}"
else
puts "Unknown type"
end

# Бесконечные диапазоны (Ruby 2.6+)
(1..).take(5) # [1, 2, 3, 4, 5]

# Композиция через метод then (Ruby 2.6+)
result = compute_value
.then { |v| transform(v) }
.then { |v| validate(v) }
.then { |v| format(v) }

# Улучшенная работа с ключевыми словами
def method_with_keywords(**options)
options.fetch(:required_key)
options[:optional_key] || "default"
end

Язык продолжает развиваться, сохраняя при этом свою философию простоты и выразительности.