RDRubyTutorial > Рубиновые модули

июля 12, 2011  |  Published in Ruby, Основы  |  5 Comments

ruby modulesОбещал, что 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!

 

Лучшая благодарность автору — ваши комментарии!

Tags: ,

Responses

  1. c0va23 says:

    июля 12, 2011 at 19:04 (#)

    Где вы были раньше. Толь вчера разбирался со всем этим с помощью англоязычных источников, а с английским у меня туго.

    Но всё равно большое спасибо. На досуге обязательно перечитаю, что бы окончательно освоить материал.

  2. says:

    июля 12, 2011 at 22:06 (#)

    Не сочтите за грубость, но может сперва проверять статьи на наличие орфографических ошибок? LibreOffice, например, не? А по теме, спасибо за статью. Очень познавательно :-)

  3. Dmitriy Nesteryuk says:

    августа 1, 2011 at 09:54 (#)

    Если модуль содержит и class methods и instance methods, то мне по душе немного другой подход подмешивания.

    module MyMod
      def self.included(base)
        base.extend(ClassMethods)
        base.class_eval{include InstanceMethods}
      end
    
      module ClassMethods
        # your class methods
      end
    
      module InstanceMethods
        # your instance methods
      end 
    end
    

    Где четко видно, какие методы для чего.

  4. Artem says:

    февраля 14, 2013 at 14:39 (#)

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

  5. says:

    ноября 18, 2013 at 09:19 (#)

    Нашел опечатки:

    Часто необходимо одмин классом наследоваться от нескольких классов…

    Часто необходимо одним классам наследоваться от нескольких классов…

    ———

    Отсутствие множественного наследования в Ruby несколько не препятствие и не показатель бедности…

    Отсутствие множественного наследования в Ruby нисколько не препятствие и не показатель бедности

    ———

    …например в Rails имеетсямодуль ActiveRecord…
    …например в Rails имеется модуль ActiveRecord…

    А в остальном отличная статья, спасибо!

Leave a Response

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