Опубликован: 27.01.2016 | Доступ: свободный | Студентов: 913 / 58 | Длительность: 23:07:00
Лекция 4:

Rails — приправленный Ruby

Ruby классы

Мы говорили, что все в Ruby является объектами, и в этом разделе мы, наконец, определим несколько собственных классов. Ruby, как и многие другие объектно-ориентированные языки, использует классы чтобы организовать методы; эти классы, затем экземплируются для создания объектов. Если вы новичок в объектно-ориентированном программировании, это может звучать как бред, так что давайте рассмотрим несколько конкретных примеров.

Конструкторы

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

>> s = "foobar"   # Буквальный конструктор для строк использующий двойные кавычки
=> "foobar"
>> s.class
=> String

Мы видим здесь, что строки реагируют на метод class, и просто возвращают класс к которому они принадлежат

Вместо использования буквального конструктора, можно использовать аналогичный именованный конструктор, что подразумевает вызов new метода на имени класса:11Эти результаты могут изменятся, в зависимости от версии Ruby, которую вы используете. Этот пример предполагает, что вы используете Ruby 1.9.3.

>> s = String.new("foobar")   # Именованный конструктор для строки
=> "foobar"
>> s.class
=> String
>> s == "foobar"
=> true

Это эквивалентно буквальному конструктору, но это более четко говорит о том, что мы делаем.

Массивы в этом контексте работают так же как строки:

>> a = Array.new([1, 3, 2])
=> [1, 3, 2]

Хэши работают по-другому. Если конструктор массива Array.new принимает начальное значение для массива, Hash.new принимает дефолтное значение для хэша, которое является значением хэша с несуществующим ключом:

>> h = Hash.new
=> {}
>> h[:foo]          # Попытка обратиться к значению несуществующего ключа :foo.
=> nil
>> h = Hash.new(0)  # Теперь несуществующие ключи будут возвращать 0 вместо nil.
=> {}
>> h[:foo]
=> 0

Когда метод вызывается на самом классе, как это происходит в случае с new, он называется методом класса. Результатом вызова new на классе является объект этого класса, также называемый экземпляром класса. Метод вызываемый на экземпляре, такой как length, называется методом экземпляра.

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

При изучении классов, полезно выяснять иерархию классов, используя superclass метод:

>> s = String.new("foobar")
=> "foobar"
>> s.class                        # Поиск класса к которому принадлежит s.
=> String
>> s.class.superclass             # Поиск суперкласса String.
=> Object
# Ruby 1.9 использует новый базовый класс BasicObject:
>> s.class.superclass.superclass
=> BasicObject
>> s.class.superclass.superclass.superclass
=> nil

Диаграмма этой иерархии наследования представлена на рис. 4.1. Мы видим здесь, что суперклассом String является Object , а суперкласс Object это BasicObject, но BasicObject не имеет суперкласса. Эта модель относится к каждому объекту Ruby: можно проследить иерархию классов достаточно далеко, и каждый класс в Ruby в конечном счете наследуется от BasicObject, который не имеет своего суперкласса. Это техническое значение выражения "все в Ruby является объектом".

Иерархия наследования String класса.

Рис. 4.1. Иерархия наследования String класса.

Создание собственного класса незаменимо для более глубокого понимания классов. Давайте сделаем Word класс с palindrome? методом, который возвращает true, если слово можно читать и справа налево и слева направо, сохраняя смысл:

>> class Word
>>   def palindrome?(string)
>>     string == string.reverse
>>   end
>> end
=> nil

Мы можем использовать его следующим образом:

>> w = Word.new              # Создание нового Word объекта.
=> #<Word:0x22d0b20>
>> w.palindrome?("foobar")
=> false
>> w.palindrome?("level")
=> true

Если этот пример поражает вас, как немного надуманный, хорошо; так и было задумано. Довольно странно создавать новый класс только для того чтобы создать метод принимающий строку в качестве аргумента. Поскольку слово это строка, более естественным решением будет унаследовать наш Word класс от String, как это показано в Листинге 4.8. (Вы должны выйти из консоли и ввести его заново, чтобы убрать старое определение Word.)

>> class Word < String             # Word наследует от String.
>>   # Возвращает true если строка соответствует своему перевертышу.
>>   def palindrome?
>>     self == self.reverse        # self это сама строка.
>>   end
>> end
=> nil
Листинг 4.8. Определение Word класса в консоли

Здесь Word < String это Ruby-синтаксис для наследования (кратко обсуждается в Разделе 3.1), который гарантирует, что, в дополнение к новому palindrome? методу, words (слова) также имеют все те же методы, что и строки:

>> s = Word.new("level") # Создать новый объект Word, инициализирует его с "level".
=> "level"
>> s.palindrome?         # Слова имеют palindrome? метод.
=> true
>> s.length              # Слова также наследуют все нормальные методы строк.
=> 5

Так как Word класс наследует от String , мы можем использовать консоль, чтобы увидеть иерархию классов в явном виде:

>> s.class
=> Word
>> s.class.superclass
=> String
>> s.class.superclass.superclass
=> Object

Эта иерархия показана на рис. 4.2.

Иерархия наследования (не встроенного) Word класса из Листинга 4.8.

Рис. 4.2. Иерархия наследования (не встроенного) Word класса из Листинга 4.8.

В Листинге 4.8, обратите внимание, что проверка того, что слово является палиндромом включает в себя вызов слова внутри Word класса. Ruby позволяет нам сделать это, используя ключевое слово self: в Word классе, self является самим объектом, что означает, что мы можем использовать

self == self.reverse

чтобы проверить, является ли слово палиндромом.12Для того чтобы узнать больше о классах Ruby и ключевом слове self, см. RailsTips пост "Class and Instance Variables in Ruby".

Изменение встроенных классов

Хотя наследование это мощная идея, в случае палиндромов может быть даже более естественно добавить palindrome? метод самому классу String, так чтобы (среди прочего) мы могли вызвать palindrome? на буквальную строку, что мы в настоящее время не можем сделать:

>> "level".palindrome?
NoMethodError: undefined method `palindrome?' for "level":String

Несколько удивительно, что Ruby позволяет сделать это; Ruby классы могут быть открыты и изменены простыми смертными, такими как мы, самостоятельно добавляющими методы к ним:13Для знакомых с JavaScript, эта функциональность сопоставима с использованием встроенного class prototype object для расширения класса. (Спасибо читателю Erik Eldridge за указание на это.)

>> class String
>>   # Возвращает true если строка соответствует своему перевертышу.
>>   def palindrome?
>>     self == self.reverse
>>   end
>> end
=> nil
>> "deified".palindrome?
=> true

(Я и не знаю что круче: то, что Ruby позволяет добавлять методы во встроенные классы или то, что слово "deified" является палиндромом.)

Изменение встроенных классов является мощной техникой, но с большой властью приходит большая ответственность и считается дурным тоном добавлять методы к встроенным классам, не имея действительно хорошей причины для этого. Rails имеет несколько хороших причин (чтобы не делать этого); например, в веб-приложениях вы часто не хотите, чтобы переменные были пустыми; например, имя пользователя не должно быть пустым или состоять из одних пробелов—и Rails добавляет blank? метод к Ruby. Rails консоль автоматически включает Rails расширения, и мы можем увидеть это, например, здесь (это не будет работать в простом irb (Интерактивном Руби)):

>> "".blank?
=> true
>> "      ".empty?
=> false
>> "      ".blank?
=> true
>> nil.blank?
=> true

Мы видим, что строка пробелов не является пустой, но она чистая (blank). Отметим также, что nil является чистым, так как nil не является строкой, это намек, на то, что Rails фактически добавляет blank? к базовому классу String, которым (как мы видели в начале этого раздела) является сам Object. Мы увидим некоторые другие примеры Rails дополнений в Ruby классы в Разделе 8.2.1.

Вадим Обозин
Вадим Обозин

Здравствуйте, записался на курс. При этом ставил галочку на "обучаться с тьютором". На email пришло письмо, о том, что записался на самостоятельное изучение курса. Как выбрать тьютора?