Классы, методы и объекты
Как мы ранее говорили, класс - это шаблон нашего объекта. В нем мы описываем какие атрибуты могут быть у объекта и какие методы у него есть, с помощью которых мы можем взаимодействовать с объектом. Давайте посмотрим как создать наш класс:
# инициализация класса с названием Person.
class Person
# конструктор класса, а также его аргументы
def initialize(name, age, address)
# присвоение аргументов конструктора к аргументам объекта
@name = name
@age = age
@address
end
end
Названия классов следует всегда записывать в формате CamelCase
(Каждое слово в названии пишется с заглавной буквы), например MagicBall
.
Ранее при создании объекта вы встречали такое слово как new
. Фактически, вызывая метод new
у класса вы вызываете его конструктор. В конструкторе вы можете определить сколько угодно аргументов как и у любого другого метода, но сами по себе аргументы никакого отношения к самому классу не имеют, это просто набор параметров, которые можно передать при создании объекта. Для того, чтобы аргументы стали частью объекта, его атрибутами, необходимо создать эти самые атрибуты и прировнять их к аргументам. атрибуты объекта создаются с помощью знака @
перед названием самого атрибута. Сделав это, ваш объект при создании уже будет иметь какое-то собственное состояние.
Объекты
Давайте посмотрим как нам теперь создать наш объект на основе этого класса:
# Создаем объект класса Person, у которого атрибут name = Mike, age = 24, address = Moscow
mike = Person.new('Mike', 24, 'Moscow')
# Создаем объект класса Person, у которого атрибут name = Jane, age = 18, address = St. Petersburg
jane = Person.new('Jane', 18, 'St. Petersburg')
p mike # = Person(name = Mike, age = 24, address = Moscow)
p jane # = Person(name = Jane, age = 18, address = St. Petersburg)
Как мы видим при инспектировании данных объектов они имеют абсолютно отличающиеся данные внутри себя.
Методы
Хорошо, у нас есть класс, есть несколько экземпляров этого класса с отличающимися атрибутами. Но что мы можем с ним делать ? Все что душе угодно. Для начала давайте посмотрим как мы можем изменять состояние этих самых объектов
# инициализация класса с названием Person
class Person
# конструктор класса, а также его аргументы
def initialize(name, age, address)
# присвоение аргументов конструктора к аргументам объекта
@name = name
@age = age
@address
end
# Как мы помним и человека раз в год день рождения, значит мы бы хотели, чтобы наш человек мог взрослеть
def birthday
@age += 1
end
end
mike = Person.new('Mike', 24, 'Moscow')
p mike # = Person(name = Mike, age = 24, address = Moscow)
mike.birthday
p mike # = Person(name = Mike, age = 25, address = Moscow)
Для того, чтобы ваш код не превратился в кашу, создавайте новые классы в новых файлах, записанные в формате lower_case
. Например наш класс Person
, значит файл должен называться person.rb
, класс называется MagicBall
, значит файл называется magic_ball.rb
и тд. Как использовать код из одного файла внутри другого ? Воспользуйтесь ключевым словом require
или require_relative
. Подробнее про это будет сказано в доп. материале.
Переменные и методы класса
Помимо наших обычных переменных объекта, которые мы объявляем с помощью @
, существуют еще переменные класса, которые объявляются с помощью @@
. Отличие от переменных объекта в том, что значение переменной объекта у каждого свое, оно является состоянием нашего объекта, в то время как переменная класса принадлежит целому классу и доступ к ней есть у любого объекта этого класса. Каждые раз создавая новый объект вы создаете, по большому счету, новый объект с новыми переменными. Если у объекта 3 переменные, вы создали 2 объекта, то фактически создаются 6 переменных, так как класс это в каком-то роде еще одна структруа данных. В случае с переменной класса она создается один раз и может быть переиспользована сразу в нескольких объектах. 1 переменная класса, 5 объектов, кол-во созданных переменных класса не меняется.
class Car
@@max_speed = 100
def initialize(color, name, speed)
@speed = speed > @@max_speed ? @@max_speed : speed
@color = color
@name = name
end
def self.increase_max_speed(new_speed)
@@max_speed = new_speed
end
end
bmw = Car.new(:green, :bmw, 90)
porsche = Car.new(:black, :porsche, 120)
p bmw # = Car(color = green, name = bmw, speed = 90)
# Как мы видим сработало ограничение через переменную класса
p porsche # = Car(color = black, name = porsche, speed = 100)
# изменяем значение через метод класса
# значение можно также менять и через обычные методы объекта
Car.increase_max_speed(140)
# А теперь сработало ограничение с новым значением @@max_speed = 120
audi = Car.new(:black, :audi, 140) # = Car(color = black, name = bmw, speed = 120)
Методы класса точно так же как и переменные не завязаны на объекте, а являются частью класса. Записать метод класса можно следующим образом:
class Person
@@all_persons = []
# Метод класса, который будет порождать новые объекты с рандомным именем
def Person.random # также можно записать как self.random
name = ('a'..'z').to_a.shuffle.take(5).join
Person.new(name)
end
# любые методы объявленные внутри такой конструкции считаются методами класса
class << self
def add_person(person)
@all_persons << person
end
def all_persons
@all_persons
end
end
def initialize(name)
@name = name
end
end
# Объект класса Person с рандомным именем созданный методом класса
random_person = Person.random
# метод класса для добавления объекта в массив
Person.add_person(random_person)
# метод класса для получения переменной класса
p Person.all_persons # = [Person(name = 'рандомное имя')]
Запись через self.method
или через class << self
зависит вашего стиля кода или вашей команды. Старайтесь придерживаться правила: если у вас один метод класса, то лучше записать его с помощью self.
, если у вас несколько таких методов, то лучше вынести их во внутрь class << self
, так как это позволяет нам поддерживать чистоту в и простоту в нашем коде.
Константы
Константы очень похожи по своей сути на переменные класса, но их концепция не завязана на ООП. Для объявления константы вам нужно записать ее как обычную переменную без @
, но полностью заглавными буквами в формате snake_case
. Константы можно определить и в обычном ruby файле без использования классов. Если в случае с переменными класса мы можем менять их значение без каких либо проблем, то в случае с константами при изменении значения мы увидим warning
сообщение: warning: already initialized constant CONSTANT_NAME
, при этом значение все равно поменяется.
ABC = 1
ABC = 2 # warning: already initialized constant ABC
К сожалению у нас нет возможности сделать константу неизменяемой, но у нас есть возможность сделать значение иммутабельным
# .freeze позволяет нам защитить значение от изменения
ABC = "ABC".freeze
# << позволяем нам изменить саму строку не меняя самое значение в переменной. Более подробно поговорим в следующих главах
# Ели бы у нас был массив ['hello', 'world'].freeze, то при попытке добавить значение через .push(element), мы бы получили такую же ошибку
ABC << "CBA" # RuntimeError: can't modify frozen String
Давайте посмотрим как это выглядит для класса:
class Car
MAX_SPEED = 100
...
end
Так же хорошей практикой является создавать отдельные классы для хранения однотипных констант
# константы класса используют часто как способ группировки констант по общему признаку
class Color
GREEN = 0
BLUE = 1
RED = 3
end
# пример вызова константы класса
puts Color::BLUE
В главе про наследование мы еще раз поговорим про константы и переменные класса, почему их использование может навредить программе, но так или иначе данные конструкции языка все равно понадобятся для решения домашнего задания
self
В прошлым примерах вы уже сталкивались с ключевым словом self
, а теперь стоит разобрать что это такое. Мы привыкли уже создавать наши объекты, помоещать их в переменную6 а после передавать эту переменную куда-то еще (либо без переменной, сразу как значение передавать), но что если мы хотим получать доступ к объекту сразу изнутри этого самого объекта ? self
позволяет нам внутри нашего класса обращаться к будущему объекту, который будет создан на основе этого класса. Звучит сложно, но с примером будет полегче
class Person
def initialize(name)
@name = name
end
def self_object
# Получаем объект, который будет создан на базе этого класса
person = self
# вызываем у этого объекта его метод для печати имени в консоль
person.print_name
# возвращаем объект
person
end
def print_name
puts @name
end
end
mike = Person.new('mike')
p mike # = Person(name = mike)
# Как мы видим, метод self_object возвращает нам... самого себя
# Мы можем работать с self точно так же как и с самим объектом, вызывая у него методы, например
self_person = mike.self_object # = 'mike'
p self_person # = Person(name = 'mike')
self_person.print_name # = 'mike'
Также, self
как мы рассматривали ранее, служит еще и для создания методов класса.
Задание animals
Внимание ! Это и последующие задания данного модуля с названием animals
необходимо выполнять в одной и той же директории в корне модуля - 3_oop/animals
Реализовать класс Cat у которого есть такие атрибуты как
name
(имя) иweight
(вес) (в граммах)Добавьте константы в которых будет указан
MIN_WEIGHT
(минимальный вес) равный 1000грамм иMAX_WEIGHT
(максимальный вес) равный 9000грамм.Создайте конструктор, который будет принимать имя в аргументах, а вес будет устанавливаться случайным образом между минимальным и максимальным весом.
Реализуйте следующие методы:
toilet
- кот ходит в туалет и теряет 1 грамм своего веса.voice
- кот издает голос 'Meow' в консоль и теряет 1 грамм своего веса.eat(food_grams)
- кот принимает пишу. В аргументах он получает кол-во еды в граммах и пополняет вес на кол-во употребеленной еды.drink(water_ml)
- кот пьет воду. В аргументах он получает кол-во воды в миллилитрах и пополняет вес в зависимости от выпитой воды в соотношении 2мл/1г весаstatus
- если вес кота больше 9000г или меньше 1000г, то возвращается статусDEAD
, в остальных случаях возвращается статусLIVE
реализуйте счетчик котов, который будет увеличиваться каждый раз при создании нового кота.
Попробуйте создать в файле main.rb
несолько экземпляров вашего класса и повызывайте методы вашего кота, чтобы удостовериться, что кот жив.
Рекомендации:
Подумайте как лучше организовать хранение статусов кота.
не бойтесь вводить новые переменные по необходимости. Эта рекоммендация работает и для поледующих заданий.
Дополнительный материал
Last updated