# Лекция 3
## Классы и объекты
---
### Структура лекции
* Понятие объекта
* Понятие класса
* Пример
* Методы
* Методы - мутаторы
#### Синтаксис классов
* Определение класса
* Определение методов
* Создание объекта
* Инициализация
* Использование аттрибутов
* Создание объекта с атрибутами
* Методы мутаторы
* Использование мутаторов
* Синтаксический сахар
* name=
* getter, setter, accessor
* Взаимодействие между классами
* Класс Cобака
* Класс Хозяин
* Использование
---
### Понятие объекта
Ruby - объектно ориентированный язык в самом прямом смысле этого слова. Каждое значение в ruby является объектом.
Объект - это сущность, обладающая некоторыми свойствами, и имеющая набор методов для работы с этими свойствами.
**Например:**
*Маленький зеленый ящик - объект со свойствами: размер - маленький и цвет - зеленый.
Так же у ящика могут быть методы: ящик можно открыть, положить в него что-то, запереть или покрасить*
Обратите внимание открыть и покрасить можно конкретный "маленький зеленый ящик", а не абстрактное понятие "ящик".
---
### Понятие класса
Параметры и методы объекта определяются в описывающем его классе. Объекты называют экземплярами или инстансами классов. И так же говорят, что объект имеет некоторый тип.
**Например:**
*Ящик - это класс. В нем можно определить свойства размер и цвет, и тогда маленький зеленый ящик из предыдущего примера будет объектом данного класса.*
Класс - это синтаксическая конструкция, которая позволяет описать новый тип объектов. При этом стоит помнить, что сам класс тоже является объектом, и у него также есть свойства и методы, которые называют свойствами класса и методами класса.
---
### Пример
Создадим класс "Собака". Мы можем описать этот класс, определив параметры и методы, например:
**Свойства:** *размер, порода, кличка.*
**Методы:** *бег, кличка, назвать*.
Теперь мы можем создать несколько объектов данного типа:
*песик1 - экземпляр класса “Собака” ( кличка: Бобик, размер: маленький, цвет: черный)
песик2 - другой объект этого типа ( кличка: Рыжик, размер: большой, цвет: коричневый )*
У каждого объекта есть несколько свойств. Совокупность этих свойств называется его состоянием.
---
Можно привести еще один пример:
Пусть у нас есть чертеж дома - это класс. Схема, которая показывает что-и где должно находиться.
Дом, построенный по этом чертежу, - объект данного класса. Он соответствует приведенной схеме, но вот покрашен может быть по другому, мебель можно расставить по разному и.т.д.
---
### Методы
Попробуем вызвать методы описанных ранее объектов:
```
песик1.кличка -> Бобик;
песик2.кличка -> Рыжик
песик1.бег -> пройдено 2 метра
песик2.бег -> пройдено 8 метров
```
Методы экземпляров класса чаще всего возвращают некоторую информацию, об объекте, либо некоторую информацию зависящую от параметров объекта. В примере выше метод "кличка" просто возвращает кличку собаки. А метод бег - дает собаке пробежать расстояние, зависящее от размера. Маленькая собака пробегает 2 метра, а большая - уже 8.
---
### Методы - мутаторы
Также методы экземпляров могут модифицировать объекты, на которых они вызываются:
```
песик1.назвать("Дикий");
песик1.кличка -> Дикий
```
Мы изменили атрибут объекта, вызвав специальный метод. И тем самым изменили его состояние. То-есть мутировали объект (изменили его).
---
## Синтаксис классов
---
### Определение класса
Новый класс определяется при помощи специального слова class. Далее следует название класса, обязательно с большой буквы. И завершается определение словом end.
```ruby=
class Speaker
#...
end
```
---
### Определение методов
Методы внутри класса определяются при помощи слов def и end, как и обычные методы. Название метода идет после слове def и начинается с маленькой буквы, следом в скобках идет перечисление атрибутов, которые нужно передать методу.
```ruby=
class Speaker
def hello(name)
puts "Hello #{name}!"
end
end
```
---
### Создание объекта
Новый объект данного класса создается при помощи метода new. Который вызывается у самого класса. После чего объект можно сохранить в переменную, и использовать его собственные методы.
```ruby=
speaker1 = Speaker.new
speaker1.hello('Vasiliy')
# => Hello Vasiliy!
```
---
### Инициализация
Метод new каждого класса вызывает создает объект данного класса и вызывает у него метод initialize, который можно использовать для инициализации объекта с атрибутами. Атрибуты объекта - это переменные, начинающиеся с префикса "@". Такие переменные хранятся вместе с объектом и содержат значения его свойств.
```ruby=
class Speaker
def initialize(name)
@name = name
end
end
```
---
### Использование аттрибутов
На прошлом слайде мы написали метод initialize который принимает переменную name и сохраняет ее в атрибут @name. Обратите внимание что name и @name - разные переменные. Первая - это параметр переданные в метод, второй - переменная объекта, хранящая его свойство. Давайте перепишем метод hello чтобы он использовать свойство @name объекта.
```ruby=
class Speaker
# ...
def hello
puts "Hello #{@name}!"
end
end
```
---
### Создание объекта с атрибутами
Обратите внимание, наш новый метод hello больше не принимает никаких параметров, он использует свойства объекта у которого вызван. А значит надо создать объект с этим свойством. Параметр передается прямо в метод new, который затем передает все параметры в initialize.
```ruby=
speaker1 = Speaker.new('Vasiliy')
speaker1.hello
# => Hello Vasiliy!
```
---
### Методы мутаторы
Как было сказано раньше, методы объектов могут не только возвращать информацию, связанную со свойствами, но и менять эти свойства:
```ruby=
class Speaker
# ...
def set_name(new_name)
@name = new_name
end
end
```
---
### Использование мутаторов
При помощи методов-мутаторов мы можем менять состояние объекта, после чего методы, зависящие от свойств будут возвращать другие данные:
```ruby=
speaker1 = Speaker.new('Vasiliy')
speaker1.hello
# => Hello Vasiliy!
speaker1.set_name('Andrey')
speaker1.hello
# => Hello Andrey!
```
---
### Синтаксический сахар
В руби очень часто можно опускать скобки вокруг параметров передаваемых в метод. Благодаря этой особенности языка можно написать красивый метод присваивания значения свойству:
```ruby=
class Speaker
# ...
def name=(new_name)
@name = new_name
end
end
```
---
### name=
Мы определили метод "name=" - это валидное имя метода и мы вполне можем его использовать так же как и set_name. А если сделать это без скобок, получается следующая конструкция:
```ruby=
speaker1 = Speaker.new('Vasiliy')
speaker1.hello
# => Hello Vasiliy!
speaker1.name = 'Andrey'
speaker1.hello
# => Hello Andrey!
```
---
### getter, setter, accessor
Методы которые устанавливают значения атрибутов, или возвращают их приходится писать очень часто. Ruby позаботился о программистах и в нем имеются соответствующие алиасы: *attr_writer*, *attr_reader* и *attr_accessor* (он создает сразу два метода для чтения и записи.
Ниже будут примеры использования всех трех алиасов:
```ruby=
class Speaker
def name=(new_name)
@name = new_name
end
end
```
```ruby=
class Speaker
attr_writer :name
end
```
---
```ruby=
class Speaker
def name
@name
end
end
```
```ruby=
class Speaker
attr_reader :name
end
```
---
```ruby=
class Speaker
def name
@name
end
def name=(new_name)
@name = new_name
end
end
```
```ruby=
class Speaker
attr_accessor :name
end
```
---
## Пример
Создадим класс **Заказ** который будет содержать список покупок, количество и цену единицы. Заказ должен уметь выводить список позиций и суммарную стоимость, причем если стоимость превышает 100р будет даваться скидка в 10%, а если 200 - 20%.
---
```ruby=
class Order
def initialize
@positions = []
end
def add(title, price, count)
@positions << [title, price, count]
end
def print
printf("% 20s % 15s % 15s % 15s\n", 'Наименование', 'Количество', 'Цена', 'Итого')
@positions.each do |position|
printf("% 20s % 15d % 15.2f % 15.2f\n", position[0], position[2], position[1], position[1]*position[2])
end
printf("% 68.2f\n", total)
end
def total
subtotal = @positions.reduce(0.0) do |result, position|
result += position[1]*position[2]
end
if subtotal >= 200
subtotal * 0.8
elsif subtotal >= 100
subtotal * 0.9
else
subtotal
end
end
end
```
---
### Использование
```ruby=
> order = Order.new
=> #<Order:0x000055d432db1f80 @positions=[]>
> order.add('Пирожок', 12.5, 5)
=> [["Пирожок", 12.5, 5]]
> order.add('Вода', 5.0, 2)
=> [["Пирожок", 12.5, 5], ["Вода", 5.0, 2]]
> order.add('Шоколадка', 15.0, 3)
=> [["Пирожок", 12.5, 5], ["Вода", 5.0, 2], ["Шоколадка", 15.0, 3]]
> order.add('Пакет', 2.5, 1)
=> [["Пирожок", 12.5, 5], ["Вода", 5.0, 2], ["Шоколадка", 15.0, 3], ["Пакет", 2.5, 1]]
> order.print
Наименование Количество Цена Итого
Пирожок 5 12.50 62.50
Вода 2 5.00 10.00
Шоколадка 3 15.00 45.00
Пакет 1 2.50 2.50
108.00
```
Как видно, сумма заказа получилась 120р, при этомбыла учтена скидка 10%. В результате конечная сумма 108.
---
### Взаимодействие между классами
Чаще всего классы используются не по отдельности а совместно. Одни классы могут использовать другие, управлять ими, следить за состоянием объектов и.т.д. Существует множество шаблонов проектирования, которые описывают различные системы взаимодействия между классами. Мы рассмотрим простейший пример:
Напишем два класса:
Собака, у которой есть степень голода, нарастающая при каждой проверке. И хозяин у которого есть собака, и который может ее кормить, снижая степень голода до 0.
---
### Класс Собака
```ruby=
class Dog
attr_writer :hunger
def hunger
@hunger = 0 unless @hunger
puts @hunger
@hunger = @hunger + 1
end
end
```
---
### Класс Хозяин
```ruby=
class Owner
def own_dog=(new_dog)
unless new_dog.is_a?(Dog)
raise 'Should be a Dog!'
end
@own_dog = new_dog
end
def check_hunger
@own_dog.hunger
end
def feed_dog
@own_dog.hunger = 0
end
end
```
---
### Использование
```ruby=
> andrey = Owner.new
=> #<Owner:0x00005632e0e3db18>
> andrey.own_dog = Dog.new
=> #<Dog:0x00005632e0e35508>
> andrey.check_hunger
0
=> 1
> andrey.check_hunger
1
=> 2
> andrey.feed_dog
=> 0
> andrey.check_hunger
0
=> 1
```
---
Как видно в данном примере объект одного класса (Owner) может следить за состоянием объекта другого класса (Dog) через специальные методы. Также он может управлять состоянием этого объекта.
В таком виде использование достаточно бесполезно, но если расширить систему Owner может иметь множество собак и кормить их всех, в результате множество операций может быть заменено на одно *andrey.feed_all_dogs*.
---
```ruby=
class Owner
def initialize
@own_dogs = []
end
def add_dog(new_dog)
unless new_dog.is_a?(Dog)
raise 'Should be a Dog!'
end
@own_dogs << new_dog
end
def feed_all_dogs
@own_dogs.each { |dog| dog.hunger = 0 }
end
end
```