Лучше всего я понимаю миксины как виртуальные классы. Миксины - это «виртуальные классы», которые были введены в цепочку предков класса или модуля.
Когда мы используем "include" и передаем ему модуль, он добавляет модуль в цепочку предков прямо перед классом, от которого мы наследуем:
class Parent
end
module M
end
class Child < Parent
include M
end
Child.ancestors
=> [Child, M, Parent, Object ...
Каждый объект в Ruby также имеет одноэлементный класс. Методы, добавленные к этому одноэлементному классу, могут вызываться непосредственно для объекта, поэтому они действуют как методы «класса». Когда мы используем "extension" для объекта и передаем объекту модуль, мы добавляем методы модуля к одноэлементному классу объекта:
module M
def m
puts 'm'
end
end
class Test
end
Test.extend M
Test.m
Мы можем получить доступ к одноэлементному классу с помощью метода singleton_class:
Test.singleton_class.ancestors
=> [#<Class:Test>, M, #<Class:Object>, ...
Ruby предоставляет несколько ловушек для модулей, когда они смешиваются с классами / модулями. included
- это метод-перехватчик, предоставляемый Ruby, который вызывается всякий раз, когда вы включаете модуль в какой-либо модуль или класс. Как и в случае с включенным, есть связанный extended
крючок для расширения. Он будет вызываться, когда модуль расширяется другим модулем или классом.
module M
def self.included(target)
puts "included into #{target}"
end
def self.extended(target)
puts "extended into #{target}"
end
end
class MyClass
include M
end
class MyClass2
extend M
end
Это создает интересный паттерн, который могут использовать разработчики:
module M
def self.included(target)
target.send(:include, InstanceMethods)
target.extend ClassMethods
target.class_eval do
a_class_method
end
end
module InstanceMethods
def an_instance_method
end
end
module ClassMethods
def a_class_method
puts "a_class_method called"
end
end
end
class MyClass
include M
# a_class_method called
end
Как вы можете видеть, этот единственный модуль добавляет методы экземпляра, методы «класса» и действует непосредственно на целевой класс (в данном случае вызывая a_class_method ()).
ActiveSupport :: Concern инкапсулирует этот шаблон. Вот тот же модуль, переписанный для использования ActiveSupport :: Concern:
module M
extend ActiveSupport::Concern
included do
a_class_method
end
def an_instance_method
end
module ClassMethods
def a_class_method
puts "a_class_method called"
end
end
end