Наш чатик

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

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

Классы и объекты в Ruby

Сегодня мы научимся писать данные в файлы, узнаем как работать со временем в Ruby, напишем программу-дневник.

План урока

  1. Как работать со временем в Ruby
  2. Запись в файлы, пишем дневник

Классы! Великие и могучие!

В программах мы постоянно оперируем объектами, мы уже говорили об этом в 4-м уроке: строки, числа, массивы.

Объекты Миша, Маша и Вадим

Наши объекты хранятся с помощью переменных: неких ярлыков, которые позволяют обращаться к объектам по имени.

Вы уже знаете, что в Ruby есть много разных видов объектов: строки (String), целые числа (Fixnum), массивы (Array). Пришло время осознать, что этих типов гораздо больше: есть ещё файлы (File), ассоциативные массивы (Hash), метки (Symbol) даже моменты времени (Time) и даты (Date), а также много-много всего другого.

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

Типы объектов в программировании называются классами. Ruby не исключение.

Ruby вообще очень высокоразвитый язык, там любая закорючка — это объект какого-то класса. Но это так, лирическое отступление.

Напомню, что посмотреть класс любого объекта можно вызвав у этого объекта по цепочке методы .class и .name.

Что такое методы объектов вам станет понятно к концу этого урока, а пока просто напомним, что

puts "Я строка".class.name

выведет на экран String, а

puts ["А","я","массив","строк"].class.name

выведет на экран Array.

Для чего создаются классы?

Класс — это некое описание типа объектов, которые можно создавать. Прежде чем человек создал первый паровоз, он как-то описал (на бумаге, в своем воображении, в чертежах) новое для того времени понятие «паровоз». Он наверняка придумал какими свойствами должен обладать паровоз, как он должен функционировать и так далее.

Паровоз!

Другими словами он придумал новое понятие, новый тип объектов «паровоз». Программисты бы сказали — создал класс Паровоз.

И уже потом человечество начало производить различные конкретные паровозы, создавать объекты этого класса.

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

До сих пор мы использовали встроенные в Ruby классы (строки, числа, массивы) – мы создавали объекты этих классов и с ними игрались. Нам не нужно было описывать эти классы, ведь они уже описаны в самом языке Ruby.

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

Давайте определимся с философским вопросом: «Когда нужно создавать новые классы». Сразу скажем, что понимание это приходит с опытом, поэтому не бойтесь экспериментировать и делать классы когда считаете это нужным.

Ругать вас за это никто не будет. Дадим лишь несколько советов, как понять, что ситуация требует именно нового класса, а не просто метода.

1. Если вы понимаете, что некую часть вашей программы можно выделить в независимый объект. Объект со своим собственным поведением и свойствами.

Главное, чтобы вы чётко понимали, как вы объясните другому человеку, что это за класс. Если вы можете сформулировать на русском языке название класса в виде простого слова — это хороший признак, что он заслуживает существования.

Например, для нашей виселицы из предыдущего урока можно было бы создать класс: «Печатальщик результата», который бы занимался всем, что связано с выводом информации для пользователя в консоль.

2. Если в языке программирования не предусмотрен какой-то уже имеющийся класс для вашей цели.

Чаще всего просто погуглив, вы либо найдёте нужный класс в Ruby, либо поймёте, как в этой ситуации поступают другие люди. Если же информацию быстро найти не удалось, смело делайте свой класс.

3. Если в вашей программе есть абстрактная модель чего-то.

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

Это всё определяется в момент проектирования программы, подробнее об этом процессе мы говорили в 10-м уроке.

Как создать класс в Ruby?

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

Иногда классы даже включаются в структуру языка, как это стало с классами строки String и момента времени Time, настолько они удобные.

Классы принято описывать в отдельных файлах. Каждому классу — свой файл. Это существенно упрощает понимание программы.

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

Для примера создадим класс Мост (Bridge), который мы опишем в файле bridge.rb (как обычно, положив его в новую папку c:\rubytut\lesson11):

class Bridge
  # Описание класса
end

Чтобы Ruby понял, что это не просто код, а описание класса, мы обернули его в конструкцию class-end. Обратите внимание, что все имена классов в Ruby (в других языках тоже) обычно начинаются с большой буквы.

Если бы класс состоял из двух слов, то второе слово тоже было бы с большой буквы

class RoadBridge
  ...
end

Внутри конструкции class-end мы пишем методы нашего класса. Как мы уже знаем, методы описываются с помощью конструкции def-end.

Особое внимание следует обратить на метод initialize — это так называемый конструктор класса, но об этом чуть позже.

Создание экземпляра класса

Пока мы просто описали класс, ничего интересного не произойдёт. Нужно создать хотя бы один объект этого класса. Для этого нам в нашей основной программе doroga.rb необходимо подключить файл bridge.rb с описанием класса Bridge.

Мы умеем делать это с помощью команды require:

require "bridge.rb"

После этого можно создать новый объект нашего нового класса Bridge. Для этого мы пишем

bridge = Bridge.new

Это очень важный момент! Давайте разберёмся, что значит каждое слово в этой записи.

Во-первых, что такое bridge, оно написано маленькими буквами, значит это не класс, а объект, вернее переменная.

Во-вторых знак равно (=), он означает, что мы в переменную bridge хотим что-то записать, хотим, чтобы переменная bridge указывал на то, что будет справа от знака равно.

В-третьих, мы видим название нашего нового класса Bridge. Мы только что описали этот класс в отдельном файле bridge.rb и подключили его (файл) с помощью команды require.

Наконец, .new — мы вызвали у нашего класса специальный метод, который как бы говорит классу: «О великий и могучий! Создай для нас свое земное воплощение в виде конкретного объекта!»

А класс отвечает: «Так и быть, я сжалюсь над тобой, смертный и дам тебе свой экземпляр, но при одном условии — я сразу же вызову у этой копии метод initialize».

Поэтому метод .new возвращает новый объект данного класса.

Причем при создании объекта у него вызывается специальный метод с именем initialize. Такой метод в программировании называется конструктор.

class Bridge
  def initialize
    puts "Мост создан"
  end
end

Вы можете объявить в классе такой метод и написать в нем какой-то функционал – тогда этот функционал будет выполнен один раз при создании каждого объекта этого класса. Но можно и не писать, тогда конструктор будет пустой, объект создастся без каких-то дополнительных действий.

Конкретный объект какого-то класса в программировании называется экземпляр класса. По-английски instance. Запомните эти слова, вот увидите, они несут свет озарения в чистом виде! ;)

Конечно, всю эту драму придумали разработчики, чтобы было удобнее создавать новые классы. В методе initialize, который вызывается каждый раз, когда создаётся новый объект указанного класса, описывается, что должно произойти с экземпляром класса перед тем, как он будет создан. Если это класс книги, например, то нужно заполнить её название и год издания. Может быть ещё имя и фамилию автора и жанр. Всё на усмотрение разработчика класса.

Ещё раз, объект (экземпляр класса) и класс — это разные вещи, как есть вот мы, «Миша» и «Вадим» — объекты, а есть «Человек» — класс, некий собирательный образ, абстракция для всех людей на Земле (и на её орбите, а возможно и в других галактиках).

Итак, мы создали новый экземпляр класса Bridge и сделали так, что переменная bridge указывает на этот объект.

Если мы напишем

puts bridge.class.name

То увидим название нашего класса Bridge.

А теперь смертельный номер. Просьба всех слабонервных удалиться. Если всё в Ruby это экземпляр какого-то класса, то что же тогда такое этот наш Bridge? Какого будет вам узнать, что это тоже объект! «Какой же у него класс?» — спросите вы. Посмотрите сами, вы уже не маленькие.

Использование методов класса

def open
  puts "Мост открыт, можно ехать"
end

Внутри нашего класса Bridge мы написали метод open. Этот метод на самом деле есть не у самого класса, а именно у его экземпляра.

Мост закрыт, ехать нельзя!

Для того, чтобы «открыть» мост (объект класса Bridge), на который указывает переменная bridge, нам необходимо вызвать у этого объекта метод open. Это делается очень просто и изящно:

bridge.open

и мы увидим в консоли наш текст открытия моста:

Мост открыт, можно ехать!

Мост открыт, можно ехать!

Именно вызов метода экземпляра класса мы делали, когда вызывали у массива, например, метод to_s:

array = [1,2,3]
puts array.to_s

выводит в консоль "[1, 2, 3]" — мы вызываем у объекта array (экземпляра класса Array) метод to_s, который возвращает этот массив но уже как строку (экземпляр класса String).

Поля класса

В методы класса, как и в обычные методы можно передавать параметры, как и обычные методы, они возвращают (или нет) какие-то значения.

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

Например, наш мост bridge (экземпляр класса Bridge) может быть каменным или деревянным, длинным или коротким, узким или широким, пешеходным или автомобильным (или даже железнодорожным) и так далее.

Давайте сделаем наш мост открывающимся и для этого создадим поле класса opened (открыт). В руби поля класса начинаются с символа «собаки» — @ (чтобы не путались с методами), поэтому в конструкторе мы опишем поведение моста по умолчанию в таком виде:

def initialize
  puts "Мост создан"
  @opened = false
end

а в метод open добавим изменение этого внутреннего поля на true

def open
  @opened = true
  puts "Мост открыт, можно ехать"
end

Все важные поля вашего объекта должны быть объявлены в конструкторе! Вам нужно сообщить Ruby заранее какими свойствами будут обладать объекты вашего класса.

Текущее значение всех полей какого-то объекта определяют так называемое состояние объекта. Фактически один объект отличается от другого объекта того же класса своим состоянием (один мост открыт, другой закрыт, например).

Мы также напишем новый метод 'is_opened?', который будет возвращать true, если мост открыт и false, если закрыт:

def is_opened?
  return @opened
end

Программисты Ruby договорились между собой, что все методы, которые возвращают true или false, будут заканчиваться знаком вопроса. В других языках как правило знак вопроса не используют в названиях методов.

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

В самой программе doroga.rb мы теперь перепишем открытие моста только для случая, когда мост закрыт:

if !bridge.is_open?
  bridge.open
end

После этого наш мост откроется и напишет Мост открыт, можно ехать!.

Ещё раз обратим ваше внимание, что если мы создадим новый мост

another_bridge = Bridge.new

то этот новый мост будет закрыт. another_bridge.is_open? вернёт false.

Надо просто немного привыкнуть к этой концепции класс-объект. После небольшой практики вы будете в этом как рыба в воде.

Кстати, рыба и селедка — селедка это объект (если конкретная селедка, вот эта).

Селёдка

А просто "рыба" это уже класс ;)

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

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