> For the complete documentation index, see [llms.txt](https://evgenii-afanasev.gitbook.io/ruby-course/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://evgenii-afanasev.gitbook.io/ruby-course/3_oop/1_class_methods_objects.md).

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

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

```ruby
# инициализация класса с названием Person. 
class Person
  # конструктор класса, а также его аргументы
  def initialize(name, age, address)
    # присвоение аргументов конструктора к аргументам объекта
    @name = name
    @age = age
    @address
  end
end
```

Названия классов следует всегда записывать в формате `CamelCase` (Каждое слово в названии пишется с заглавной буквы), например `MagicBall`.

Ранее при создании объекта вы встречали такое слово как `new`. Фактически, вызывая метод `new` у класса вы вызываете его конструктор. В конструкторе вы можете определить сколько угодно аргументов как и у любого другого метода, но сами по себе аргументы никакого отношения к самому классу не имеют, это просто набор параметров, которые можно передать при создании объекта. Для того, чтобы аргументы стали частью объекта, его атрибутами, необходимо создать эти самые атрибуты и прировнять их к аргументам. атрибуты объекта создаются с помощью знака `@` перед названием самого атрибута. Сделав это, ваш объект при создании уже будет иметь какое-то собственное состояние.

## Объекты

Давайте посмотрим как нам теперь создать наш объект на основе этого класса:

```ruby
# Создаем объект класса 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)
```

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

## Методы

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

```ruby
# инициализация класса с названием Person
class Person
  # конструктор класса, а также его аргументы
  def initialize(name, age, address)
    # присвоение аргументов конструктора к аргументам объекта
    @name = name
    @age = age
    @address
  end

  # Как мы помним и человека раз в год день рождения, значит мы бы хотели, чтобы наш человек мог взрослеть
  def birthday
    @age += 1
  end
end
```

```ruby
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 объектов, кол-во созданных переменных класса не меняется.

```ruby
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
```

```ruby
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)
```

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

```ruby


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
```

```ruby
# Объект класса 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`, при этом значение все равно поменяется.

```ruby
ABC = 1
ABC = 2 # warning: already initialized constant ABC
```

К сожалению у нас нет возможности сделать константу неизменяемой, но у нас есть возможность сделать значение иммутабельным

```ruby
# .freeze позволяет нам защитить значение от изменения
ABC = "ABC".freeze
# << позволяем нам изменить саму строку не меняя самое значение в переменной. Более подробно поговорим в следующих главах
# Ели бы у нас был массив ['hello', 'world'].freeze, то при попытке добавить значение через .push(element), мы бы получили такую же ошибку
ABC << "CBA" # RuntimeError: can't modify frozen String
```

Давайте посмотрим как это выглядит для класса:

```ruby
class Car
  MAX_SPEED = 100

...
end
```

Так же хорошей практикой является создавать отдельные классы для хранения однотипных констант

```ruby
# константы класса используют часто как способ группировки констант по общему признаку
class Color
  GREEN = 0
  BLUE = 1
  RED = 3
end
```

```ruby
# пример вызова константы класса
puts Color::BLUE
```

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

## self

В прошлым примерах вы уже сталкивались с ключевым словом `self`, а теперь стоит разобрать что это такое. Мы привыкли уже создавать наши объекты, помоещать их в переменную6 а после передавать эту переменную куда-то еще (либо без переменной, сразу как значение передавать), но что если мы хотим получать доступ к объекту сразу изнутри этого самого объекта ? `self` позволяет нам внутри нашего класса обращаться к будущему объекту, который будет создан на основе этого класса. Звучит сложно, но с примером будет полегче

```ruby
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
```

```ruby
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` несолько экземпляров вашего класса и повызывайте методы вашего кота, чтобы удостовериться, что кот жив.

Рекомендации:

* Подумайте как лучше организовать хранение статусов кота.
* не бойтесь вводить новые переменные по необходимости. Эта рекоммендация работает и для поледующих заданий.

## Дополнительный материал

* [Классы и объекты в Ruby](https://www.rubyguides.com/2019/02/ruby-class/)
* [Константы](https://www.rubyguides.com/2017/07/ruby-constants/)
* [Переменные класса VS переменные объекта](https://stackoverflow.com/questions/15773552/ruby-class-instance-variable-vs-class-variable)
* [self](http://rubydev.ru/2012/09/self-ruby/)
