Модули и Примеси в Ruby

августа 9, 2010  |  Published in Ruby, Основы  |  12 Comments

ruby modulesМодули являются отличным решением для группирования методов, классов и констант вместе.
Использую модули можно извлечь две значительные выгоды:

-Модули обеспечивают пространства имен и, следовательно, предотвращают возникающие с именованием методов, классов и констант ошибки.

-Модули реализуют примеси (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 множественное наследование чуждо. Это сделано с целью избежать проблем возникающих при использовании множественного наследования в других языках программирования.

Данная статья является переводом оригинальной статьи

Tags: ,

Responses

  1. Vitaly says:

    августа 18, 2010 at 19:21 (#)

    Не совсем понял с примесями, если мы включам два модуля в которых есть одинаковые методы, то как класс Sample из примера их отличит?

    samp=Sample.new
    samp.a1
    samp.a2
    samp.a1 <— метод модуля B, называется как метод a1 модуля A
    samp.b2
    samp.s1

  2. says:

    августа 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»

  3. Alexey says:

    августа 29, 2011 at 16:01 (#)

    Стоит заметить, что лучше писать так:

    Module A
      def self.my_method
        ...
      end
    end
    
  4. Restless says:

    января 5, 2012 at 08:01 (#)

    Хорошая инфа, но подача отвратительная. Вы, если уж беретесь писать на какую то тематику, то делайте это достойно. Вылезая в топ со своим блогом вы как бы неформально несете ответственность за корректность подачи материала. Неправильно поданный материал в конечном итоге ворует время у ваших потенциальных читателей. Не забывайте об этом!

  5. admin says:

    января 5, 2012 at 12:59 (#)

    Restless, что конкретно не нравится?

  6. Андрей says:

    января 18, 2012 at 20:46 (#)

    У меня вопрос по поводу примесей двух модулей, у которых есть одинаковые методы. Возьмём пример Максима с модулями А,B и классом Sample. В класс Sample включаются эти 2 модуля.
    Вопрос:
    Можно ли вообще как то вызвать в отношении объекта конкретные методы модулей?
    Например:
    s.some_method => «a» #здесь я хочу конкретно использовать метод модуля А

    s.some_method => «b» #здесь я хочу конкретно использовать метод модуля В

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

  7. admin says:

    января 18, 2012 at 22:59 (#)

    Андрей, да, можно использовать следующие вызовы:

    A.method_name
    A::method_name

    если это методы самого модуля, то есть объявлены через self.

  8. anon says:

    января 30, 2012 at 14:29 (#)

    Владимир, поставь на сайт , так легче всего сообщать о твоих опечатках =) А то захламлять комментарии подобными сообщениями не комильфо.

  9. Duke says:

    октября 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 был. А инстанс методов всё равно нет, поэтому включение этого модуля ровным счётом ничего не даст. Желательно бы исправить, а-то может сбить с толку.

  10. TIT says:

    ноября 12, 2012 at 06:35 (#)

    Подскажите, а можно включить классы в модуль, которые находятся в других файлах?
    Например, у меня есть n файлов с классами (class1.rb, class2.rb .. class5.rb) и есть модуль Global. Я хочу использовать классы через главный модуль. MyModule::MyClass1.new. Это возможно?

  11. psylone says:

    мая 24, 2013 at 01:24 (#)

    TIT, да, можно. Но с одной оговоркой. Так как require всегда выполняется в глобальном пространстве имён, то если вы сделаете что-то подобное:

    module MyModule
      require "./class1.rb"
      require "./class2.rb"
    end
    

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

    в файле class1.rb

    module MyModule
      class Class1
      end
    end
    

    в файле class2.rb

    module MyModule
      class Class2
      end
    end
    

    и тогда в файле my_module.rb можно сделать require так:

    require "./class1.rb"
    require "./class2.rb"
    
    puts MyModule::Class1 # => "MyModule::Class1"
    puts Class2 # => uninitialized constant Class2 (NameError)
    
  12. Дмитрий says:

    апреля 7, 2014 at 06:29 (#)

    Одно замечание. Если модули включать следующим образом:

    include A, B

    И в модулях есть методы с одинаковыми именами, то методы из модуля А будут перекрывать методы из модуля B.

    Если таким образом:

    include A
    include B

    То все с точностью до наоборот. Методы модуля B перекрывают методы модуля A.

Leave a Response

Для подсветки кода используйте BB - коды: [language]...[/language], где language может быть: ruby, javascript, css, html.