Инкапсуляция
Прежде чем переходить к инкапсуляции давай поговорим про то, как можем получать доступ к нашим атрибутам из вне. Возьмем тот же класс Person
:
class Person
def initialize(name, age)
@name = name
@age = age
end
# создаем метод name, который будет нам возвращать атрибут name
def name
@name
end
# создаем метод, который позводлит нам менять значение атрибута name с помощью знака `=`
def name=(new_name)
@name = new_name
end
# создаем метод age, который будет нам возвращать атрибут age
def age
@age
end
# создаем метод, который позводлит нам менять значение атрибута age с помощью знака `=`
def age=(new_age)
@age = new_age
end
end
mike = Person.new('Mike', 23)
puts mike.name # = Mike
puts mike.age # = 23
# тот самый метод name=(new_name)
mike.name = 'Mike Brown'
# тот самый метод age=(new_age)
mike.age = 21
puts mike.name # = Mike Brown
puts mike.age # = 21
По сути мы написали обычные методы-обертки для каждого нашего атрибута для того, чтобы менять или получать значения конкретных атрибутов.
Метод, который позволяет получать значение атрибута называется обычно Getter
, а метод, который позволяет изменять это значение, называется Setter
.
Ruby позволяем нам сильно сократить за счет attribute accessors
. Существует 3 типа аксессоров:
attr_reader
- создает невидимый метод для чтения атрибутаattr_writer
- создает невидимый метод для записи атрибутаattr_accessor
- создает невидимые методы записи и чтения атрибута
Давайте посмотрим как может выглядеть наш класс Person
с использованием аксессоров:
class Person
# Хоть мы и не создаем метод name, name=, age, age=, но мы все равно получаем такие методы при использовании нашего класса
# Если бы мы использовали attr_reader :name, :age, значит у нас бы создался невидимый метод name и age для получения значений
# Если бы мы использовали attr_writer :name, :age, значит у нас бы создался невидимый метод name= и age= для изменения значений
# Обрати внимание, что мы передаем не просто названия наших атрибутов, а передаем символы с таким же названием как и у атрибутов
attr_accessor :name, :age
def initialize(name, age)
@name = name
@age = age
end
end
mike = Person.new('Mike', 23)
# работает точно так же как и в случае с методами name и age, но уже за счет attr_accessor
# Такая же возможность у нас была, если бы мы использовали attr_reader
puts mike.name # = Mike
puts mike.age # = 23
# тот самый метод name=(new_name)
mike.name = 'Mike Brown'
# тот самый метод age=(new_age)
mike.age = 21
# работает точно так же как и в случае с методами name= и age=, но уже за счет attr_accessor
# Такая же возможность у нас была, если бы мы использовали attr_writer
puts mike.name # = Mike Brown
puts mike.age # = 21
Как мы видим количество написанного кода внутри нашего класса Person
заметно снизилось.
А при чем тут инкапсуляция ? Инкапсуляция - это принцип ООП, который заставляет нас уберегать наши объекты от нежелательного воздействия из вне. Не всегда нам нужна возможность менять наш класс снаружи как угодно, иногда это может сломать нашу логику или затруднить использование нашего кода для посторонних разработчиков. Возьмем к примеру объект из реальной жизни. Нужно ли нам давать пользователю знать о внутренностях автомобиля для того, чтобы пользователь начал ездить на этой машине ? Нет, пользователь должен знать как ездить на машине, а не как она устроена, для этого производители машин скрывают от пользователя реализацию автомобиля. В программировании это работает точно также. Нам не нужно давать пользователю все возможности для работы с нашим объектом, если конечная цель использования с этим не связана.
Для того, чтобы в ruby защитить наш объект от нежелательного использования у нас есть такие инструменты как: attribute accessors
, а также приватные методы и публичные.
Публичные методы - это методы, которыми мы можем пользоваться снаружи класса, вызывая их у объектов этих классов. Методы которые мы рассматривали раньше были публичными.
Приватные методы - это методы, которыми мы можем пользоваться только внутри нашего класса, вызывая их из других методов, например. Обычно приватные методы создают, если нам нужно в разных публичных методах переиспользовать один и тот же кусок кода, а также для разбиения большого методы на более мелкие. Это позволяет снизить сложность прочтения кода вашего приложения.
class Car
# Пользователь не должен самостоятельно менять значение этого атрибута, так как это может поломать логику нашей программы
attr_reader :mileage # публичный метод для получения значения переменной mileage
def initialize
@mileage = 0
end
# пользователь может только добавлять километраж, но не может изменять его на свое усмотрение
def drive(miles)
@mileage += miles
end
def start
end
# для того, чтобы пользователь смог поехать на нашем автомобиле ему не нужно уметь самостоятельно запускать двигатель
# У нас может быть метод запуска машины, который будет запускать сразу нескольких компонентов машины
# но запускать только двигатель конечному пользователю уметь не нужно
private # все методы, определенные ниже, являются приватными
def engine_start
# действия, необходимые для запуска двигателя
end
# а также ему не нужно уметь его останавливать
# У нас может быть метод остановки машины, который будет запускать остановку сразу нескольких компонентов машины
# но останавливать только двигатель конечному пользователю уметь не нужно
def engine_stop
# действия, необходимые для остановки двигателя
end
end
car = Car.new
# метод drive публичный, значит мы можем им воспользоваться
car.drive(10)
# mileage имеет attr_reader, за счет которого мы можем получить значение километража
puts "Пробег: #{car.mileage} км"
# при этом если мы попробуем изменить километраж с помощью car.mileage = 10, то мы получим ошибку
# такое же поведение будет если мы попробуем вызвать метод engine_start или engine_stop
Возможно по началу будет тяжело в этом ориентироваться, но если каждый раз задавать вопрос себе А нужен ли публичный доступ для данного метода ?
или А нужен ли acessor для изменения атрибута ? Не сломает ли он нашу программу, если пользователь захочет напрямую изменить его ?
.
Инкапсуляция помогает сделать код более безопасным и уменьшить количество взаимодействия между объектами, снижая риск возникновения ошибок в ходе работы.
Часто можно услышать такое понятие как API
и многие ошибочно считают, что это касается только сетевого взаимодействия, но API
и инкапсуляция тесно между собой связаны. API
- это итоговая реализация инкапсуляции нашей программы, которая выстраивает все способы взаимодейсвия с нашей программы. Сам Ruby тоже предоставляем нам API
для разработки программ. Взять тот же String
. Что такое строка для компьютера ? Это всего лишь набор байтов доступ к которым Ruby нам не дает. Вместо этого мы получаем набор методов для взаимодейсвия с нашей строкой, а также публичные доступы до определенных переменных на чтение, которые позволяют получить полезную информацию о нашей строке.
Задание animals
Внимание ! Это и последующие задания данного модуля с названием animals
необходимо выполнять в одной и той же директории в корне модуля - 3_oop/animals
выставьте для вашего класса
Cat
те права доступа и акссесоры, которые по вашему мнению большего всего подходят.Реализуйте логику подсчета суммарно съеденной еды, а также счетчик суммарно выпитой воды.
создайте экземпляр класса кота и 'замяйкайте' его до сметри.
создайте еще одие экзампляр класса кота и перекормите его до смерти.
Задание elevator
Создайте класс Elevator, эмулирующий работу пассажирского лифта. В классе создайте:
Переменные
current_floor
(текущий этаж),min_floor
иmax_floor
(минимальный и максимальный этажи).Конструктор с двумя параметрами
min_floor
иmax_floor
, сохраняющий эти параметры в соответствующих переменных класса.Значение переменной
current_floor
изначально должно быть равно 1.Метод
get_current_floor
, возвращающий текущий этаж, на котором находится лифт.Метод
move_down
, перемещающий лифт на один этаж вниз (уменьшающий значение переменнойcurrent_floor
на единицу).Метод
move_up
, перемещающий лифт на один этаж вверх.Метод
move(floor)
, перемещающий лифт на заданный в параметре этаж, если он задан верно. Если параметр у метода задан неверно, ничего не делать и выводить в консоль сообщение об ошибке. Этот метод может перемещать лифт только последовательно, по одному этажу, с помощью циклов и методовmove_up
иmove_down
, и он должен выводить в консоль текущий этаж после каждого перемещения между этажами.
elevator = Elevator.new(-3, 36)
loop do
puts 'Введите номер этажа: '
floor = gets.chomp.to_i
elevator.move(floor)
end
Этот код поможет вам протестировать созданный класс Elevator: он будет создавать лифт и в консоли запрашивать этаж, на который нужно переместить лифт, после чего вызывать у него метод move с указанием полученного из консоли этажа. Запустите получившийся код и убедитесь, что он работает корректно.
Дополнительный материал
Last updated