Статьи

Понимание объектной модели

Структура и иерархия фона

Задумывались ли вы, почему Ruby ведет себя так?

class A def self.my_method 'hello' end def my_other_method 'hello again' end end A.my_method # => "hello" a = A.new a.my_other_method # => "hello again" a.my_method # => NoMethodError: undefined method 

Если вы пришли из классического языка, такого как Java, вы можете подумать, что my_method очень похож на статический метод. Но есть ключевые отличия:

  • Методы класса вызываются только из класса и наследуются.
  • Методы экземпляра вызываются только через экземпляр класса и наследуются.
  • Объекты экземпляра также могут объявлять «классовые» методы.

Смущены еще?

В этой статье мы рассмотрим объектную модель. Это даст вам более глубокое понимание того, как Ruby складывает и управляет объектами. Для целей этой статьи мы будем использовать Ruby MRI 2.1. Если интересно, я рекомендую irb в консоли и irb ноги. Не забудьте надеть плавки, так как мы пойдем в глубокий конец бассейна. Я тоже буду на моем.

Что такое метод синглтона?

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

 class D; def method_one; 'hello from method_one'; end; end def D.method_two; 'hello from method_two'; end D.method_two # => "hello from method_two" d = D.new d.method_one # => "hello from method_one" 

С точки зрения Руби, эти два вызова методов будут похожи. Теоретически, мы получили бы одноэлементные методы вызова одноэлементного объекта. Итак, когда получатель D или d вызывает метод, в Ruby должен быть поиск, который идет на один шаг прямо к «классу» и вверх по цепочке предков. Но подождите, скажете вы, не метод экземпляра, а метод класса?

Давайте внимательнее посмотрим, как глубоко заходит эта кроличья нора:

 d.class # => D d.class.instance_methods.grep(/method_one/) # => [:method_one] 

Все идет нормально. Поиск метода в приемнике d идет на один шаг вправо к «классу» и находит method_one. Имейте в виду, что в Ruby классы также являются одноэлементными объектами. Но как насчет D?

 D.class # => Class D.class.instance_methods.grep(/method_two/) # => [] 

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

Ну, что-то не совсем правильно, так как насчет этого:

 def d.method_three; 'hello from method_three'; end; d.method_three # => "hello from method_three" d.class # => D d.class.instance_methods.grep(/method_three/) # => [] 

Подожди еще секунду! method_three не определен в классе D. Может быть, есть что-то критическое, чего мы не видим из этого. Одно можно сказать наверняка: с этими двумя последними вызовами они оба отсутствуют в предполагаемом классе. Чтобы наша гипотеза синглтон-метода имела какой-либо смысл, эти методы должны принадлежать «классу». Посмотрим, сможем ли мы найти эти неуловимые классы.

Введите собственные классы

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

Слово eigen по-немецки (произносится как: AYE-gun) примерно означает «свое». В данном случае это означает «собственный класс объекта». В этой статье мы будем придерживаться собственного класса для нашей терминологии.

Чтобы найти эти неуловимые НЛО в Ruby, вы можете сделать:

 d.singleton_class # => #<Class:#<D:0x007ff882b629c0>> 

Или, если вы хотите произвести впечатление на своих друзей своими безумными навыками Ruby, взломайте:

 class Object; def eigenclass; class << self; self; end; end; end d.eigenclass # => #<Class:#<D:0x007ff882b629c0>> 

С помощью этого кода мы вновь открываем Object , который находится в цепочке предков (подробнее об этом позже), и добавляем метод. Этот метод переходит прямо в собственный класс и возвращает «я», которое является собственным классом получателя. Отличный трюк, а?

Теперь, когда мы нашли наши собственные классы, давайте проверим, имеет ли моя теория смысл:

 D.eigenclass.instance_methods.grep(/method_two/) # => [:method_two] d.eigenclass.instance_methods.grep(/method_three/) # => [:method_three] 

Тада! Мы нашли наши методы. Обратите внимание, что поиск этого метода ничем не отличается от поиска по method_one . Напомним, это один шаг прямо к «классу» и вверх по цепочке предков. Кажется, что Ruby оборачивает этот код для нас в метод поиска, называемый singleton_methods , но теперь мы знаем, что все это дым и зеркала.

Я должен упомянуть слово о типизации уток, так как некоторые люди приходят в ужас от одноэлементных методов в Ruby. Типизация утки: «Если она ходит как утка и крякает как утка, то это должна быть утка». В Ruby «тип» объекта — это набор одноэлементных методов, на которые он реагирует. Это не связано с определением класса.

Я знаю, вы, возможно, слышали, как дядя Бен Спайдермен в своей голове: «С большой силой приходит большая ответственность». Руби всегда предполагает, что вы ответственны за использование этой мощной техники.

Поиск метода в действии

Вооружившись нашим уверенным знанием Ruby, мы должны быть в состоянии пройти наш путь к глубокому концу. Давайте напишем быструю «лабораторную крысу» программу, чтобы мы могли проверить объектную модель на работе:

 class C; def an_instance_method; end; end def C.a_class_method; end class E < C; end obj = E.new def obj.a_singleton_method; end 

Теперь давайте сделаем один шаг прямо к «классу» и по цепочке предков, чтобы увидеть, как все устроено:

 arr = [:an_instance_method, :a_class_method, :a_singleton_method] obj.eigenclass # => #<Class:#<E:0x007f8b2a85cd40>> obj.eigenclass.instance_methods.select { |m| arr.include?(m) } # => [:a_singleton_method, :an_instance_method] obj.eigenclass.superclass # => E obj.class # => E E.superclass # => C C.instance_methods.select { |m| arr.include?(m) } # => [:an_instance_method] C.superclass # => Object Object.superclass # => BasicObject BasicObject.superclass # => nil 

Важно отметить, что суперкласс собственного класса объекта является классом объекта. Я рекомендую повторять это последнее предложение снова и снова, пока все просто не щелкнет. Цепочка предков остается на классах, поэтому мы можем найти методы внутри собственного класса obj. Как показано :an_instance_method принадлежит C Кроме того, instance_methods извлекает методы из цепочки предков.

А как насчет собственных классов наших классов?

 E.eigenclass # => #<Class:E> E.eigenclass.superclass # => #<Class:C> C.eigenclass.instance_methods.select { |m| arr.include?(m) } # => [:a_class_method] C.eigenclass.superclass # => #<Class:Object> Object.eigenclass.superclass # => #<Class:BasicObject> BasicObject.eigenclass.superclass # => Class Class.superclass # => Module Module.superclass # => Object 

Глядя на приведенный выше пример, суперкласс собственного класса класса является собственным классом суперкласса класса. Теперь повторите это последнее предложение, супер-быстро! Цепочка предков для классов и собственных классов объединяется в Object . Видите ли, плавать с «большими парнями» не так уж и сложно.

Но как насчет модулей?

Оказывается, в Ruby модули мало чем отличаются от классов. Главное отличие в том, что они не отображаются в цепочке предков при вводе суперкласса. Давайте посмотрим, есть ли способ отслеживать модули, попробуйте:

 module F; def a_module_method; end; end class E; include F; end E.superclass # => C E.ancestors # => [E, F, C, Object, Kernel, BasicObject] 

Как вы можете видеть, когда вы добавляете модуль, он добавляется в цепочку предков. Мы можем проверить это с помощью:

 E.instance_methods.include?(:a_module_method) # => true 

В заключение

Хорошо, теперь пришло время для поп-викторины!

Можете ли вы сказать мне, что происходит, когда я делаю это?

 module F; class G; end; end 

Как это выглядит в цепочке предков? Есть ли способ создать экземпляр класса G? Если вы полностью потерялись, оставляйте комментарии.

Объектная модель Ruby таинственно увлекательна и проста. Как только вы обернетесь вокруг него, весь язык будет казаться, что он встал на свои места. Я надеюсь, что теперь вы стали лучше понимать этот прекрасный язык.

Счастливого взлома!