Наш чатик

Телеграм чат начинающих программистов. Общаемся и помогаем друг другу

Если ссылка не открывается, можно найти нас в поиске по чатам @rubyrush или пойти другими путем

Классы, наследование

В этом уроке мы рассмотрим непростую тему наследования классов. Мы научимся писать классы-детей, наследующие поведение от классов-родителей. А также, спроектируем программу «Блокнот», в которой помимо обычных заметок будут также задачки и ссылки.

План урока

  1. Наследование классов
  2. Готовим классы для нового блокнота

Концепция наследования

Чтобы понять, что такое наследование, давайте рассмотрим в качестве примера понятие Средство транспортировки:

Средством транспортировки может быть что угодно: машина, корабль, самолёт или даже газовая или нефтяная труба — средство транспортировки горючих материалов куда надо и кому надо.

У каждого средства транспортировки, вне зависимости от его типа, есть как минимум вот такие свойства и методы

СредствоТранспортировки

Свойства:
@пропускная_способность
@год_запуска_в_эксплуатацию

Методы:
начать_транспортировку
остановить_транспортировку

Давайте начнём конкретизировать. Корабль — это тоже средство транспортировки, но у него, помимо указанных выше, есть ещё несколько методов и свойств (какой бы корабль мы ни рассматривали):

Корабль:
Все методы и свойства «СредстваТранспортировки» плюс:

Свойства:
@скорость_хода
@водоизмещение

Методы:
начать_движение
остановиться

Наконец, добираемся до чего-то совсем конкретного (конкретнее только указать кадастровый номер какого-нибудь судна): рассмотрим моторную лодку:

МоторнаяЛодка:
Все методы и свойства «Корабля» плюс:

Свойства:
@текущая_температура_двигателя
@расход_топлива

Методы:
завести_мотор
заглушить_мотор

Получается, что эти понятия как бы вложены друг в друга: МоторнаяЛодка < Корабль < СредствоТранспортировки.

Для описания таких структур в объектно-ориентированных языках программирования придумали понятие наследования.

Транспортом — Кораблём — Моторной лодкой

Что такое наследование и зачем оно нужно?

Класс — это чертеж, по которому создается объект какой-то определенной группы.

А теперь представьте, что у нас много похожих классов, каждый из которых мы можем использовать для создания объектов. Тогда мы можем сделать класс для создания классов и наследовать классы уже от него.

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

Наследование классов

При этом родительский класс, как правило, ещё абстрактнее, чем его дети: «транспорт» куда абстрактнее «моторной лодки».

Этот шаблон широко используется в современных фреймворках (например, в популярном фреймворке для создания веб-приложений 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

Итак, выделим общие поля в родительский класс 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

Напишем класс Memo, который унаследует основной функционал от класса Post.

# Класс "Заметка", разновидность базового класса "Запись"
class Memo < Post

  # отдельный конструктор здесь не нужен, т. к. он совпадает с родительским

  # Этот метод пока пустой, он будет спрашивать ввод содержимого Заметки
  # наподобие программы Дневник из "базового блока" курса
  def read_from_console
  end
end

Дочерний класс Link

Класс ссылки тоже будет наследоваться от класса Post:

# Класс Ссылка, разновидность базового класса "Запись"
class Link < Post

  def initialize
    super # вызываем конструктор родителя

    # потом инициализируем специфичное для ссылки поле
    @url = ''
  end

  # Этот метод пока пустой, он будет спрашивать 2 строки — адрес ссылки и описание
  def read_from_console
  end

  # Массив из трех строк: адрес ссылки, описание и дата создания
  # Будет реализован в след. уроке
  def to_strings
  end
end

Дочерний класс Task

Наконец, опишем в файле 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

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