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
Язык продолжает развиваться, сохраняя при этом свою философию простоты и выразительности.