RDRubyTutorial > Рубиновые модули
июля 12, 2011 | Published in Ruby, Основы | 5 Comments
Обещал, что RubyDev Ruby tutorial будет закрыт, но не тут то было!
Вы уже должно быть прочитали главу посвященную классам и узнали о наследовании классов. Часто необходимо одмин классом наследоваться от нескольких классов, что называется множественным наследованием, однако Ruby по ряду причин не располагает возможностью множественного наследования. Отсутствие множественного наследования в Ruby несколько не препятствие и не показатель бедности и недостаточной функциональности языка, напротив, отсутствие множественного наследование избавляет программиста от различных проблем связанных с конфликтом имен, в то же время Ruby предоставляет свой альтернативу множественному наследованию — примеси (mixins).
Модули как раз таки и служат для реализации этого самого подмешивания, а также для логического объединения и инкапсуляции(отделения от внешнего мира) кода.
Представим, что ваше приложение на Ruby состоит из нескольких классов, тогда, поскольку приложение является чем-то монолитным, его следует объединить в модуль. Для особо крупных приложений используется сразу несколько модулей, один основной (корневой), который содержит приложение целиком, и несколько которые объединяют части кода приложения по определенному признаку, например в Rails имеетсямодуль ActiveRecord, который хранит весь код реализующий паттерн ActiveRecord для работы с базой данных, модуль ActiveSupport, который включает в себя другие модули, например ActionController, который хранит в себе весь код реализующий контроллеры, сам ActiveSupport отвечает за реализацию VC от паттерна MVC (Model-Viev-Controller, если забыли).
Что такое модуль? — Модуль это просто еще одно пространство имен, коробка, в которую вы укладываете ваш код, для того, чтобы он не путался с другими составляющими программы. Можете представлять модуль как примитивный класс, который лишен некоторых функций. Модули, что подмешиваются в классы называются примесями (mixin), все, что они делают — это просто дают классу в который были подмешаны доступ ко всему своему содержимому.
Модуль — это объект (да, да, как и все остальное в Ruby).
$ ruby -v
ruby 1.9.2p180 (2011-02-18 revision 30909) [i686-linux]
$ irb
Module.class #=> Class Module.superclass #=> Object module Mod end Mod.class #=> Module
В Ruby имеется несколько поставляемых с ядром и со стандартной библиотекой модулей, давайте рассмотрим один из самых простых модулей — модуль Math, который предоставляет различные методы реализующие математические функции, и константы.
Math::PI #=> 3.141592653589793 Math::E #=> 2.718281828459045
Вот так модуль Math предоставляет нам математические константы — число Пи и экспоненту. А еще Math предоставляет методы тригонометрических функций (аргументы в радианах, чтобы получить значение в градусах необходимо аргумент*Math::PI/180):
Math::cos(45) #=> 0.5253219888177297 Math::cos(45) #=> 0.5253219888177297 Math::sin(45) #=> 0.8509035245341184 Math::tan(45) #=> 1.6197751905438615 Math::atan(45) #=> 1.5485777614681775 Math::log10(10) #=> 1.0 Math::log10(100) #=> 2.0 Math::log2(4) #=> 2.0 Math::log(1) #=> 0.0 Math::cos(45*Math::PI/180) #=> 0.7071067811865476
Представьте, как было бы удобно использовать методы типа: fixnum.cos. Для этого мы можем совсем просто использовать модуль в нашем классе:
class Fixnum def method_missing(name) Math.send(name, self) end end 45.cos #=> 0.5253219888177297
Но это не совсем то, для чего модули предназначены. Давайте научимся подмешивать модуль в класс, это делается при помощи метода include:
class Integer include Math end
Что нам это дает? Мы подмешали в наш класс код модуля Math и можем его там использовать:
class Integer include Math def cos super(self*PI/180) end def sin super(self*PI/180) end end Math::cos(45) #=> 0.5253219888177297 Math::cos(45*Math::PI/180) #=> 0.7071067811865476 45.cos #=> 0.7071067811865476
Теперь мы значительно упростили вычисление косинуса и синуса числа, более того, теперь используется более привычное, лично для меня, измерение угла в градусах, а не в радианах.
Что произойдет, если мы подмешаем второй модуль с теми же именами методов?
module AntiMath def cos nil end def sin nil end end class Integer include AntiMath end 45.cos #=> ArgumentError: wrong number of arguments (1 for 0)
Как видите, второй модуль AntiMath переписал методы cos и sin из модуля Math, точнее не переписал, а подменил методы cos и sin для Integer на свои собственные, для которых агрументы не определены, из-за чего и возникает ошибка ArgumentError. Как с этим бороться? — Просто используйте имя модуля перед именем вызываемого модуля для того, чтобы не возникало такого конфликта имен, как в примере выше, то есть используйте Math::cos вместо просто cos.
Помимо подмешивания методов объектов из модуля, вы также можете подмешать методы класса используя метод extend вместо include:
module ABC def my_method "Hello!" end end class Integer extend ABC end Integer.my_method #=> "Hello!"
Используя include все методы, что содержатся в модуле становятся методами объектов, а использую extend — методами класса.
Модули также могут включать в себя методы обозначенные как public, private и protected, которые при подмешивании в класс сохраняют свое объявление. Модули также могут содержать свои собственные методы, которые объявлены для self:
module ABC def self.hi "Hi!" end end Integr.hi #=> NoMethodError
Как видите, это методы не наследуются, однако могут быть использованы из модуля:
class Integer def hi puts ABC::hi puts ABC.hi end end 2.hi #Hi! #Hi!
Что сделать, чтобы одни методы были методами класса, а другие — методами экземпляра класса при подмешивании модуля? — Ответ прост! Для этого следует все методы, которые после подмешивания должны становиться методами класса, поместить в отдельный модуль!
module MyModule def hello "hello" end module ClassMethods def bye "bye" end end end class MyClass include MyModule extend MyModule::ClassMethods end MyClass.new.hello #=> "hello" MyClass.new.bye #=> NoMethodError MyClass.bye #=> "bye" MyClass.hello #=> NoMethodError
Специальные методы модулей. Модули, так же как и классы, имеют специальные методы. В классах такими специальными методами являются, например инициализатор (конструктор) — initialize, который автоматически выполняется при создании нового экземпляра класса. В модулях, существуют методы included и embeded, которые автоматически выполняются при том или ином способе подмешивания модуля.
module MyMod def self.included(includer) puts "#{self} included in #{includer}" end end class Integer include MyMod end #=> MyMod included in Integer
Подробнее о конфликте имен
Конфликт имен — это конфликт возникающий между двумя одноименными методами, классами, переменными и т.д., которые объявлены в классе и модуле или только в подмешиваемых модулях. Чтобы избежать этого необходимо правильно организовывать ваш код в модулях и классах.
Важно понимать: Методы, что содержатся в модулях не являются методами модуля, вы никак их не сможете вызвать от туда, если только при объявлении методов перед их именами не стоял указатель self. Если методы объявлены как def self.method_name, то такие методы являются методами модуля и могут быть вызваны через Module.method_name или Module::method_name.
module MyMod def self.mymethod puts "mymethod" end def MyMod::another_method puts "another method" end end MyMod.mymethod #mymethod MyMod::mymethod #mymethod MyMod.another_method #another method MyMod::another_method #another method
Лично я предпочитаю использовать два двоеточия при обращении к внутренним модулям и классам и одну точку — разделитель при обращении к методам модуля.
При обращении к внешним модулям в контексте модуля необходимо использовать два двоеточия перед именем внешнего модуля, хотя это не обязательно. Использование двух двоеточий дас понять интерпретатору, что вы хотит использовать внешний модуль в том случае если имеется совпадение имен модулей (внешнего и внутреннего).
module A def self.hello puts "hello" end end module B def self.hello puts "hello" end end module Mod module B def self.hello puts "HELLO" end end def self.mod_hello puts B.hello puts Mod::B.hello puts ::B.hello puts A.hello end end Mod.mod_hello #HELLO #HELLO #hello #hello
И не забывайт о том, что хотя и сложно писать имя модуля перед именем метода или класса, вам следует это делать для того, чтобы избежать возможного конфликта имен. Используемый ранее пример кода, где подмешанный из модуля метод переписывался в классе с использование super является плохим стилем, вместо:
def my_method super.upcase end
следует писать:
def my_method Mod.my_method.upcase end
Кода больше, однако при подмешивании другого модуля в котором имеется метод my_method у вас не возникнет проблем!
Красивый код
Многоие люди, преимущественно новичка как я делаю подмешивание методов класса и экземпляра класса из модуля речез выделение методов класса в отдельный вложенный модуль и это правильно, однако, некоторые люди делают это неправильно, точнее не так красиво как это можно сделать, рассмотрим пример:
#Вместо такого стиля: module Mod module ClassMethods def palindrome?(word) word.downcase! word == word.reverse ? true : false end end def my_method puts "hello" end end class MyClass include Mod extend Mod::ClassMethods end MyClass.new.my_method #hello puts MyClass.palindrome?("Eye") #true #Использовать такой: module Mod def self.included(klass) klass.extend ClassMethods end module ClassMethods def palindrome?(word) word.downcase! word == word.reverse ? true : false end end def my_method puts "hello" end end class MyClass include Mod end MyClass.new.my_method #hello puts MyClass.palindrome?("Eye") #true
Благодаря этому, при подмешивании методов экземпляра класса из Mod, вы автоматически подмешиваете методы класса из Mod::ClassMethods.
Удачи вам в изучении Ruby!
Лучшая благодарность автору — ваши комментарии!
июля 12, 2011 at 19:04 (#)
Где вы были раньше. Толь вчера разбирался со всем этим с помощью англоязычных источников, а с английским у меня туго.
Но всё равно большое спасибо. На досуге обязательно перечитаю, что бы окончательно освоить материал.
июля 12, 2011 at 22:06 (#)
Не сочтите за грубость, но может сперва проверять статьи на наличие орфографических ошибок? LibreOffice, например, не? А по теме, спасибо за статью. Очень познавательно :-)
августа 1, 2011 at 09:54 (#)
Если модуль содержит и class methods и instance methods, то мне по душе немного другой подход подмешивания.
Где четко видно, какие методы для чего.
февраля 14, 2013 at 14:39 (#)
пипец, автор, проверяй плиз орфографию, запарило «спотыкаться» на ошибках типа «одмин классом», «предоставляет свой альтернативу множественному» и т.п. мелко, но неприятно.
за статью спасибо!
ноября 18, 2013 at 09:19 (#)
Нашел опечатки:
Часто необходимо одмин классом наследоваться от нескольких классов…
Часто необходимо одним классам наследоваться от нескольких классов…
———
Отсутствие множественного наследования в Ruby несколько не препятствие и не показатель бедности…
Отсутствие множественного наследования в Ruby нисколько не препятствие и не показатель бедности
———
…например в Rails имеетсямодуль ActiveRecord…
…например в Rails имеется модуль ActiveRecord…
А в остальном отличная статья, спасибо!