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

Как мы ранее говорили, класс - это шаблон нашего объекта. В нем мы описываем какие атрибуты могут быть у объекта и какие методы у него есть, с помощью которых мы можем взаимодействовать с объектом. Давайте посмотрим как создать наш класс:

# инициализация класса с названием 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