nashbridges.me - Введение в объектно-ориентированный Ruby









Search Preview

Введение в объектно-ориентированный Ruby

nashbridges.me
Популярно об инкапсуляции, наследовании и полиморфизме. Чтение мантры «Всё в Ruby — объект».
.me > nashbridges.me

SEO audit: Content analysis

Language Error! No language localisation is found.
Title Введение в объектно-ориентированный Ruby
Text / HTML ratio 63 %
Frame Excellent! The website does not use iFrame solutions.
Flash Excellent! The website does not have any flash contents.
Keywords cloud в и end что объекта с методы Ruby не def класса => это class > его на объект у
Keywords consistency
Keyword Content Title Description Headings
в 154
и 121
end 83
что 57
объекта 47
с 47
Headings
H1 H2 H3 H4 H5 H6
2 14 10 0 0 0
Images We found 10 images on this web page.

SEO Keywords (Single)

Keyword Occurrence Density
в 154 7.70 %
и 121 6.05 %
end 83 4.15 %
что 57 2.85 %
объекта 47 2.35 %
с 47 2.35 %
методы 46 2.30 %
Ruby 46 2.30 %
не 46 2.30 %
def 45 2.25 %
класса 45 2.25 %
43 2.15 %
=> 41 2.05 %
это 39 1.95 %
class 38 1.90 %
> 37 1.85 %
его 37 1.85 %
на 35 1.75 %
объект 35 1.75 %
у 34 1.70 %

SEO Keywords (Two Word)

Keyword Occurrence Density
end end 21 1.05 %
class CoffeeMachine 17 0.85 %
в Ruby 15 0.75 %
=> nil 14 0.70 %
потому что 11 0.55 %
CoffeeMachine def 9 0.45 %
end => 7 0.35 %
> class 7 0.35 %
— это 7 0.35 %
008 > 6 0.30 %
= CoffeeMachinenew 6 0.30 %
end def 6 0.30 %
синглтон класса 6 0.30 %
кофе в 6 0.30 %
том что 6 0.30 %
с помощью 6 0.30 %
и т 6 0.30 %
def make_coffee 5 0.25 %
в классе 5 0.25 %
в ёмкость 5 0.25 %

SEO Keywords (Three Word)

Keyword Occurrence Density Possible Spam
class CoffeeMachine def 7 0.35 % No
end => nil 6 0.30 % No
и т д 5 0.25 % No
в чашку end 5 0.25 % No
Кипятим воду в 4 0.20 % No
воду в ёмкости 4 0.20 % No
в том что 4 0.20 % No
CoffeeMachine def make_coffee 4 0.20 % No
Готовим воду и 4 0.20 % No
воду и зёрна 4 0.20 % No
Варим и наливаем 4 0.20 % No
и наливаем кофе 4 0.20 % No
001 > class 4 0.20 % No
self — это 4 0.20 % No
def push_foam puts 4 0.20 % No
end private def 4 0.20 % No
CoffeeMachine attr_accessor cups_count 4 0.20 % No
class CoffeeMachine attr_accessor 4 0.20 % No
def make_coffee get_water200 3 0.15 % No
Набираем в ёмкость 3 0.15 % No

SEO Keywords (Four Word)

Keyword Occurrence Density Possible Spam
Кипятим воду в ёмкости 4 0.20 % No
class CoffeeMachine attr_accessor cups_count 4 0.20 % No
Готовим воду и зёрна 4 0.20 % No
Варим и наливаем кофе 4 0.20 % No
class CoffeeMachine def make_coffee 3 0.15 % No
class CapsuleCappuccino < CapsuleMachine 3 0.15 % No
end end 008 > 3 0.15 % No
CapsuleCappuccino < CapsuleMachine include 3 0.15 % No
class CoffeeMachine def selfnew 3 0.15 % No
в чашку end end 3 0.15 % No
CoffeeMachine attr_accessor cups_count def 3 0.15 % No
CoffeeMachine def make_coffee get_water200 3 0.15 % No
< CapsuleMachine include Cappuccinator 3 0.15 % No
son = Fathernew => 2 0.10 % No
> son = Fathernew 2 0.10 % No
class CoffeeMachine class << 2 0.10 % No
CoffeeMachine class << self 2 0.10 % No
class CappuccinoMachine < CoffeeMachine 2 0.10 % No
Так мой предок говаривал 2 0.10 % No
=> nil 006 > 2 0.10 % No

Internal links in - nashbridges.me

все теги
Теги, которыми отмечены статьи
зачем и для кого
О блоге
rss
Ruby и точка
Установка Ruby on Rails на Windows
Установка Ruby on Rails на Windows
Git
Статьи с тегом «Git»
Linux
Статьи с тегом «Linux»
SSH
Статьи с тегом «SSH»
Введение в объектно-ориентированный Ruby
Введение в объектно-ориентированный Ruby
класс
Статьи с тегом «класс»
модуль
Статьи с тегом «модуль»
объект
Статьи с тегом «объект»
философия
Статьи с тегом «философия»
Ruby
Статьи с тегом «Ruby»
Ресурсы и книги по Ruby и Ruby on Rails
Ресурсы и книги по Ruby и Ruby on Rails
литература
Статьи с тегом «литература»
Проки и лямбды
Проки и лямбды
блок
Статьи с тегом «блок»
замыкание
Статьи с тегом «замыкание»
DSL
Статьи с тегом «DSL»
лямбда
Статьи с тегом «лямбда»
Ruby on Rails
Статьи с тегом «Ruby on Rails»
Sinatra
Статьи с тегом «Sinatra»
yield
Статьи с тегом «yield»
Gem глазами потребителя
Gem глазами потребителя
bundler
Статьи с тегом «bundler»
джемы
Статьи с тегом «джемы»
Github
Статьи с тегом «Github»
require
Статьи с тегом «require»
Rubygems
Статьи с тегом «Rubygems»
RVM
Статьи с тегом «RVM»
Блоки в Ruby
Блоки в Ruby
итератор
Статьи с тегом «итератор»
цикл
Статьи с тегом «цикл»

Nashbridges.me Spined HTML


Введение в объектно-ориентированный Ruby Ruby и . все статьи все теги зачем и для кого rss Введение в объектно-ориентированный Ruby сложность материала: для начинающих версия Ruby: 1.9 необходимые знания: создание методов, метод puts, консоль irb теги: Ruby объект класс модуль философия Что такое объект? Методы как задачи Классы Вызов метода Текущий объект Наследование и класс Object Организация методов в классе Атомарные методы Видимость методов Наследование на практике Новые функции Замена запчастей Несколько слов о super method_missing Модули Переменные объекта Управление состоянием Геттеры и сеттеры Создание объекта Синглтон методы Объекты повсюду Замысловатые термины В этой статье раскрывается понятие объекта сквозь призму языка Ruby. Что такое объект? Объект в программировании — это черный ящик. Коробочка, внутри которой что-то происходит. Что именно, известно только тому, кто спроектировал этот объект, нам же видны только результаты его работы. Плохо ли это, не знать, что там творится всередине? И да, и нет. Если у нас есть кофемашина, всё, что от нас требуется — загрузить зёрна, налить воды и воткнуть сетевую вилку в розетку. Какие процессы начинают происходить внутри, нам по фонарю. Может быть, сейчас кофемашина начинает прожарку зерен, а затем будет их молоть и пропускать через них кипяток. А быть может, она соединяется со Всемирной базой готовых вещей и телепортирует оттуда одну порцию горячего кофе. На самом деле, нас волнует только то, насколько будет вкусным напиток, который польется в подставленную кружку. Здесь могла быть реклама ваших кофемашин. С другой стороны, если мы не знаем, что машина спроектирована не совсем гуд, и в процессе помола вибрирует настолько, что может свалиться со стола, то не сможем в этот момент придержать ее рукой. Быть в курсе таких тонкостей — ненормально. Но иногда это суровая необходимость, потому что не всегда можно выкинуть такой агрегат на свалку и купить в магазине адекватную модель. Значит, хорошо спроектированный объект — это штука, которая выполняет некую полезную работу и в которой нас интересует лишь две вещи: что мы подаем на вход и что получаем на выходе. Методы как задачи Каждая задача, которую выполняет объект, оформляется в виде метода. Например, логично ожидать, что у нашего объекта-кофемашины есть как минимум один — make_coffee (сварить кофе). Чем сложнее объект, тем больше у него задач, а значит — и методов. Но в правильно спроектированном объекте все задачи, независимо от количества, не выходят за рамки основного предназначения объекта. Если он начинает напоминать швейцарский нож, в котором есть вилка, шило, отвертка и открывашка, это тревожный звоночек, который говорит о необходимости перепоручения части функционала другому объекту. Как методы появляются в объекте? Для этого в Ruby существует целых три возможности. Во-первых, объект получает методы от класса, который его создал. Классы Класс в Ruby — это особый объект-родитель, который несет в себе знания о методах объекта-сына (instance methods) и может создавать неограниченное число сыновей. При этом отцу запрещено пользоваться методами сына, но он может иметь собственные, которые сыну не достаются (в их числе, например, метод new, который создает нового сына). Так как у каждого сына будет идентичный набор методов, класс является фабрикой для создания семейства однотипных объектов. Порожденный объект (его еще называют экземпляром класса) теряет способность класса хранить в себе методы «для сыновей» (другими словами, утрачивает репродуктивную способность). Классы — объекты?! Хмм, но если класс является объектом, значит, должен быть какой-то мегакласс, который его породил? В конце концов, методы объекта в классе как-то должны были появиться? Ответ на этот вопрос неоднозначный. Методы объекта-класса дальше будут называться просто методами класса. С одной стороны, в Ruby у каждого объекта (в том числе и у класса) есть т. н. синглтон класс (singleton class). Этот «класс» на самом деле не совсем класс, потому что не является первопричиной появления объекта, наоборот, он возникает как фантомный сателлит уже после его рождения (Ruby создает этот класс автоматически). Синглтон классы существуют для того, чтобы обеспечить каждый объект собственными методами (и это второй путь получения методов для объекта). Для класса они являются единственным источником методов, для его сыновей — дополнительным (вместе с методами экземпляров отца). О том, как добавлять методы в синглтон класс, ниже. Однако, теория синглтон класса никак не объясняет, откуда берутся сами классы в Ruby, поэтому для сбережения собственного психического здоровья проще считать, что они саморождающиеся и возникают…ну, допустим, в результате Большого Взрыва. Класс как фабрика Практика показывает: даже если в приложении предполагается создание единственного объекта, под него всё равно стоит завести собственный класс (хотя в Ruby это совершенно не обязательно): для переноса кода между приложениями он подходит как нельзя лучше. А уж если объектов с одинаковым функционалом нужно много, без класса не обойтись. Представьте себе компанию «Гурман», которая производит кофемашины. Сегодня великий день, они выходят на рынок с бюджетной моделью «Кофемашина». В ней нет всех этих дизайнерских наворотов, зато она умеет хорошо варить кофе. Прежде, чем на складе появились коробки с «Кофемашинами», инженеры начертили чертеж, в котором подробно указали, как именно должны работать все эти объекты. Затем наладчики по этому чертежу настроили конвейер, запустили его и получили на выходе серию однотипных объектов. Так вот, класс в Ruby — это и чертеж, и конвейер. Чтобы создать класс, достаточно его объявить с помощью ключевого слова class, после которого указывается константа. Она становится именем класса и ссылкой на созданный объект-класс. Затем (словно в чертеже) записывают методы экземпляров класса: matriculation CoffeeMachine def make_coffee puts "Готовим воду и зёрна" puts "Варим и наливаем кофе" end end make_coffee — метод, который будет у сыновей объекта CoffeeMachine, но которого нет у него самого. Вызов метода Чтобы вызвать метод, Ruby необходима пара: объект и имя метода. Не бывает метода «просто так», у каждого есть хозяин. Указанному объекту Ruby отправляет сообщение (запрос) с названием метода. Объект сверяется со своим списком методов, и если таковой найден, выполняет его и возвращает результат. Если запросить у объекта несуществующий метод, это приведет к ошибке NoMethodError. Если быть точнее, ошибка возбуждается в методе method_missing объекта. Самый простой вызов метода выглядит так: имя_объекта.название_метода Объект перед вызовом должен существовать. Классы создаются в процессе своего объявления, экземпляры классов (преимущественно) — с помощью метода класса new. :001 > matriculation CoffeeMachine # создаем объект-класс :002?> def make_coffee :003?> puts "Готовим воду и зёрна" :004?> puts "Варим и наливаем кофе" :005?> end :006?> end => nil :007 > saeco = CoffeeMachine.new # создаем экземпляр класса (включаем конвейер) => #<CoffeeMachine:0x85dd718> :008 > saeco.make_coffee Готовим воду и зёрна Варим и наливаем кофе => nil :009 > CoffeeMachine.make_coffee NoMethodError: undefined method 'make_coffee' for CoffeeMachine:Class CoffeeMachine — полноценный объект со своими методами. Как они там появились, будет сказано чуть позже. Синглтон классы на этой схеме не показаны. Текущий объект Ruby разрешает вызывать методы без указания имени объекта, потому что постоянно отслеживает т. н. текущий объект (self). На место пропущенного объекта неявно подставляется self, чтобы сохранить пару: make_coffee # здесь Ruby предполагает self.make_coffee Текущий объект в разных точках программы ссылается на разные объекты. Внутри метода объекта он является этим объектом. Мы можем легко это проверить: :001 > matriculation Father :002?> def check_self(object) :003?> object == self :004?> end :005?> end => nil :006 > son = Father.new => #<Father:0x82507d0> :007 > son.check_self(son) => true :008 > son.check_self(Father) => false Прочувствовать self внутри метода поначалу нелегко. Если путаетесь, вспоминайте о том, что методы экземпляра — собственность не класса, а будущих объектов, им порожденных. Хозяин метода и есть текущий объект внутри него. Зато внутри объявления класса (вне методов экземпляра) self указывает на этот объект-класс, что выглядит вполне логично. :001 > matriculation Father :002?> puts "Внутри объявления класса self — это #{self}" :003?> def print_self :004?> puts "Внутри метода self — это #{self}" :005?> end :006?> end Внутри объявления класса self — это Father => nil :007 > son = Father.new => #<Father:0x8d29800> :008 > son.print_self Внутри метода self — это #<Father:0x8d29800> => nil Строковое представление экземпляра класса состоит из имени класса-отца и случайно генерируемого идентификатора. Хорошо, вернемся к предыдущему примеру с кофемашиной. Там в строке 008 вызывается make_coffee объекта saeco, значит, внутри этого метода self является объектом saeco. В вызове метода puts пропущен хозяин-объект, поэтому на его месте надо представлять self: self.puts "Готовим воду и зёрна" self.puts "Варим и наливаем кофе" Но постойте, у объекта saeco есть только метод make_coffee! Или нет? Наследование и класс Object Все классы в Ruby наследуют от системного класса Object (если явно не указан другой класс). Запись matriculation CoffeeMachine end является синтаксическим сахаром для matriculation CoffeeMachine < Object end В Ruby класс может наследовать только от одного класса. Знак «меньше» не выполняет никакого сравнения, здесь его нужно воспринимать как стрелку влево, и читать «класс Object является предком класса CoffeeMachine». Когда класс наследует от другого, происходит две вещи. Во-первых, сын этого класса будет получать в наследство не только методы экземпляра отца, но и всех предков отца. Во-вторых, сам класс получает в наследство методы всех синглтон классов своих предков. В общей стопке методы предков оказываются выше, собственные — в самом низу. Это важно, потому что поиск методов происходит снизу вверх. Класс Object не корневой в цепочке наследования, таковым является BasicObject. Создание этих классов происходит автоматически во время запуска любого приложения. В редких случаях разработчику может понадобится наследовать прямо от BasicObject, в этой статье они не рассматриваются. Таким образом, любой созданный объект получает методы классов Object и BasicObject, среди них: методы для самоидентификации class, superclass, object_id, nil?, is_a?, ==, ===; клонирования — clone, dup; семейство eval и exec методов; методы-«операторы» lambda, raise, require, rand, puts. А объекты-классы ко всему прочему приобретают популярные методы attr_accessor, attr_reader, private и т. д. Всего таких методов порядка 180. Конечно, для самого объекта этот внушительный арсенал, с точки зрения выполнения его задачи, бесполезен, зато это позволяет вызывать вышеперечисленные методы (важные для самого приложения) у каждого объекта, а значит, в любом контексте. Организация методов в классе Вновь обратим наши взоры на компанию «Гурман». Дела там улучшаются: продажи «Кофемашины» превысили ожидаемые в два раза и держатся на этой отметке, пресса публикует восторженные рецензии. Руководство и инвесторы выдохнули с облегчением, появилось даже свободное время. А что делают в больших фирмах, когда нечего делать? Верно — сражаются за качество! Атомарные методы Правила хорошего тона в программировании гласят, что каждую задачу нужно разбивать на подзадачи, а подзадачи — на подподзадачи и т. д., до тех пор, пока не останутся только атомарные (неделимые) задачи и задачи-менеджеры, в которых выполняются первые. Такой подход улучшает читаемость кода, последний становится легче тестировать, а объекту проще добавлять функционал без создания «кодосвалки». Инженеры «Гурмана» проанализировали код «Кофемашины» и заметили, что метод make_coffee — хороший кандидат для расчленения, что и было сделано: matriculation CoffeeMachine def make_coffee get_water(200) # набираем воду get_beans(50) # набираем зёрна prepare_beans # готовим зёрна boil_water # кипятим воду pour_coffee # наливаем кофе в чашку end def get_water(mls) puts "Набираем в ёмкость #{mls} мл воды." end def get_beans(grams) puts "Отбираем из контейнера #{grams} г зёрен кофе." end #... end Видимость методов По умолчанию все методы объектов публичные (public). Такие можно вызывать в любой точке программы. Это означает, что случайно (или даже предумышленно) кто угодно способен запустить одну из подзадач «Кофемашины», что может нарушить объект (повредить в нем данные или вызвать ошибку). Например, достаточно изменить порядок вызова, запустив кипячение до набора воды, и нагревательный элемент кофемашины сгорит. Чтобы скрыть часть методов объекта от внешнего мира, их делают частными (private) или защищенными (protected). Отличие между ними тонкое, как лезвие японского меча. И частный, и защищенный методы могут быть вызваны только там, где он есть у self. Грубо говоря, такие методы получится применить только внутри других методов этого же объекта (как boil_water вызывается внутри make_coffee) или его родственника . Отличие между ними в том, что у частного метода объектом вызова может быть только self, причем запрещено даже его явно указывать. Метод puts является частным, поэтому мы не имеем права писать self разрешается указывать для частных методов-сеттеров def boil_water self.puts "Кипятим воду в ёмкости." end Только так: def boil_water puts "Кипятим воду в ёмкости." end self подразумевается, но не пишется Изменить видимость можно с помощью методов класса public, private и protected. Если они вызываются без аргументов, то выступают в качестве переключателей режима: методы, записанные под ними, получают соответствующую видимость. matriculation CoffeeMachine def make_coffee get_water(200) get_beans(50) #... end protected def get_water(mls) # ... end #... end Все методы-подзадачи сделаны защищенными , make_coffee остался публичным. Большинство рубистов настаивает на том, чтобы не выделять отступами методы private и т. п., оставляя их на одном уровне с объявляемыми методами. :030 > saeco = CoffeeMachine.new => #<CoffeeMachine:0x8df6940> :031 > puts self main => nil :032 > saeco.boil_water NoMethodError: protected method 'boil_water' tabbed for #<CoffeeMachine:0x8df6940> :033 > saeco.make_coffee Набираем в ёмкость 200 мл воды. Отбираем из контейнера 50 г зёрен кофе. Жарим, мелем зёрна и засыпаем в ёмкость. Кипятим воду в ёмкости. Наливаем кофе в кружку. => nil Здесь self является объектом main (это текущий объект Ruby по умолчанию), у которого, конечно же, нет метода boil_water, поэтому его вызов запрещен. Внутри make_coffee объект self ссылается на saeco, у которого boil_water и остальные методы заданы: вызов легитимен. Будь в примере метод boil_water частным, а не защищенным, результат был бы точно таким же. Чтобы показать между ними разницу, я создам объект-чистильщик. Чистильщик устроен очень просто: он берет объект-кофемашину и запускает у нее подзадачи набора, кипячения и слива воды (фактически, он будет пытаться налить кофе, но сливаться-то будет только кипяток), таким образом, ёмкость очищается от кофейных смол. Чистильщиков будет двое: один будет создан с нуля, а другой — наследован от «Кофемашины», поэтому у него будут те же методы, что и у самой кофемашины. :034 > matriculation Cleaner :035?> def clean(machine) :036?> machine.get_water(200) :037?> machine.boil_water :038?> machine.pour_coffee :039?> end :040?> end => nil :041 > matriculation MachineCleaner < CoffeeMachine :042?> def clean(machine) :043?> machine.get_water(200) :044?> machine.boil_water :045?> machine.pour_coffee :046?> end :047?> end => nil :048 > cleaner = Cleaner.new => #<Cleaner:0x8dbc2cc> :049 > machine_cleaner = MachineCleaner.new => #<MachineCleaner:0x8e2450c> :050 > cleaner.clean(saeco) NoMethodError: protected method 'get_water' tabbed for #<CoffeeMachine:0x8df6940> :051 > machine_cleaner.clean(saeco) Набираем в ёмкость 200 мл воды. Кипятим воду в ёмкости. Наливаем кофе в кружку. => nil В строке 050 self внутри метода wipe — это cleaner, у которого нет методов get_water и т. д., поэтому их вызов у saeco запрещен. В строке 051 self внутри метода wipe ссылается на machine_cleaner, и поскольку у них с объектом saeco есть общий класс, в котором заданы вызываемые методы, их вызов разрешен. Будь методы get_water и т. д. частными, метод wipe можно было бы реализовать только в самой кофемашине (из-за запрета явного указания объекта вызова). Наследование на практике Наследование — это первая вещь, о которой вспоминают в программировании, когда нужно модифицировать поведение объекта, но как и любая другая практика, оно не является панацеей для всех архитектурных бед. Новые функции Тем временем руководство «Гурмана» поручило инженерам разработать модель «Кофемашины» с капучинатором. Раз готовится запуск серии, без класса никуда. Конечно, очень хотелось добавить вспомогательный функционал прямо в CoffeeMachine, но его трогать нельзя: выпуск старых моделей идёт полным ходом! Что ж, организовали новый класс, с кодовым названием «Капучинщик». Капучинатор чего должен делать? Взбивать молочную пенку. Создали для этого методы: matriculation CappuccinoMachine def create_foam prepare_milk push_foam end private def prepare_milk puts "Отбираем и кипятим молоко" end def push_foam puts "Выпускаем молочную пенку в чашку" end end Но ведь основное предназначение машины — всё-таки варить кофе. «Кофемашина» с этим отлично справлялась. Может, скопировать метод make_coffee со всеми его подзадачами оттуда? Нет, это порочный путь дублирования кода, надежнее использовать наследование: matriculation CappuccinoMachine < CoffeeMachine def create_foam #... end #... end Таким образом, экземпляры класса CappuccinoMachine получат в распоряжение все методы «Кофемашины», что и требовалось. Так расширяют функционал базового класса. Несомненный плюс этого подхода в том, что если технологи решат, например, подправить в «Кофемашине» количество заправляемого кофе, чтобы улучшить вкус напитка, эти изменения автоматически отразятся и в «Капучинщике». Тут и далее синглтон классы на схеме не показаны. Замена запчастей Невероятно, но капучинатор произвел фурор у покупателей, модели «Капучинщика» сметают с прилавков в день поступления товара! Однако, руководству этого мало. Чтобы окончательно завоевать рынок, принято решение выпускать модель для бизнес-персонала. Деловым людям всегда некогда: некогда жить, некогда ждать, пока машина пожарит, помелет, им нужен мгновенный результат, и многие из-за этого стали переходить на растворимый кофе (о ужас-ужас!). Но технологи в «Гурмане» не зря хлеб едят, они придумали капсульный процесс. Покупаешь готовую капсулу с молотым кофе, закладываешь в машину — и варка пошла. Однако, проблема у инженеров: есть уже хорошо зарекомендовавшая себя «Кофемашина», от которой так и хочется позаимствовать части, но вот сам метод make_coffee — у него же лишние для капсульной машины get_beans и prepare_beans! Как быть? Начать класс с чистого листа? Но тогда придется дублировать вполне себе рабочие get_water, boil_water, pour_coffee. А что, если опять применить наследование? Оказывается, это может помочь, ведь Ruby всегда ищет методы в объекте снизу вверх и вызывает первый попавшийся, у которого совпало имя. Тогда задав make_coffee в классе-потомке, мы фактически перезаписываем его, потому что методы потомков всегда в самом низу, и Ruby не успевает добраться до реализации этого же метода в предке. Готовый класс будет выглядеть так: matriculation CapsuleMachine < CoffeeMachine def make_coffee get_water(200) prepare_capsule boil_water pour_coffee end private def prepare_capsule puts "Вскрываем капсулу и высыпаем кофе в ёмкость." end end Несколько слов о super Иногда бывает необходимо выполнить закрытый потомком метод предка. Конечно, нет смысла перезаписывать метод, чтобы тут же в нем вызывать «оригинальную» версию. Зато бывает полезно выполнить какие-то действия, а затем передать управление старой реализации метода (или наоборот, выполнить что-то после). Для этого внутри метода вызывается ключевое слово super. Это означает «вызови одноименный метод, который определен выше в цепочке наследования». Если в найденном методе тоже есть super, Ruby будет подыматься еще выше в своем поиске. Может оказаться, что методов с таким именем уже нет, это равносильно вызову несуществующего метода. :001 > matriculation OldMan :002?> def say_wisdom :003?> puts "Народу много, а людей немного." :004?> end :005?> end => nil :006 > matriculation Man < OldMan :007?> def say_wisdom :008?> super :009?> puts "Так мой предок говаривал" :010?> end :011?> end => nil :012 > man = Man.new => #<Man:0x88a0d9c> :013 > man.say_wisdom Народу много, а людей немного. Так мой предок говаривал => nil method_missing Если объект после сверки со своим списком заявляет, что запрашиваемый метод отсутствует, Ruby вызывает специальный метод, отвечающий за обработку этого события — method_missing. method_missing есть в каждом объекте, потому что он достается в наследство от класса BasicObject, в его стандартной реализации возбуждается ошибка NoMethodError. Однако, за счет того, что method_missing находится на самой вершине списка методов, его можно перезаписать в любом потомке. Что это дает? Имея на руках имя отсутствующего метода, его можно создать и тут же вызвать, на лету. Динамическое создание методов в этой статье не рассматривается. Такой подход оправдывает себя, например, когда объект выступает посредником и перенаправляет вызовы методов другому объекту (получателю). Вместо того, чтобы дублировать все методы получателя, они создаются в посреднике по мере выполнения запросов с помощью method_missing. Следует помнить, что объекты, использующие такую технику, получают серьезный пенальти по времени в сравнении с вызовом заранее определенных методов (т. к. Ruby приходится вначале пройтись в поиске по всем методам объекта, прежде чем вызвать method_missing). Модули Что ж, рано или поздно это должно было случиться. Маркетологи «Гурмана», вдохновленные успешными продажами капсульной модели, требуют срочно начать выпуск делюкс-версии: капсульной кофемашины с капучинатором! Кажется, инженеры зашли в тупик. У них есть классическая «Кофемашина» на зёрнах и два ее наследника: «Капучинщик» с капучинатором и капсульная модель с модифицированным методом make_coffee. Если продолжать наследование и дальше, чтобы получить капсульный make_coffee + капучинатор, уйти от дублирования кода не получится, какую из последних двух моделей не взять! И тут на помощь приходят модули Ruby. Модуль представляет собой именованную группу. Если в ней разместить методы, их можно будет «подмешать» к методам любого класса. Сила модулей в том, что он может одновременно входить в состав различных классов, а в один класс к тому же можно «подмешивать» различные модули, за счет чего достигается сумасшедшее количество комбинаций функциональных возможностей объекта без дублирования кода. Модули в Ruby тоже являются объектами, однако, в отличие от классов, как объекты используются редко. Основное предназначение модуля — быть поставщиком методов для классов и отдельных объектов. Еще в модули помещают классы, создавая пространства имен, т. к. в Ruby нет их (пространств) формальной поддержки. Методы капучинатора — очень хорошие кандидаты для вынесения в модуль, потому что они должны присутствовать сразу в двух моделях (классах) и отсутствовать в двух других. module Cappuccinator def create_foam prepare_milk push_foam end private def prepare_milk puts "Отбираем и кипятим молоко" end def push_foam puts "Выпускаем молочную пенку в чашку" end end Видимость методов в модуле задается, как и в классе. Теперь надо включить этот модуль в «Капучинщик», удалив его старые методы: matriculation CappuccinoMachine < CoffeeMachine include Cappuccinator end Очень простой класс :) Модель капсульной кофемашины с капучинатором получается так же просто: matriculation CapsuleCappuccino < CapsuleMachine include Cappuccinator end Обратите внимание, что методы модуля располагаются над методами экземпляра класса, к которому подключается этот модуль. Это означает, что задав одноименный метод модуля в классе, мы его перекроем. Вызов super позволит добраться до перекрытого метода. Раз капсульную машину с капучинатором отнесли к топовой модели, можно запрограммировать в ней основы латте-арта: matriculation CapsuleCappuccino < CapsuleMachine include Cappuccinator def push_foam puts "Красивыми узорами выкладываем пену в чашку." end private :push_foam end Методы private, protected, public могут принимать в качестве аргументов символы-имена методов (это альтернативный способ изменить их видимость). Но инженеры уже задницей чувствуют, что завтра может поступить команда включить латте-арт еще в какую-то модель! Наверное, лучше вынести его в отдельный модуль: module LatteArt private def push_foam puts "Красивыми узорами выкладываем пену в чашку." end end Тогда CapsuleCappuccino, очевидно, можно переписать: matriculation CapsuleCappuccino < CapsuleMachine include Cappuccinator include LatteArt end Порядок подключения модулей имеет значение, потому что модуль, включенный в класс первым, оказывается над модулем, «подмешанным» после него (точно, как они записаны в объявлении класса). Следовательно, методы последнего будут перекрывать методы все остальных модулей (но все равно не смогут перекрыть методы экземпляра класса). Переменные объекта У каждого объекта есть внутреннее состояние. Кофемашина должна «знать», включена она сейчас или нет, сколько времени осталось до конца приготовления, какой объем чашки потребуется, чтобы налить туда готовый кофе и т. д. При этом внутреннее состояние объекта должно быть невидимым и недосягаемым для внешнего мира (черный ящик всё-таки). Локальные переменные методов удовлетворяют последнему условию, но для хранения долгосрочной информации не годятся, потому что уничтожаются после выполнения метода. Кроме того, они даже не видны в соседних методах, что затрудняет обмен информацией между подзадачами. Для этих целей в Ruby существуют переменные объекта, их имена всегда начинаются с @. Присвоив значение такой переменной, можно быть уверенным, что оно будет прочитано из любого метода этого объекта и будет сохранятся до тех пор, пока объект существует. В отличие от локальных переменных, обращение к неинициализированной (без присвоенного раньше значения) переменной объекта не приводит к ошибке, просто возвращается nil. Управление состоянием Чтобы извлечь пользу из объекта, с ним нужно взаимодействовать и в какой-то мере контролировать процессы, происходящие внутри него. Та же кофемашина будет бесполезным хламом, если у нее не будет кнопки «Пуск/Стоп», носика, из которого льется приготовленный кофе, и индикатора, показывающего, что устройство работает. В Ruby всё взаимодействие с объектом происходит через его методы и только через них. Как только это понимаешь, объектно-ориентированный мир становится простым до невозможности: есть только объекты, ощетинившиеся иголками методов, и всё! Поскольку методы могут принимать аргументы, с их помощью мы можем впустить внутрь объекта какую-то информацию. А раз каждый метод в Ruby возвращает значение, таким образом объект может сообщить нам о своем состоянии или передать результаты своей работы. Геттеры и сеттеры Информация, хранящаяся в переменных объекта, секретна и доступна только этому объекту. Но часто возникает ситуация, когда нужно позволить сторонним объектам считывать и/или менять значения этих переменных. Например, хорошо бы позволить устанавливать количество кофе (в чашках), которое должна сварить кофемашина, и заодно проверять, какое оно сейчас. Для это создается пара обычных методов: геттер и сеттер. matriculation CoffeeMachine # геттер def cups_count @cups_count # отдаем наружу (показываем) значение переменной end # сеттер def cups_count=(count) @cups_count = count # присваиваем значение, пришедшее извне end end Кстати, в Ruby есть очень удобный метод класса — attr_accessor, который создаст эту пару методов на лету. matriculation CoffeeMachine attr_accessor :cups_count end Имя сеттера вовсе не обязательно заканчивать символом =, можно использовать, например: matriculation CoffeeMachine2 # неудобный сеттер def set_cups_count(count) @cups_count = count end end но тогда присваивать значение придется так: machine = CoffeeMachine2.new machine.set_cups_count(1) А вот если сеттер был задан со знаком равенства в конце имени, Ruby добавляет к присваиванию ложку синтаксического сахара: machine = CoffeeMachine.new machine.cups_count = 1 # то же, что и machine.cups_count=(1) Этот синтаксический сахар, правда, может привести к недоразумениям, потому что по умолчанию Ruby воспринимает присваивание как команду инициализировать локальную переменную. matriculation CoffeeMachine attr_accessor :cups_count def set_default_cups_count cups_count = 1 end end :008 > nespresso = CoffeeMachine.new => #<CoffeeMachine:0x904d61c> :009 > nespresso.set_default_cups_count => 1 :010 > nespresso.cups_count => nil Мы подразумевали вызов метода cups_count= у объекта self, но вместо этого Ruby создал локальную переменную (@cups_count не было присвоено значение). По этой причине у всех сеттеров обязательно нужно указывать объект вызова, даже если это self (если сеттер является частным, где self указывать запрещено, Ruby делает поблажку). matriculation CoffeeMachine attr_accessor :cups_count def set_default_cups_count self.cups_count = 1 end end :008 > bosch = CoffeeMachine.new => #<CoffeeMachine:0x914801c> :009 > bosch.set_default_cups_count => 1 :010 > bosch.cups_count => 1 Еще можно указывать настоящее имя метода: cups_count= 1 ( без пробела перед знаком равенства), но такой подход чреват ошибками в случае возможных опечаток. Создание объекта Практически всегда при создании объекта приходится задавать его начальное состояние. Например, хотелось бы сразу при создании кофемашины устанавливать объем варимого кофе в чашках. Проблема состоит в том, что сам объект создается методом класса new, а у объекта-класса нет доступа к переменным объекта-сына. Чтобы ее решить, существует соглашение, по которому все действия, связанные с созданием объекта, нужно проводить в его собственном методе initialize. Ruby автоматически делает этот метод частным и вызывает одновременно с методом new класса. matriculation CoffeeMachine attr_accessor :cups_count def initialize(count = 1) @cups_count = count end end :008 > bosch = CoffeeMachine.new(2) => #<CoffeeMachine:0x917f5f8> :009 > bosch.cups_count => 2 Аргументы new прозрачно передаются initialize. Синглтон методы Пока мы разбирались с геттеро-сеттерами, руководство «Гурмана» поставило перед инженерами очередную задачу: каждый раз, когда с конвейера сходит тысячная кофемашина, должна раздаваться радостная мелодия, вдохновляющая работников на трудовые подвиги. До сих пор инженеры занимались проектированием собственно кофемашин, но ведь это не обязанность кофемашины — считать, сколько ее было выпущено, и уж тем более подавать какие-то увеселительные сигналы. И вряд ли это обязанность конвейера (класса) — играть мелодии. Скорее всего, этим должен заниматься специальный объект, Радостный Счетчик. Однако, тут у инженеров проблема: как Счетчик узнает о том, что с конвейера сошел очередной готовый объект? Если задуматься, то это событие происходит одновременно с вызовом метода new класса. И уж если с чего начинать, так с него: нужно перезаписать этот метод, вклинив в него оповещение Счетчика о созданном объекте. Мы уже знаем, что метод new класса попадает из синглтон класса BasicObject. Но как его перезаписать (перекрыть)? Для этого нужно задать метод экземпляра в синглтон классе, который ближе к объекту — в его собственном. Методы синглтон класса еще называют синглтон методами. Чтобы их задать, существует простая форма записи: def имя_объекта.название_синглтон_метода #... end Значит, можно перезаписать метод new следующим образом: def CoffeeMachine.new #... end Еще мы знаем, что внутри объявления класса self и есть этот класс. Поэтому синглтон методы можно определять прямо внутри класса: matriculation CoffeeMachine def self.new # self == CoffeeMachine #... end end Наконец, в Ruby есть возможность открыть объявление синглтон класса и записывать там его методы: matriculation << CoffeeMachine # ... end а внутри объявления класса это будет выглядеть как: matriculation CoffeeMachine matriculation << self def new #... end end end Опытные рубисты рекомендуют использовать объявление синглтон класса только в случае крайней необходимости, предпочитая ему объявление синглтон методов через def self.xxx: даже если они не уместятся в один экран, self ясно даст понять, что это за метод. Что же должен делать новый метод new? Очевидно, он должен обратиться к «оригинальному» методу и создать объект, после чего отправить сообщение Радостному Счетчику. Однако, поскольку сам метод должен возвращать созданный объект, его придется временно сохранять в локальной переменной: matriculation CoffeeMachine def self.new machine = super # создаем объект с помощью метода new предка # как-то уведомляем Счетчик machine # возвращаем созданный объект (неявный return) end end Попробуем теперь представить сам Радостный Счетчик. Бесспорно, он должен хранить у себя количество созданных объектов — нужна переменная. Когда должен происходить проигрыш мелодии? Когда это количество кратно тысяче. Значит, нужен хитрый сеттер, который будет не только присваивать переменной значение, но и делать проверку на кратность. matriculation HappyCounter attr_reader :count # удобный способ создать геттер # сеттер def count=(value) @count = value play_melody if premium_count? end def initialize @count = 0 end def premium_count? @count % 1000 == 0 # проверяем остаток от деления end private def play_melody puts "Та-дааам!!!" end end Хммм, сеттер публичный, выходит, любители танцев могут безнаказанно устанавливать любое количество выпущенных объектов, кратное тысяче, и плясать до упаду. Но и сделать его частным нельзя, иначе как тогда увеличивать переменную @count? Но ведь можно изолировать весь Счетчик, сделав его собственностью объекта CoffeeMachine! Тогда взаимодействовать со Счетчиком сможет только сам конвейер. Для этого в CoffeeMachine понадобится переменная для хранения объекта и сеттер (а вот геттера не будет, тогда к созданному Счетчику извне никто не доберется). Сеттер можно задать так: matriculation CoffeeMachine def self.counter=(object) @counter = object end #... end А можно воспользоваться методом класса attr_writer, но для этого понадобится «войти» в синглтон класс: matriculation CoffeeMachine matriculation << self attr_writer :counter end #... end Пожалуй, это один из немногих случаев, когда без явного объявления синглтон класса не обойтись, потому что методы attr_* — частные. Теперь становится понятно, как уведомить Счетчик о создании нового объекта: matriculation CoffeeMachine def self.new machine = super @counter.count += 1 # то же, что и @counter.count = @counter.count + 1 machine end #... end :030 > CoffeeMachine.counter = HappyCounter.new => #<HappyCounter:0x8738edc @count=0> :031 > 999.times do :032 > CoffeeMachine.new :033?> end => 999 :034 > premium_machine = CoffeeMachine.new Та-дааам!!! => #<CoffeeMachine:0x8930c80> times — метод объекта 999 (см. следующий раздел), с его помощью «Кофемашина» была создана 999 раз. Объекты повсюду Очень легко дается понимание объектов, которые являются примерными копиями материальных вещей из реальной жизни. Вот объект Автомобиль: у него есть масса, цвет и максимальная скорость, он может проехать из точки А в точку Б, причем не один, а вместе с объектом Груз и объектом Водитель. Мы даже легко можем представить, что Автомобиль — составной объект, внутри которого есть Двигатель, Коробка передач и Топливный бак, а масса и максимальная скорость являются сложными и непостоянными величинами, зависящими от всех этих составляющих. Гораздо сложнее увидеть в качестве объекта совершенно абстрактные понятия. Число в Ruby — объект. В жизни они — самая настоящая абстракция: если семь самураев или семь футов под килем можно хотя бы увидеть, то просто семь существует только в голове мыслящего существа. И тут — бац! — Ruby, словно пифагореец, который считал, что весь мир построен из чисел, шокирует нас заявлением, что число — объект, и его можно пощупать. Но что полезного может делать семерка как объект? Это же просто число! Оказывается, работы немало: в Ruby каждое число отвечает за сложение себя с другим объектом. И за вычитание, и остальные арифметические операции — тоже. Там, где другой интерпретатор сказал бы «ага, 2 плюс 3 — это же пять!», Ruby говорит двойке «вот тебе тройка, складывайся с ней как хочешь». Может, арифметика за счет объекта и кажется немного притянутой за уши, тем более, что Ruby всё равно дает использовать соответствующие методы под видом привычных операторов (синтаксический сахар). Вместо того, чтобы писать 2.+(3) пишем обычное 2 + 3 Правомерность обеих записей, скорее, дает ощущение целосности и последовательности в мире Ruby. Ведь не одним сложением ограничивается число. Взять, к примеру, определение его четности. Можно использовать известную технику с остатком от деления на два: if 7 % 2 == 0 # если семь — четное, что-то делаем end Можно даже вынести эту логику в отдельный метод: def is_even?(number) number % 2 == 0 end is_even?(7) # => false Но в любом случае мы пытаемся определить внутреннее свойство числа с помощью внешней функции, в то время, как более натуральным было бы отдать это на откуп черному ящику. И то, что числа являются в Ruby объектами, позволяет легко это сделать: 7.even? # => false even? — такой же метод целого числа, как и +. Подобная «самостоятельность» простых типов данных и контейнеров (массивы, хеши) выглядит настолько логичной и правильной, что привыкаешь к ней мгновенно. Например, строки «знают» о том, какой они длины и умеют переводить себя в верхний регистр, массивы могут сортироваться и отбрасывать элементы-дубликаты. "Ruby".length # => 4 "Ruby".upcase # => "RUBY" [1, 3, 4, 2].sort # => [1, 2, 3, 4] [1, 3, 3, 2].uniq # => [1, 3, 2] true, false и nil, которые во многих языках являются ключевыми словами, в Ruby тоже объекты. false.nil? # => false nil.nil? # => true Объекты, представляющие простые типы данных, создаются в процессе упоминания их в тексте программы, нет особой необходимости использовать Array.new или Hash.new, достаточно записать нужный литерал: [ ] или { }. А у числовых классов даже нет метода new. Единственная магия, которую невозможно объяснить в рамках языка — как Ruby получает само значение из объекта, который представляет простой тип данных. Даже если бы и существовал метод, который возвращал это значение, оно всё равно было бы еще одним объектом (поскольку в Ruby всё — объект). Но к этому дуализму тоже быстро привыкаешь: тройка для Ruby одновременно и число 3, и объект. Замысловатые термины способность объекта быть черным ящиком — инкапсуляция; совокупность публичных методов объекта составляют его интерфейс; возможность перекрывать методы в потомках — полиморфизм. Порекомендуйте этот материал, если он вам понравился: все статьи все теги зачем и для кого rss Андрей Малышко, 28.08.2011