Модули и Примеси в Ruby
августа 9, 2010 | Published in Ruby, Основы | 12 Comments
Модули являются отличным решением для группирования методов, классов и констант вместе.
Использую модули можно извлечь две значительные выгоды:
-Модули обеспечивают пространства имен и, следовательно, предотвращают возникающие с именованием методов, классов и констант ошибки.
-Модули реализуют примеси (mixins) — элегантное решение заменяющее множественное наследование.
Модули определяют пространства имен, песочницу в которой ваши методы и константы могут работать без возникновения проблем с другими методами и константами, которые имеют такие же имена.
Так выглядит модуль:
module Identifier
statement1
statement2
………..
end
Модульные константы именуются тем же образом, что константы в обычных классах, с первой заглавной буквой в названии. Определение методов выглядит аналогично: модульный метод объявляется так же как и метод класса.
Так же как с методами класса, мы вызываем методы модуля, но перед именами методов мы пишем имя модуля и разделитель ::, таким образом мы ссылаемся на методы класса используя конструкцию модуль::метод, где двойное двоеточие разделяет имя модуля и имя метода.
Пример:
#!/usr/bin/ruby # Модуль определен в файле trig.rb module Trig PI = 3.141592654 def Trig.sin(x) # .. end def Trig.cos(x) # .. end end
Мы можем определять несколько модулей с одинаковыми именами функций (методов), но с различной функциональностью:
#!/usr/bin/ruby # Модуль объявлен в файле moral.rb module Moral VERY_BAD = 0 BAD = 1 def Moral.sin(badness) # ... end end
Мы можем определять методы модуля так же, как и методы класса, разделяя при определении методы имя метода от имени модуля точкой.
Использование метода require
В Ruby require используется так же, как и include в Си и Си++ и как import в Java. Если программа нуждается в использовании каких-либо определенных модулей, она может просто загрузить файлы с модулями используя require:
Синтаксис:
require filename
Здесь не обязательно вписывать после имени файла расширение .rb.
Пример:
require 'trig.rb' require 'moral' y = Trig.sin(Trig::PI/4) wrongdoing = Moral.sin(Moral::VERY_BAD)
ВАЖНО: Здесь оба файла содержат методы с одинаковыми именами, из-за чего возникает двусмысленность кода, однако модули позволяют избежать этой двусмысленности путем указания того, какому модулю принадлежит тот или иной метод.
Использование include в Ruby
Мы можем включать в класс модули. Для включения модуля в класс используется include:
Синтаксис:
include modulename
Если модуль объявлен в отдельном файле, то он нуждается в включении этого файла используя require перед включением самого модуля в класс.
Рассмотрим следующий модуль написанный в файле Week.rb.
module Week FIRST_DAY = "Sunday" def Week.weeks_in_month puts "You have four weeks in a month" end def Week.weeks_in_year puts "You have 52 weeks in a year" end end
Теперь мы можем включить этот модуль в класс следующим образом:
#!/usr/bin/ruby require "Week" class Decade include Week no_of_yrs=10 def no_of_months puts Week::FIRST_DAY number=10*12 puts number end end d1=Decade.new puts Week::FIRST_DAY Week.weeks_in_month Week.weeks_in_year d1.no_of_months
В результате этого получим следующий результат:
Sunday
You have four weeks in a month
You have 52 weeks in a year
Sunday
120
Примеси в Ruby
Я предполагаю, что у вас есть знания ООП, которые необходимы для понимания следующей части статьи.
Если класс может наследоваться от более чем одного родительского класса, то данный класс использует множественное наследование.
Ruby не поддерживает множественного наследования, но модули в Ruby дают другую, прекрасную альтернативу множественному наследованию. Эта альтернатива позволяет использовать примеси (mixins), вместо множественного наследования.
Примеси дают нам отличный контроль над добавлением функциональности классам, однако, их истинная сила проявляется когда код в примесях начинает взаимодействовать с кодом класса, который их использует.
Давайте рассмотрим следующий пример кода для понимания примесей:
module A def a1 end def a2 end end module B def b1 end def b2 end end class Sample include A include B def s1 end end samp=Sample.new samp.a1 samp.a2 samp.b1 samp.b2 samp.s1
Модуль А состоит из методов а1 и а2. Модуль В состоит из методов b1 и b2. Класс Sample включает в себя оба модуля A и B. Класс Sample может получать доступ ко всем четырем методам с именами a1, a2, b1 и b2. Поэтому, мы можем видеть что класс Sample наследуется от обоих модулей. Таким образом мы можем сказать, что класс Sample наследуется от нескольких модулей, при том, что для Ruby множественное наследование чуждо. Это сделано с целью избежать проблем возникающих при использовании множественного наследования в других языках программирования.
Данная статья является переводом оригинальной статьи
августа 18, 2010 at 19:21 (#)
Не совсем понял с примесями, если мы включам два модуля в которых есть одинаковые методы, то как класс Sample из примера их отличит?
samp=Sample.new
samp.a1
samp.a2
samp.a1 <— метод модуля B, называется как метод a1 модуля A
samp.b2
samp.s1
августа 19, 2010 at 22:01 (#)
module A
def some_method
‘a’
end
end
module B
def some_method
‘b’
end
end
class Sample
include A
include B
end
Если в класс Sample включаются два модуля A и B, которые содержат методы с одинаковыми именами, то метод из последнего включенного модуля B перекрывает метод из ранее включенного модуля A.
s = Sample.new
s.some_method => «b»
Если в методе some_method из модуля B нужно получить доступ к перекрытому методу some_method из модуля A, то мы можем использовать ключевое слово super.
module B
def some_method
super + ‘b’
end
end
s.some_method => «ab»
августа 29, 2011 at 16:01 (#)
Стоит заметить, что лучше писать так:
января 5, 2012 at 08:01 (#)
Хорошая инфа, но подача отвратительная. Вы, если уж беретесь писать на какую то тематику, то делайте это достойно. Вылезая в топ со своим блогом вы как бы неформально несете ответственность за корректность подачи материала. Неправильно поданный материал в конечном итоге ворует время у ваших потенциальных читателей. Не забывайте об этом!
января 5, 2012 at 12:59 (#)
Restless, что конкретно не нравится?
января 18, 2012 at 20:46 (#)
У меня вопрос по поводу примесей двух модулей, у которых есть одинаковые методы. Возьмём пример Максима с модулями А,B и классом Sample. В класс Sample включаются эти 2 модуля.
Вопрос:
Можно ли вообще как то вызвать в отношении объекта конкретные методы модулей?
Например:
s.some_method => «a» #здесь я хочу конкретно использовать метод модуля А
s.some_method => «b» #здесь я хочу конкретно использовать метод модуля В
Может это можно указать как-то через пространство имён?
Просто разве ни у кого не возникало проблем , когда инклюдишь несколько модулей , а в них одинаковые методы, и нужно в разных случаях использовать то один метод, то другой в отношении одного и того же объекта?
января 18, 2012 at 22:59 (#)
Андрей, да, можно использовать следующие вызовы:
A.method_name
A::method_name
если это методы самого модуля, то есть объявлены через self.
января 30, 2012 at 14:29 (#)
Владимир, поставь на сайт , так легче всего сообщать о твоих опечатках =) А то захламлять комментарии подобными сообщениями не комильфо.
октября 9, 2012 at 13:05 (#)
Ошибочка, которая влияет на восприятие и понимание.
Теперь мы можем включить этот модуль в класс следующим образом:
#!/usr/bin/ruby
require «Week»
class Decade
include Week
no_of_yrs=10
def no_of_months
puts Week::FIRST_DAY
number=10*12
puts number
end
end
Так как до этого были определены только self. методы в модуле, нет никакой необходимости включать модуль в класс, работать будет и без include. Главное чтоб require был. А инстанс методов всё равно нет, поэтому включение этого модуля ровным счётом ничего не даст. Желательно бы исправить, а-то может сбить с толку.
ноября 12, 2012 at 06:35 (#)
Подскажите, а можно включить классы в модуль, которые находятся в других файлах?
Например, у меня есть n файлов с классами (class1.rb, class2.rb .. class5.rb) и есть модуль Global. Я хочу использовать классы через главный модуль. MyModule::MyClass1.new. Это возможно?
мая 24, 2013 at 01:24 (#)
TIT, да, можно. Но с одной оговоркой. Так как require всегда выполняется в глобальном пространстве имён, то если вы сделаете что-то подобное:
то оба класса будут доступны в глобальном пространстве имен. Для того, чтобы реализовать вашу схему нужно определять классы таким образом:
в файле class1.rb
в файле class2.rb
и тогда в файле my_module.rb можно сделать require так:
апреля 7, 2014 at 06:29 (#)
Одно замечание. Если модули включать следующим образом:
include A, B
И в модулях есть методы с одинаковыми именами, то методы из модуля А будут перекрывать методы из модуля B.
Если таким образом:
include A
include B
То все с точностью до наоборот. Методы модуля B перекрывают методы модуля A.