Вопрос по ruby – Наследование методов класса от модулей / миксинов в Ruby

79

Известно, что в Ruby методы класса наследуются:

class P
  def self.mm; puts 'abc' end
end
class Q < P; end
Q.mm # works

Однако меня удивляет, что он не работает с миксинами:

module M
  def self.mm; puts 'mixin' end
end
class N; include M end
M.mm # works
N.mm # does not work!

Я знаю, что метод #extend может сделать это:

module X; def mm; puts 'extender' end end
Y = Class.new.extend X
X.mm # works

Но я пишу миксин (или, скорее, хотел бы написать), содержащий как методы экземпляра, так и методы класса:

module Common
  def self.class_method; puts "class method here" end
  def instance_method; puts "instance method here" end
end

Теперь я хотел бы сделать следующее:

class A; include Common
  # custom part for A
end
class B; include Common
  # custom part for B
end

Я хочу, чтобы A, B наследовали методы экземпляра и класса отCommon модуль. Но, конечно, это не работает. Так есть ли секретный способ заставить это наследование работать из одного модуля?

Мне кажется нелегким разделить это на два разных модуля, один для включения, другой для расширения. Другим возможным решением будет использование классаCommon вместо модуля. Но это всего лишь обходной путь. (Что делать, если есть два набора общих функцийCommon1 а такжеCommon2 и нам действительно нужны миксины?) Есть ли глубокая причина, по которой наследование методов класса не работает от миксинов?

возможный дубликатIs that possible to define a class method in a module? Phrogz
С той разницей, что здесь я знаю, что это возможно - я прошу наименее уродливый способ сделать это и по причинам, по которым наивный выбор не работает. Boris Stitnicky
Имея больше опыта, я понял, что Руби зашел бы слишком далеко, угадывая намерение программиста, если бы включение модуля также добавило методы модуля к одноэлементному классу включения. Это потому, что "методы модуля" на самом деле ничего, кроме одноэлементных методов. Модули не являются специальными для использования одноэлементных методов, они являются специальными для того, чтобы быть пространствами имен, где определены методы и константы. Пространство имен совершенно не связано с одноэлементными методами модуля, поэтому на самом деле наследование классов одноэлементных методов более удивительно, чем его отсутствие в модулях. Boris Stitnicky

Ваш Ответ

4   ответа
5

для парней, которые уже находятся в Rails (или не возражают в зависимости отАктивная поддержка), Concern здесь полезно:

require 'active_support/concern'

module Common
  extend ActiveSupport::Concern

  def instance_method
    puts "instance method here"
  end

  class_methods do
    def class_method
      puts "class method here"
    end
  end
end

class A
  include Common
end
2

module M
  def self.included(base)
    base.class_eval do # do anything you would do at class level
      def self.doit #class method
        @@fred = "Flintstone"
        "class method doit called"
      end # class method define
      def doit(str) #instance method
        @@common_var = "all instances"
        @instance_var = str
        "instance method doit called"
      end
      def get_them
        [@@common_var,@instance_var,@@fred]
      end
    end # class_eval
  end # included
end # module

class F; end
F.include M

F.doit  # >> "class method doit called"
a = F.new
b = F.new
a.doit("Yo") # "instance method doit called"
b.doit("Ho") # "instance method doit called"
a.get_them # >> ["all instances", "Yo", "Flintstone"]
b.get_them # >> ["all instances", "Ho", "Flintstone"]

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

154

included оттуда подключить и внедрить методы класса.

module Foo
  def self.included base
    base.send :include, InstanceMethods
    base.extend ClassMethods
  end

  module InstanceMethods
    def bar1
      'bar1'
    end
  end

  module ClassMethods
    def bar2
      'bar2'
    end
  end
end

class Test
  include Foo
end

Test.new.bar1 # => "bar1"
Test.bar2 # => "bar2"
includeError: User Rate Limit ExceededextendError: User Rate Limit Exceeded
Error: User Rate Limit Exceeded Boris Stitnicky
Error: User Rate Limit ExceededincludedError: User Rate Limit Exceeded
Error: User Rate Limit Exceededthis threadError: User Rate Limit Exceeded"why?".
Error: User Rate Limit Exceeded
29

объясняющая необходимые концепции метапрограммирования, необходимые для понимания того, почему включение модуля работает так же, как в Ruby.

What happens when a module is included?

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

module M
  def foo; "foo"; end
end

class C
  include M

  def bar; "bar"; end
end

C.ancestors
#=> [C, M, Object, Kernel, BasicObject]
#       ^ look, it's right here!

Когда вы вызываете метод в экземпляреCРуби рассмотрит каждый элемент этого списка предков, чтобы найтиinstance method с предоставленным именем. Так как мы включилиM вC, M теперь предокCпоэтому, когда мы звонимfoo на примереCRuby найдет этот метод вM:

C.new.foo
#=> "foo"

Обратите внимание, чтоthe inclusion does not copy any instance or class methods to the class & # X2013; он просто добавляет «примечание» классу, который также должен искать методы экземпляра во включенном модуле.

What about the "class" methods in our module?

Поскольку включение только изменяет способ отправки методов экземпляра, включая модуль в классonly makes its instance methods available в этом классе. & Quot; класс & quot; методы и другие объявления в модуле не копируются автоматически в класс:

module M
  def instance_method
    "foo"
  end

  def self.class_method
    "bar"
  end
end

class C
  include M
end

M.class_method
#=> "bar"

C.new.instance_method
#=> "foo"

C.class_method
#=> NoMethodError: undefined method `class_method' for C:Class
How does Ruby implement class methods?

В Ruby классы и модули являются простыми объектами & # x2013; они являются экземплярами классаClass а такжеModule, Это означает, что вы можете динамически создавать новые классы, назначать их переменным и т. Д .:

klass = Class.new do
  def foo
    "foo"
  end
end
#=> #<Class:0x2b613d0>

klass.new.foo
#=> "foo"

Также в Ruby у вас есть возможность определить так называемыеsingleton methods на объектах. Эти методы добавляются как новые методы экземпляра в специальный, скрытыйsingleton class объекта:

obj = Object.new

# define singleton method
def obj.foo
  "foo"
end

# here is our singleton method, on the singleton class of `obj`:
obj.singleton_class.instance_methods(false)
#=> [:foo]

Но разве классы и модули тоже не просто объекты? На самом деле они есть! Значит ли это, что у них тоже могут быть одноэлементные методы? Да, это так! И вот как рождаются методы класса:

class Abc
end

# define singleton method
def Abc.foo
  "foo"
end

Abc.singleton_class.instance_methods(false)
#=> [:foo]

Или, более распространенным способом определения метода класса является использованиеself внутри блока определения класса, который ссылается на создаваемый объект класса:

class Abc
  def self.foo
    "foo"
  end
end

Abc.singleton_class.instance_methods(false)
#=> [:foo]
How do I include the class methods in a module?

Как мы только что установили, методы класса на самом деле являются просто методами экземпляра в одноэлементном классе объекта класса. Значит ли это, что мы можем простоinclude a module into the singleton class добавить кучу методов класса? Да, это так!

module M
  def new_instance_method; "hi"; end

  module ClassMethods
    def new_class_method; "hello"; end
  end
end

class HostKlass
  include M
  self.singleton_class.include M::ClassMethods
end

HostKlass.new_class_method
#=> "hello"

этоself.singleton_class.include M::ClassMethods линия выглядит не очень красиво, поэтому Руби добавилObject#extend, который делает то же самое & # x2013; то есть включает модуль в синглтон-класс объекта:

class HostKlass
  include M
  extend M::ClassMethods
end

HostKlass.singleton_class.included_modules
#=> [M::ClassMethods, Kernel]
#    ^ there it is!
Moving the extend call into the module

Этот предыдущий пример не является хорошо структурированным кодом по двум причинам:

We now have to call both include and extend in the HostClass definition to get our module included properly. This can get very cumbersome if you have to include lots of similar modules. HostClass directly references M::ClassMethods, which is an implementation detail of the module M that HostClass should not need to know or care about.

Так как насчет этого: когда мы звонимinclude в первой строке мы как-то уведомляем модуль о том, что он включен, а также передаем ему наш объект класса, чтобы он мог вызыватьextend сам. Таким образом, работа модуля заключается в добавлении методов класса, если он этого хочет.

Это именно то, чтоspecial self.included method для. Ruby автоматически вызывает этот метод всякий раз, когда модуль включается в другой класс (или модуль), и передает объект класса хоста в качестве первого аргумента:

module M
  def new_instance_method; "hi"; end

  def self.included(base)  # `base` is `HostClass` in our case
    base.extend ClassMethods
  end

  module ClassMethods
    def new_class_method; "hello"; end
  end
end

class HostKlass
  include M

  def self.existing_class_method; "cool"; end
end

HostKlass.singleton_class.included_modules
#=> [M::ClassMethods, Kernel]
#    ^ still there!

Конечно, добавление методов класса - не единственное, что мы можем сделать вself.included, У нас есть объект класса, поэтому мы можем вызвать любой другой (класс) метод:

def self.included(base)  # `base` is `HostClass` in our case
  base.existing_class_method
  #=> "cool"
end

Похожие вопросы