Справка
Телеграм чат начинающих программистов. Общаемся и помогаем друг другу
Если ссылка не открывается, можно найти нас в поиске по чатам @rubyrush
или
пойти другим путем
В этом уроке мы рассмотрим непростую тему наследования классов. Мы научимся писать классы-детей, наследующие поведение от классов-родителей. А также, спроектируем программу «Блокнот», в которой помимо обычных заметок будут также задачки и ссылки.
Чтобы понять, что такое наследование, давайте рассмотрим в качестве примера понятие Средство транспортировки:
Средством транспортировки может быть что угодно: машина, корабль, самолёт или даже газовая или нефтяная труба — средство транспортировки горючих материалов куда надо и кому надо.
У каждого средства транспортировки, вне зависимости от его типа, есть как минимум вот такие свойства и методы
СредствоТранспортировки
Свойства:
@пропускная_способность
@год_запуска_в_эксплуатацию
Методы:
начать_транспортировку
остановить_транспортировку
Давайте начнём конкретизировать. Корабль — это тоже средство транспортировки, но у него, помимо указанных выше, есть ещё несколько методов и свойств (какой бы корабль мы ни рассматривали):
Корабль:
Все методы и свойства «СредстваТранспортировки» плюс:
Свойства:
@скорость_хода
@водоизмещение
Методы:
начать_движение
остановиться
Наконец, добираемся до чего-то совсем конкретного (конкретнее только указать кадастровый номер какого-нибудь судна): рассмотрим моторную лодку:
МоторнаяЛодка:
Все методы и свойства «Корабля» плюс:
Свойства:
@текущая_температура_двигателя
@расход_топлива
Методы:
завести_мотор
заглушить_мотор
Получается, что эти понятия как бы вложены друг в друга: МоторнаяЛодка < Корабль < СредствоТранспортировки
.
Для описания таких структур в объектно-ориентированных языках программирования придумали понятие наследования.
Класс — это чертеж, по которому создается объект какой-то определенной группы.
А теперь представьте, что у нас много похожих классов, каждый из которых мы можем использовать для создания объектов. Тогда мы можем сделать класс для создания классов и наследовать классы уже от него.
Наследование позволяет не указывать весь набор свойств для каждого класса, а выделить часть общих свойств будущих объектов в отдельный чертеж (родительский класс), а дочерние классы могут расширить/изменить это поведение/свойства. Всё как в жизни — дети похожи на родителей, но идут дальше и сильно отличаются в деталях.
При этом родительский класс, как правило, ещё абстрактнее, чем его дети: «транспорт» куда абстрактнее «моторной лодки».
Этот шаблон широко используется в современных фреймворках (например, в популярном фреймворке для создания веб-приложений Ruby on Rails). Именно поэтому так важно понять эту концепцию.
Чтобы закрепить в голове понятие наследования и уяснить, зачем оно вообще нужно, давайте напишем программу-дневник, в который можно писать три разных типа записей: заметки, задачки и ссылки.
Воспользуемся программой из 17-го урока (вы можете также взять её из материалов к этому уроку). Мы будем её развивать на протяжении этого и следующего урока.
Итак, у нас будет три типа записи: заметка, ссылка и задача.
Давайте для начала разберёмся со свойствами.
Для заметки нам понадобятся такие поля
@text # содержание заметки
@created_at # дата создания заметки
Для ссылки нам понадобятся такие поля
@url # адрес ссылки
@description # описание ссылки
@created_at # дата создания ссылки
А для тудушки — такие:
@text # тело задачи
@due_date # к какому числу задачу нужно сделать
@created_at # дата создания задачки
Как вы видите, у всех записей есть общее поле @created_at
, также если присмотреться, у всех классов есть поле с текстом @text
, только у ссылки оно называется @description
. Большой беды не будет, если мы его переименуем.
Поля @created_at
и @text
мы вынесем в родительский класс Post
. Дочерний класс Memo
не будет иметь дополнительных полей, дочерний класс Task
будет иметь дополнительное поле @due_date
, а дочерний класс Link
будет иметь дополнительное поле @url
.
Итак, выделим общие поля в родительский класс Post
. Для этого в папке урока rubytut2/lesson6
создайте папку notepad
и в ней создайте файл post.rb
с описанием класса Post
. Мы опишем в этом классе общую логику работы с записями:
# Базовый класс "Запись"
# Задает основные методы и свойства, присущие всем разновидностям Записи
class Post
# Конструктор
def initialize
@created_at = Time.now # дата создания записи
@text = nil # массив строк записи — пока пустой
end
# Этот метод вызывается в программе, когда нужно
# считать ввод пользователя и записать его в нужные поля объекта
def read_from_console
# должен быть реализован классами-детьми,
# которые знают как именно считывать свои данные из консоли
end
# Этот метод возвращает состояние объекта в виде массива строк, готовых к записи в файл
def to_strings
# должен быть реализован классами-детьми,
# которые знают как именно хранить себя в файле
end
# Этот метод записывает текущее состояние объекта в файл
def save
# он будет только у родителя, его мы напишем позже
end
end
# PS: Весь набор методов, объявленных в родительском классе называется интерфейсом класса
# Дети могут по–разному реализовывать методы, но они должны подчиняться общей идее
# и набору функций, которые заявлены в базовом (родительском классе)
Напишем класс Memo
, который унаследует основной функционал от класса Post
.
# Класс "Заметка", разновидность базового класса "Запись"
class Memo < Post
# отдельный конструктор здесь не нужен, т. к. он совпадает с родительским
# Этот метод пока пустой, он будет спрашивать ввод содержимого Заметки
# наподобие программы Дневник из "базового блока" курса
def read_from_console
end
end
Класс ссылки тоже будет наследоваться от класса Post
:
# Класс Ссылка, разновидность базового класса "Запись"
class Link < Post
def initialize
super # вызываем конструктор родителя
# потом инициализируем специфичное для ссылки поле
@url = ''
end
# Этот метод пока пустой, он будет спрашивать 2 строки — адрес ссылки и описание
def read_from_console
end
# Массив из трех строк: адрес ссылки, описание и дата создания
# Будет реализован в след. уроке
def to_strings
end
end
Наконец, опишем в файле task.rb
класс Task
, который наследует себя от класса Post
:
# Подключим встроенный в руби класс Date для работы с датами
require 'date'
# Класс Задача, разновидность базового класса "Запись"
class Task < Post
def initialize
super # вызываем конструктор родителя
# потом инициализируем специфичное для Задачи поле - дедлайн
@due_date = Time.now
end
# Этот метод пока пустой, он будет спрашивать 2 строки - описание задачи и дату дедлайна
def read_from_console
end
# Массив из трех строк: дедлайн задачи, описание и дата создания
# Будет реализован в след. уроке
def to_strings
end
end
В этом уроке мы познакомились с идеей наследования классов. Но нам не хватает некоторых понятий для того, чтобы дописать наш блокнот...