у какого объекта класса class метод superclass возвращает значение null на языке ruby
Блог 7even
про ruby, rails, sinatra, git и всё на свете
Объектная модель Ruby
Данный опус является моей попыткой собрать воедино все принципы, согласно которым работает объектная модель в языке Ruby. Цель статьи в том, чтобы читатель увидел логику там, где раньше видел “магию”.
Первый раздел предназначен скорее для тех, кто с Ruby знаком слабо; практикующим рубистам следует его пропустить и переходить к описанию вызова метода.
Основы
Вызов метода
Объект содержит какое-то количество инстанс-переменных (например, объект класса Person может иметь инстанс-переменную @name ) и ссылку на свой класс.
Посмотрим, что происходит, когда вызывается определенный метод:
Так вот. Как только выполнение попадает внутрь вызванного метода, self становится этим объектом-получателем и остается им до тех пор, пока не будет вызван метод с другим объектом-получателем, либо пока метод не будет полностью выполнен (а также в некоторых других случаях, которые будут описаны далее). Все это время обращения к инстанс-переменным будут работать с текущим объектом, и вызовы методов без указания получателя будут попадать также в текущий объект.
Прокси-классы
Но как в эту схему вписываются модули, подключенные в классы из цепочки наследования? Их методы также должны быть видимы объекту класса, который стоит ниже в цепи (если считать, что BasicObject стоит на самом верху).
Дело в том, что при включении класса руби создает анонимный класс, и помещает его в цепочку наследования прямо над включившим этот модуль классом. Такие анонимные классы часто называют прокси-классами. Соответственно, при поиске метода, определенного в модуле, он будет найден в анонимном прокси-классе, и все произойдет так, как если бы метод был определен в одном из настоящих классов.
Синглтон-классы
Это все может выглядеть как магия, но на самом деле ruby руководствуется единой логикой. Посмотрим внимательнее, что тут происходит.
self и текущий класс
В каждой точке кода определены так называемые “текущий объект” и “текущий класс”.
Посмотрим все типичные случаи, имеющие собственные правила определения self и текущего класса.
Верхний уровень
Определение класса
Определение метода
В объявлении метода self указывает на объект, являющийся получателем при вызове этого метода. Это, в свою очередь, самый очевидный пример смены текущего объекта.
Текущий класс в определении метода не меняется; внутри определения метода можно определить второй метод, и при вызове первого второй станет методом того же класса:
Когда речь идет об обычном объекте, мы можем определить для него сразу несколько синглтон-методов:
В более распространенной ситуации, когда class используется в определении класса, в роли объекта выступает сам класс, и мы попадаем в синглтон-класс класса:
BasicObject#instance_eval / Module#class_eval
BasicObject#instance_eval
Module#class_eval
Цепочки наследования
При объявлении нового класса:
При включении модуля в класс создается прокси-класс, содержащий инстанс-методы модуля, и помещается в цепочку прямо над включающим классом
Синглтон-класс обычного объекта (не класса и не модуля) наследуется от номинального класса этого объекта (класс, который возвращает object.class )
При подключении в один класс нескольких модулей порядок в цепочке наследования определяется порядком подключения: подключенный последним модуль будет стоять сразу после класса, его подключившего; далее предпоследний подключенный модуль итд; т.е., например, последний модуль может переопределить метод, заданный в предпоследнем.
Выводы
Резюмируя все вышеописанное:
Любой объект содержит группу инстанс-переменных и ссылку на класс.
Любой класс или модуль содержит группу инстанс-методов, и предоставляет их всем объектам, в чьих цепочках наследования он находится; также любой класс содержит ссылку на родительский класс.
Любой объект может иметь собственный синглтон-класс, содержащий его синглтон-методы; он наследуется от номинального класса исходного объекта.
Классы и модули являются объектами, и обладают всеми свойствами объектов (имеют свой класс и полученные от него методы, также могут иметь синглтон-класс).
При подключении модуля в класс он помещается в цепочку наследования прямо над этим классом.
Синглтон-класс объекта наследуется от номинального класса объекта; за исключением синглтон-классов классов, каждый из которых наследуется от синглтон-класса родителя исходного класса.
При обращении к инстанс-переменной она ищется в текущем объекте ( self ); при вызове метода без указания получателя он будет вызван на текущем объекте.
При объявлении метода в обычной форме ( def method_name ) он становится инстанс-методом текущего класса;
При объявлении метода с указанием получателя ( def object.method_name ) он становится синглтон-методом указанного объекта.
Указатели на текущий объект ( self ) и текущий класс меняются в зависимости от контекста.
Class методы в ruby
Не так давно, занявшись изучением ruby, я столкнулся с тем, что не понимаю, что такое class методы, точнее в чем их отличие от instance и зачем они вообще нужны. В учебнике, который я изучаю на данный момент, эта тема была описана не достаточно подробно или я не дочитал до подробного описания, но в любом случае мне стало интересно разобраться и я полез искать ответы в google. Данный пост является всем тем, что мне удалось найти и понять. Конечно, для опытных ruby разработчиков тут интересного мало, но я надеюсь, что смогу помочь таким же новичкам в языке как и я. Если я вас заинтриговал — прошу под кат.
Для начала, давайте определимся: в ruby все объекты. Это нужно впитать, усвоить и всегда помнить. Даже классы — это объекты имя которых — константа. И с каждым объектом можно совершать какие либо действия благодаря методам. Следовательно, возникает резонный вопрос: можно ли что-то делать с классами благодаря методам?
Думаю, что многие догадаются, что ответ на этот вопрос будет положительный, даже больше скажу: в ruby каждый метод является либо методом экземпляра (instance method) либо методом класса (class method соответственно). Отличаются они, кроме способа создания, только тем, что первые методы вызываются с экземпляром класса, а вторые — с классом. Важно понимать, в чем заключается различие между этими двумя методами и уметь определять когда и какой метод использовать. Поэтому рассмотрим методы класса (class methood), но для начала напомню, а может для кого и расскажу, что такое методы экземпляра.
Методы экземпляра (instance metods):
Часто используемые, обычно с таких методов начинается изучение языка практически во всех учебниках. Обычно создаются они тремя способами:
Ничего сложного и все понятно. Используются они везде и всюду. Например: в rails модули экземпляра могут отвечать за создание и удаление тех же постов, ну или часто встречающийся метод — метод сложения.
Методы класса (class methods):
Руби очень гибкий и лаконичный язык, поэтому он предоставляет нам аж целых четыре различных способа создания методов класса:
Когда стоит пользоваться данными методами? Обычно их используют, когда вы имеете дело не с отдельными экземплярами класса. Самый банальный пример — методы валидации в rails. Выглядит код примерно так:
Взаимодействие instance и class методов между собой.
Бывают случаи, когда в методе класса необходимо вызвать метод экземпляра, ruby для этих целей предоставляет элегантный способ:
А иногда бывают случаи когда наоборот, нужно вызвать метод класса в методе экземпляра. Для этих целей нужно использовать метод class :
Важно понимать, что self возвращает значение конкретного класса, поэтому иногда происходит путаница, когда нужно, что бы и наследники такого класса возвращали метод родителя. Для этих целей нужно использовать вместо self непосредственно имя класса родителя:
Руби Классы
Классы
В объектно-ориентированном программировании класс представляет собой расширяемый программный код-шаблон для создания объектов, предоставляющий начальные значения для состояния (переменные-члены) и реализации поведения. Во многих языках имя класса используется как имя класса (сам шаблон), имя конструктора класса по умолчанию (подпрограмма, создающая объекты), а также как тип объектов, создаваемых при создании экземпляра класса; эти различные понятия легко объединить.
Определение класса
Классы в Ruby являются первоклассными объектами, каждый из которых является экземпляром класса Class.
Пример :
Конкретизация:
Экземпляр объекта создается из класса посредством процесса, называемого экземпляром. В Ruby он создается с помощью метода Class new.
Переменные экземпляра:
Переменные экземпляра создаются для каждого экземпляра класса и доступны только в этом экземпляре. Переменные, начинающиеся с символа @, являются «переменными экземпляра». Вне определения класса, используя открытые методы экземпляра, значение переменной экземпляра может быть прочитано или изменено.
Пример:
Выходные данные приведенного выше кода показывают пустые значения (nil) и 200. @x определено ниже класса Calculation и является переменной экземпляра для объекта класса, тогда как @x, определенное внутри метода add_num, является переменной экземпляра, принадлежащей экземплярам Calculation. Это две разные переменные, и первая доступна только в методе класса (доступ к нему осуществляется в методах add_num и show_data).
Методы доступа:
В предыдущем разделе мы уже видели, что переменная экземпляра может быть доступна или изменена только в пределах определения метода экземпляра. Если вы хотите получить к нему доступ извне, вам нужно определить общедоступные методы доступа. Вот пример:
Переменные класса:
Переменные класса связаны с иерархией классов и совместно используются классом, его подклассами и его экземплярами. Переменная класса должна начинаться с @@ (два знака «at»). Остальная часть имени следует тем же правилам, что и переменные экземпляра.
Пример:
Переменные экземпляра класса:
Классы могут иметь переменные экземпляра. Это дает каждому классу переменную, которая не является общей для других классов в цепочке наследования.
Методы класса:
Объявление метода класса в Ruby аналогично обычному методу, за исключением того, что методы класса имеют префикс self или имя класса, за которым следует точка. Методы класса выполняются на уровне класса и могут быть вызваны без экземпляра объекта. Они могут получить доступ к переменным класса, но не могут получить доступ к переменным экземпляра. Вот пример:
Пример:
Видимость класса: публичная, частная и охраняемая
Ruby предоставляет три уровня доступности методов: Public, Private и Protected.
Защищенный метод похож на закрытый метод в том, что он может быть вызван только из реализации класса или его подклассов. Он отличается от закрытого метода тем, что его можно явно вызывать в любом экземпляре класса, и он не ограничен неявным вызовом self.
Пример:
В приведенном выше примере school.no_of_students работает, но school.class_v выдает ошибку, поскольку закрытый метод не может быть вызван вне области действия класса. Закрытые методы могут быть вызваны только неявно, см. Следующий пример:
Пример:
Пример: Защищенный контроль доступа
Методы получения и установки:
Переменные экземпляра создаются для каждого экземпляра класса и доступны только в этом экземпляре. Но переменные экземпляра объекта на самом деле не являются частными, вы просто не можете их видеть. Чтобы получить доступ к переменной экземпляра, вам нужно создать метод получения и установки. Методы getter возвращают значение конкретной переменной экземпляра. Вот пример:
Пример:
В приведенном выше примере метод student_name. это метод получения
Метод ‘setter’ используется для изменения значения переменных экземпляра извне. Вот пример:
Пример:
В приведенном выше примере мы определили метод установки sname =, а переменной экземпляра @sname присвоено значение параметра new_name.
attr_accessor, attr_reader, attr_writer:
Ruby предоставляет несколько методов, чтобы легко выполнить вышеуказанную задачу: attr_accessor, attr_reader, attr_writer, где attr_accessor предоставит вам функциональность get / set, читатель предоставит только getter, а writer предоставит только setter. Метод attr_accessor создает оба getter, setter методы и их переменные экземпляра. Вот пример:
Пример:
Пример: attr_reader, attr_writer
Вы можете сделать ту же работу, используя методы attr_reader и attr_writer:
Константа класса Ruby:
Константы могут быть определены внутри классов, но, в отличие от переменных экземпляра, они доступны вне класса.
Пример :
Наследование:
Вы можете получить доступ к методу родителя с помощью ключевого слова super, если ваш класс переопределяет метод из родительского класса (суперкласса).
Синглтон классы:
В Ruby есть способ определить методы, специфичные для конкретного объекта, и такие методы называются Singleton Methods. Когда вы объявляете одноэлементный метод для объекта, Ruby автоматически создает класс для хранения только одноэлементных методов. Вновь созданный класс называется Singleton Class. Это полезно, когда вы хотите получить доступ к этому экземпляру в разных частях приложения, например, в функции регистрации, связи с внешними системами, доступа к базе данных и т. Д.
Вы можете использовать singleton_method, чтобы получить список имен для всех одноэлементных методов объекта. Вот пример:
Вы также можете получить доступ к одноэлементному классу объекта, используя следующий синтаксис:
Ruby on Rails c нуля!
Эти загадочные True, False и Nil объекты Ruby
мая 4, 2010 | Published in Ruby, Основы
Каждое выражение и каждый объект в Ruby имеет булево значение, что означает, что каждый объект и каждое выражение оцениваются как true или как false. Это делает true и false довольно специфическими значениями в языке Ruby. При всем при этом, true и false являются не просто значениями, а объектами Ruby.
Объекты True и False
Вы, наверняка, знаете, что true и false — это зарезервированные слова Ruby, что означает, что вы не сможете использовать их как имена для переменных или методов. Но помимо этого, оба true и false также являются абсолютно полноценными объектами. Ruby имеет 2 специальных класса от которых происходят эти объекты, а именно:
TrueClass
и
FalseClass
и оба объекта true и false являются единственными экземплярами этих классов. Ruby не позволит вам создать несколько экземпляров этих классов, вы можете убедиться в этом посмотрев пример:
irb(main):001:0> true.class
=> TrueClass
irb(main):002:0> false.class
=> FalseClass
irb(main):003:0> p = TrueClass.new
NoMethodError: undefined method `new’ for TrueClass:Class
from (irb):3
irb(main):004:0> q = FalseClass.new
NoMethodError: undefined method `new’ for FalseClass:Class
from (irb):4
irb(main):005:0>
Но, как же каждое выражение в Ruby оценивается значением true или false? Хороший вопрос, постараюсь на него ответить. Каждое выражение в Ruby оценивается объектом и каждый объект в свою очередь может использоваться в условном выражении, что значит, что каждый объект в конечном счете должен оцениваться булевым значением. Вы можете убедиться в этом, взглянув на пример:
Попробуйте это сами с любым выражением или объектом Ruby. Также можно убедиться в том, что значения true и false являются одними и теми же объектами (мы получаем тот же экземпляр TrueСlass или FalseClass), для этого нам необходимо лишь узнать каким id обладает тот или иной объект. Вы наверняка заметите, что для объектов true в равной степени, как и для объектов false, id будет всегда одинаковым. Более того, id объекта false всегда будет равен 0, а id объекта true всегда будет равен 2, например:
irb(main):001:0> true.object_id
=> 2
irb(main):002:0> false.object_id
=> 0
irb(main):003:0>
Таким образом, теперь мы знаем, что: каждое выражение в Ruby оценивается объектом, и каждый объект имеет булево значение. Булевы значения это всегда копии класса TrueClass или FalseClass и благодаря этому имеют всегда один и тот же id объекта. Многие объекты в Ruby будут иметь булево значение true, и только два объекта имеют булево значение false, это собственно объект false и объект nil.
Объект Nil
Объект nil – еще одна особенность Ruby. Nil — такой же объект, как и любой другой. Объект nil является экземпляром особого класса — NilClass. Я выше уже упоминал о том, что nil это один из двух объектов в Ruby, которые обладают булевым значением false.
Так же как true и false, у всех объектов nil id будет одинаковым. Id объекта nil всегда равен 4:
irb(main):001:0> nil.class
=> NilClass
irb(main):002:0> nil.object_id
=> 4
Самое интересное, что с помощью объекта nil в Ruby можно частично реализовать паттерн Null Object прибегая к минимальным усилиям. Объект nil уже снабжен некоторыми методами для того, чтобы вы могли его использовать при этом не получая надоедливых сообщений об ошибках в случаях, когда выполнение определенного фрагмента кода в результате возвратит nil. Именно поэтому объект nil ассоциируется и преобразуется в пустую строку, пустой массив, хэш или число 0. Пример:
irb(main):002:0> nil.to_s
=> «»
irb(main):003:0> nil.to_i
=> 0
irb(main):004:0> nil.to_a
=> []
Из-за того, что вы можете всегда добавлять методы к экземплярам классов в Ruby, а объект nil всегда является единственным экземпляром, вы можете добавить к объекту nil интересные методы, так что ваш код будет использовать nil в качестве Null Object, т.е.:
irb(main):001:0> def nil.quack
irb(main):002:1> puts ‘Кря, кря, кря не можешь сделать из меня паштет ведь я nil’
irb(main):003:1> end
irb(main):004:0> nil.quack
Кря, кря, кря не можешь сделать из меня паштет ведь я nil
irb(main):005:0>
Сейчас если исполняется код с объектами, которые умею крякать как утки и работа происходит с объектом nil вместо объекта DuckLike, то в этом случае ваш модернизированный объект nil сможет крякнуть что-то разумное о причине неработоспособности объекта DuckLike (или вывести ошибку, если это предпочтительно).
Я думаю это довольно мощный материал. Паттерн NullObject уже как бы встроен в язык, вам только необходимо использовать его в своих целях.
Ruby/Для фанатов
Содержание
Подробнее о методах [ править ]
Создание метода [ править ]
Ruby по умолчанию возвратит из метода результат последнего выполненного выражения, поэтому в конце метода или в условных конструкциях слово return можно опускать. Поскольку методы могут быть переопределены в процессе выполнения программы, можно «на ходу» переписать метод так:
Указание значений по умолчанию [ править ]
У методов могут быть необязательные аргументы. Для этого им нужно присвоить значение, которое следует применять «по умолчанию»:
Методы с восклицательным и вопросительным знаком [ править ]
Обычно программист, чтобы проверить, пуст ли массив, посмотрит его длину:
Если вы реализуете программу, которой будут пользоваться другие, считается хорошим тоном реализовывать методы-предикаты.
Ещё одна их прелесть — сочетание с модификаторами выражения:
Методы с восклицательным знаком на конце меняют объект, к которому привязаны.
Методы присваивания [ править ]
Другие особые варианты пунктуации — знак равенства и арифметические знаки.
Знак равенства в конце названия метода означает, что этот метод присваивает свойству объекта значение:
Операторы [ править ]
Операторы (умножение, деление, возведение в степень и так далее — вплоть до сравнения!) — тоже методы. Например:
«Поглощение» аргументов метода [ править ]
Можно «свернуть» аргументы с помощью звёздочки — тогда метод получит массив в качестве аргумента:
Поскольку теперь наш метод принимает неограниченное количество элементов, мы можем пользоваться ими как массивом и в теле функции:
Можно разделить аргументы на обязательные и необязательные, просто пометив последний аргумент «звёздочкой». Если методу будут переданы только обязательные аргументы, в переменной «со звёздочкой» в теле метода будет пустой массив.
Звёздочкой полезно пользоваться и когда нужно передать методу аргументы, но не хочется указывать их по отдельности. Следуя тому же примеру:
Подробнее о замыканиях [ править ]
Понятие замыканий довольно просто: это часть программы, при создании захватывающая переменные окружающей среды. По сути замыкание есть анонимный метод.
Ruby позволяет создавать анонимные методы и передавать их функциям — такие анонимные методы называются замыканиями. Очень большое количество функций Ruby основано на использовании замыканий. Например, итераторы (такие как each и map ). Замыкание — это фактически «функция в функции» — программист определяет операцию, которую необходимо выполнить, но непосредственно её выполнение осуществляет метод, которому замыкание передаётся.
Зачем они нужны [ править ]
Замыкания позволяют избавиться от очень большого количества операций, которые для каждого программиста являются привычными, а именно:
Как создать замыкание [ править ]
При передаче замыкания методу, замыкание следует за скобками аргументов.
Поскольку при отсутствии аргументов скобки необязательны, простейшая запись такова:
Важно помнить, что замыкание использует методы и переменные, указанные при его создании, то есть замыкание захватывает контекст, но переменные, определённые в замыкании, остаются для него локальными!
Необходимо заметить, что если переменная была определена ранее, то она может использоваться внутри замыкания:
Как уже упоминалось, если замыкание многострочное, целесообразней пользоваться формой с do … end :
Замыкания принимают аргументы [ править ]
В данном случае при каждом выполнении замыкания переменная i будет получать значение из диапазона 1..3 в каждом положении итератора, начиная с единицы.
Аргументы метода указываются после открывающей фигурной скобки или после слова do через запятую и ограничиваются двумя вертикальными чертами.
Свои методы с замыканиями [ править ]
Ключевое слово yield в методе открывает раздвижные двери, впускающие аргумент[ы] в замыкание.
При этом строка будет передаваться замыканию в переменную words при каждом выполнении.
Если замыкание обязательно, следует пометить его как последний аргумент метода и в начале аргумента добавить амперсанд:
Последнее утверждение не совсем верно. Даже совсем не верно. Указания переменной замыкания недостаточно для контроля наличия входного замыкания. Дело в том, что в случае, если замыкание не вызывается, то и ошибки не будет:
Более того, вызов функции twice без указания замыкания также приведёт к ошибке. Таким образом, гораздо лучше вместо введения обязательного параметра задавать замыкание по-умолчанию:
Здесь lambda — пустая функция, а closure.call — явный способ вызова замыкания на выполнение.
Замыкание можно также передать другому методу, просто указав его как последний аргумент с амперсандом:
Наконец, на десерт, напишем свой inject.
Некоторые применения замыканий [ править ]
Замыкания — одна из главных особенностей Ruby. Уметь ими пользоваться — ключ к очень коротким и очень понятным программам, делающим очень много.
Типичное применение замыкания — когда после выполнений некой операции нужно «вынести мусор»: закрыть открытый ресурс или отсоединиться от сети. Предположим, что мы пишем метод для интернет-системы. При этом мы хотим выполнить несколько операций. Но чтобы их выполнить, нужно подключить пользователя к Сети. После того, как операции завершились, надо его так же незаметно отключить.
В данном случае мы сохраняем то, что вернуло замыкание, в метод, закрываем соединение и возвращаем результат замыкания как свой собственный.
Чаще всего о методах, принимающих замыкания, можно говорить как о деепричастном обороте — например, «соединившись», «внутри_транзакции», «с файлом», «трижды».
Если воспользоваться встроенной проверкой исключений, то метод принимает такой вид:
Тогда, даже если метод вызовет ошибку, соединение всё равно будет закрыто.
Методы, которых не было [ править ]
Экспериментально замечено, что во время сессии у студентов значительно повышается способность к изобретениям различного рода. Иногда удаётся направить эту энергию в нужное русло: некоторые студенты во время сдачи зачёта начинают придумывать свои методы. Естественно, что «придуманные методы» они реализовать не могут, но с этим замечательно справляются их преподаватели. Некоторым методам даже дают имена студентов, которые приложили своё незнание к их созданию. Многие из таких методов включают в последующие версии языка.
Метод реализован только для массивов, но возможно его добавление к хешам или строкам.
Случайное число из диапазона [ править ]
Студенты часто возмущаются: почему, чтобы получить случайное число от 3 до 6 нужно писать нечто невнятное вида:
Откуда чего берётся? Почему нельзя написать проще? Например вот так:
Действительно, почему? Давайте добавим такую функциональность к классу Range:
Для проверки можно выполнить следующий код:
Или еще проще (без изменения класса Array):
Для проверки выполним следующий код:
Странно, но, видимо, всё работает!
Способы расширения библиотеки методов [ править ]
Как добавить метод к массиву/строке/венику? [ править ]
Важно помнить, что в Ruby все типы являются объектами, даже сами классы. Каждый класс до конца выполнения программы остаётся открытым, а это значит, что в любой тип можно добавить собственные методы (или изменить поведение существующих). Каждый класс можно определять постепенно, в нескольких частях программы:
Операция расширения класса (добавление нового метода к существующему) по сути не отличается от создания нового класса.
У объектов в Ruby есть методы класса и методы экземпляра. В нашем примере consonants — это именно метод экземпляра. При создании нового класса или изменении существующего создать метод класса можно, начав его имя с имени класса или с self и точки:
В контексте класса self — это сам класс.
Если к классу надо добавить много методов сразу, то при описании класса можно выйти на уровень его объекта-класса. Это свойство в Ruby называется eigenclass (нем. eigen — свой, особый). Подозревая, что многие из читателей незнакомы с математическим понятием собственного значения/вектора/пространства, мы кратко и по-программистски назовём eigenclass айгенклассом. Аналогичные концепции в других языках, например в Smalltalk, от которого Ruby наследовал свою объектную идеологию, называются также метаклассами.
Добавим к классу File метод myself :
Если нужно добавить метод только к конкретному экземпляру, нужно выйти на его айгенкласс:
Возможность добавлять и изменять устройство уже существующих классов — одно из основных свойств Ruby, обеспечивающих великую гибкость языка. Часто бывает, что метод возвращает не тот результат, который нам нужен — тогда при его изменении все программы, обращающиеся к данному методу будут получать изменённый результат.
Программист-разрушитель [ править ]
Как ни странно, изредка программисту приходится взять на себя позицию разрушителя — удалить существующий метод или константу. Метод undef позволяет сделать это:
Уничтожение класса несколько сложнее, но тоже возможно:
После этого Broom будет существовать только для объекта-экземпляра:
Это свойство Ruby крайне полезно, если нужно создать класс, наследующий от другого, но при этом имеющий другого родителя. Например:
Полная замена чужих классов довольно опасна, но бывают ситуации, когда эта методика спасает.
Как написать свой итератор? [ править ]
Как написать свой класс? [ править ]
Писать класс не так уж и сложно. Простейший класс будет выглядеть так:
Наследовать или перемешать? [ править ]
Как сделать свою библиотеку методов? [ править ]
Методика самопознания [ править ]
Что такое самопознание? [ править ]
Сразу после того, как мы узнали информацию о текущем состоянии, у нас может возникнуть непреодолимое желание эту информацию использовать в своих целях. Именно поэтому мы рассмотрим не только методы самопознания, но и методы самомодификации. Переносить их в отдельный раздел особого смысла не имеет (так как их очень мало) и поэтому они будут рассмотрены попутно, как бы между делом.
Методами самомодификации мы будем называть такие, которые позволяют изменять объект или класс на основании информации о его текущем состоянии
Каждый из методов самопознания отвечает на тот или иной вопрос о текущем состоянии. Именно поэтому все дальнейшие заголовки будут выглядеть как вопросы.
Совокупность методов самомодификации и самопознания называют методами метапрограммирования
Какому классу принадлежит объект? [ править ]
Язык программирования Ruby является строго типизированным (как и большинство других языков), то есть никогда не существует неопределённости по поводу класса того или иного объекта. Долгое время Ruby считали не строго типизированным языком, но это заблуждение, которое возникло из динамической структуры языка: класс переменной определяется объектом, на который ссылается эта переменная и вместе со сменой объекта может поменяться и класс переменной.
Авторы дают неверную интерпретацию понятия «строго типизированный»
Исходя из вышеизложенного, мы можем оказаться в ситуации, когда необходимо узнать, какой класс хранится в той или иной переменной (или возвращается каким либо методом). Для этой цели служит метод .class («неожиданно», правда?), который возвращает класс, которому принадлежит данный объект.
Обратите внимание, что метод .class возвращает объект класса Class (очередная неожиданность). А почему же не строка? Дело в том, что от класса можно сразу же вызывать методы этого класса. Например, нужно создать объект точно такого же класса, как и у переменной my_variable. Тогда ваш код может выглядеть следующим образом:
Думаю, что никому не надо объяснять, какую мощь дает подобный механизм. Например, в своей деятельности мне не раз приходилось создавать массив классов, от каждого из которых вызывались одни и те же методы.
У метода .class есть синоним .type, но последние версии интерпретатора выдают предупреждение, что метод .type устарел и в дальнейшем исчезнет из языка
От какого класса унаследован этот класс? [ править ]
Другим инструментом исследования классовой иерархии является метод .superclass, который предназначен для получения класса родителя. Заметим, что метод .superclass можно вызывать только для объекта класса Class, который, в свою очередь, можно получить посредством метода .class. Какое-то сложное определение получилось, но давайте рассмотрим работу метода .superclass на примере:
Из примера видно, что посредством последовательного вызова метода .superclass можно узнать обо всей иерархии наследования какого либо класса.
Какие классы/модули использовались этим классом? [ править ]
На первый взгляд может показаться странным такой вопрос, так как мы уже знаем метод superclass, что дает нам возможность получить достоверную информацию о иерархии родительских классов. Дело в том, что в языке Ruby отсутствует множественное наследование, которое компенсируется механизмом примесей. Вот для того, чтобы получить массив всех классов/модулей, которые относятся к данному классу и существует метод .ancestors. Посмотрим его в деле:
В соответствии с негласным правилом, методы, имена которых заканчиваются на s возвращают массив. Это правило получило свое дальнейшее развитие в Ruby on Rails и его следует придерживаться всем разработчикам на Ruby
Обратите внимание, что текущий класс располагается нулевым элементом в массиве, а все остальные классы расположены в порядке наследования/примешивания.
Как получить список примесей класса? [ править ]
Поработав с методом .ancestors может возникнуть резонный вопрос, а что если нужно получить только список примесей без родительских классов? В принципе, для этого надо знать только то, что все примеси имеют класс Module. Узнать это можно, если выполнить простенькую программу:
Поэтому, для того, чтобы получить список примесей класса, можно воспользоваться следующей программой:
Но зачем писать больше, если для этого есть специальный метод .included_modules, который возвращает список подключенных примесей.
Ну, а если результат одинаковый, то для решения этой задачи лучше вызвать один метод .included_modules вместо двух (ancestors и .select).
Как получить список всех родительских классов? [ править ]
Хочу сразу Вас огорчить, что для решения этой задачи специального метода не существует, а это значит, что задачу будем решать через уже известные.
Для поиска списка всех родительских классов мы создали метод .hierarchy в классе Class. Родительские классы возвращаются в виде массива объектов класса Class. Вот теперь порядок!
Является ли данный объект экземпляром этого класса? [ править ]
Вопрос дурацкий, но тем не менее стоит того, чтобы его осветили. Почему дурацкий? Да потому, что мы запросто можем узнать класс объекта при помощи метода .class и сравнить два класса при помощи методов == и !=.
Для подобной же цели придуман специальный метод .instance_of?, который позволяет заменить связку методов .class и ==.
Метод .instance_of? можно перевести на русский, как вопрос «является ли экземпляром класса?»
Обратите внимание, что число пять хоть и является целым, но метод .instance_of? не признает его экземляром класса Integer. В принципе он прав: число пять является экземпляром класса Fixnum, а не Integer. Это означает, что метод .instance_of? излишне строг.
И что же нам делать, если эта строгость нам ни к чему? Для этого можно воспользоваться методами .class, .ancestors и .include?, которые будут учитывать не только текущий класс, но и его родительские классы с примесями.
Подобный код замечательно решает поставленную нами задачу, но выглядит громоздким. Немудрено, что для подобной задачи применяется специальный метод .is_a? (и его менее популярный псевдоним .kind_of?).
Результаты примера следует интерпретировать так: число пять является целым (класс Integer), но не слишком большим (класс Fixnum); при этом, элементы этого типа можно сравнивать между собой (примесь Comparable), но перечислимым множеством они не являются (примесь Enumerable).
А как сравнивать классы между собой? [ править ]
Подобное разнообразие методов сравнения связано с тем, что в класс Class включили примесь Comparable, то есть сделали объекты класса Class сравнимыми между собой
Для того, чтобы разобраться в работе методов сравнения, рассмотрим несколько примеров, а потом подробно разберем их результаты.
Метод «меньше» воспринимается Ruby, как вопрос «является ли потомком?», то есть первый пример можно расценить как вопрос вида: «класс Fixnum является ли потомком класса Numeric?» Если мы взглянем на иерархию наследования чисел, то увидим, что утвердительный ответ (true) на этот вопрос вполне оправдан.
Следуя той же логике, метод «больше» воспринимается как вопрос «является ли родителем?», то есть второй пример можно расценить как вопрос вида: «класс Object является ли родителем класса Integer?» Так как все классы унаследованы от класса Object (кроме самого Object), то утвердительный ответ (true) не должен нас сильно удивить.
Третий пример нам демонстрирует одну интересную особенность методов сравнения: если классы не состоят в отношениях «родитель-потомок», то они являются несравнимыми и поэтому метод сравнения отреагировал на эту ситуацию возвратом nil.
Напомним, что в логическом контексте объект nil равносилен объекту false (то есть они являются взаимозаменяемыми)
Последний пример демонстрирует работу метода «меньше или равно». Он соединяет в себе методы «меньше» и «равно», то есть воспринимается как вопрос вида: «является ли класс IO классом File или его потомком?» Исходя из того, что класс File является потомком класса IO, а не наоборот, ответ на этот вопрос отрицательный (false).
Какие константы доступны? [ править ]
Чтобы узнать какие константы объявлены в том или ином классе/модуле необходимо вызвать метод .constants (еще одна «неожиданность») от этого класса/модуля.
Эта константа объявлена? [ править ]
С высоты полученных ранее знаний, мы можем решить эту задачу следующим образом:
Для решения задачи использовалась комбинация методов .constants и .include?, что не совсем рационально, так как метод .constants возвращает массив, содержащий список всех констант модуля или класса. В нашем случае это не совсем критично, но существуют классы (вроде Object), где количество констант может составлять десятки, а то и сотни. Поэтому, правильней будет использовать специальный метод .const_defined?, который сделает то же самое, но без промежуточного массива (чем сэкономит нам массу ценнейших микросекунд и байт памяти).
Этот же метод хорошо использовать для проверки доступности того или иного класса, так как практически все классы представляют собой константы класса Object.
Существует способ создать класс, но не присваивать ему имя. Только этот способ мы пока рассматривать не будем
Из примера следует, что модуль Math с математическими методами доступен, а вот классу комплексных чисел повезло меньше.
Как по имени константы получить ее значение? [ править ]
Давайте еще раз посмотрим на пример получения доступных констант:
Мы лишь узнали имена констант, но их значения нам пока не доступны. Для того, чтобы получить значения константы существует метод .const_get (который также следует вызывать от конкретного класса или модуля). Для примера, давайте заменим имена констант из предыдущего примера на их значения:
Обратите внимание, что внутри итератора .map нам приходится указывать конкретный модуль (Math), чтобы метод .const_get нормально работал. Иначе возникнет ошибка вида NoMethodError
Какие классы доступны для использования? [ править ]
Последняя сортировка (метод .sort) нужна для красоты, так как в исходном массиве (который вернул итератор .select) имена констант не упорядочены.
Все ошибки имеют свой собственный класс. Сделано это для того, чтобы можно было обработку ошибок настраивать на конкретный класс ошибок. Имена таких классов всегда заканчиваются на слово Error
Те читатели, которым интересны только классы ошибок, могут использовать следующий код:
Упорядоченность результата в последнем примере унаследована от упорядоченности массива classes.
Как изменить значение константы? [ править ]
Вы сами хоть поняли, что спросили? Константа потому и константа, что ее значение неизменно. Ситуаций, когда необходимо изменить значение константы просто не должно возникать!
Любые попытки поменять значение константы будут вызывать бурную реакцию со стороны интерпретатора. Он будет выдавать предупреждение вида: «значение этой константе уже присвоено»
Тем не менее, существует способ создать новую константу. Сделать это можно посредством метода присвоения.
Создавать новую константу можно не только для корневого класса (Object), но и для любого другого модуля или класса (имя которого необходимо указать перед вызовом метода)
Все это замечательно, но что делать если задано только имя константы в виде строки? Для этих целей используется метод .const_set, который нужно вызывать от класса, в который следует добавить новую константу. Давайте перепишем предыдущий пример с тем предположением, что имя константы нам задано в виде строки.
Напоминаем, что для получения значения константы, заданной только своим именем, осуществляется методом .const_get
Еще немножко усложним задание. Пусть теперь в виде строки задано не только имя константы, но и имя класса. Тогда решение может выглядеть следующим образом:
Все это время мы создавали константу Pi в модуле Math. Имейте в виду, что в реальности это не требуется, так как в этом самом модуле уже задана константа PI, которая имеет достаточно точное значение числа Пи
Получается достаточно длинное выражение, но иногда у программиста просто нет возможности его сократить.
Какие переменные доступны? [ править ]
Переменные бывают следующих видов: экземпляра, класса, локальные и глобальные. Первые два вида переменных еще называют атрибутами и они имеют очень важное значение для самопознания. Тем не менее, мы сначала рассмотрим методы самопознания для последних двух видов переменных, так как работа с ними наиболее простая и незамысловатая.
Какие глобальные переменные доступны? [ править ]
Для того, чтобы получить список глобальных переменных надо вызвать метод global_variables (который следует вызывать без указания класса, так как он является закрытым).
Метод .include? применен для того, чтобы читателю не пришлось просматривать весь список глобальных переменных в поисках $a.
Имена глобальных переменных всегда имеют префикс $. Переменные называются глобальными потому, что их область видимости распространяется на всю программу, а не только на какой-то конкретный класс, объект или метод
Какие локальные переменные доступны? [ править ]
Список локальных переменных доступен посредством вызова метода local_variables (который также следует вызывать без указания класса, так как он тоже является закрытым).
Имена всех локальных переменных начинаются со строчной latinskoi’ буквы или знака _. Локальными они называются потому, что их область видимости распространяется только на конкретный блок или метод
Почему глобальные и локальные переменные считаются не интересными с точки зрения самопознания? Дело в том, что значения этих переменных лишь косвенным образом влияют на текущее состояние классов или объектов. Именно поэтому, среди методов самопознания отсутствуют те, которые позволили бы получить или изменить текущее состояние глобальной или локальной переменной.
Какую информацию можно получить о переменных экземпляра? [ править ]
Теперь перейдем к более полезным и востребованным видам переменных: экземпляра (объекта) и класса. Начнем по-порядку, с переменных экземпляра.
Метод получения массива имен переменных экземпляра называется .instance_variables (префикс instance_ практически всегда указывает на принадлежность к экземпляру).
В данном примере мы придумали класс Sample и создали конструктор этого класса .initialize, который вызвали посредством обращения Sample.new. Конструктор вернул нам экземпляр класса Sample, от которого мы и вызвали метод .instance_variables.
Теперь мы можем получить значение любой из этих переменных посредством вызова метода .instance_variable_get:
У переменных экземпляра можно не только узнать значение, но и поменять его. Осуществить это можно посредством метода .instance_variable_set
Помимо рассмотренных методов получения/изменения, есть еще и метод удаления переменной экземпляра .remove_instance_variable, то есть можно в любой момент времени удалить переменную экземпляра. Давайте рассмотрим, что из этого получится:
Создавать дополнительный метод .remove в классе Sample пришлось потому, что метод .remove_instance_variable является закрытым, то есть он может быть использован только внутри метода экземпляра. Если создание дополнительного метода не требуется, то можно использовать метод .instance_eval, который выполняет любой программный код в контексте экземпляра. Перепишем наш пример с использованием этого метода.
Кстати, метод .instance_eval можно использовать для получения значения переменной экземпляра. Например вот так:
Какую информацию можно получить о переменных класса? [ править ]
Работа с переменными класса осуществляется практически также, как и с переменными экземпляра. С той лишь разницей, что в названии методов вместо слова instance_ надо писать class_. На этом можно было бы и закончить, но давайте все таки рассмотрим примеры применения этих методов.
В соответствии с вышеизложенным правилом, заменяем в названии метода .instance_variable слово instance_ на class_ и получаем название метода, который получает массив имен переменных класса.
Как и прежде, мы создали класс Sample, но на этот раз не стали реализовывать конструктор .initialize, так как для работы с классом совсем не обязательно создавать его экземпляр (методом .new).
Пришло время рассмотреть методы получения и изменения значения переменной класса. В примере мы изменим значение переменной класса (методом class_variable_set) и сразу же получим ее новое значение (методом class_variable_get). Основная проблема заключается в том, что оба метода являются закрытыми. Поэтому их вызов возможен только в контексте метода класса. Для решения этой проблемы мы создадим метод Sample.change, который будет менять переменную и возвращать ее новое значение в качестве своего результата.
Ну и напоследок рассмотрим увлекательный процесс удаления переменных класса. Делается это при помощи метода remove_class_variable и при помощи уже известного нам метода .class_eval (так как метод remove_class_variable является закрытым).
Получить значение переменной класса без помощи метода class_variable_get (только при помощи .class_eval) у меня не получилось. Возможно, что это получится у вас
Какие методы доступны? [ править ]
Посмотреть список доступных методов вызвав .methods При этом необходимо учитывать, что когда мы производим вызов метода для класса, там не будут содержаться методы, определенные в нем (и доступные для экземпляров этого класса). Так например
для первого случая вернет false, в то время, как при проверке наличия метода method1 в экземпляре(a) класса(Simple) вернет true
Можно ли вызвать этот метод? [ править ]
Для проверки этого используется метод .respond_to?. Этот метод возвращает, отвечает ли класс на вызов переданного в .respond_to? параметры(имени метода) По аналогии с предыдущим примером