Ruby元编程:如何使模块方法看到类的变量

时间:2017-02-04 10:51:49

标签: ruby metaprogramming

例如,我有一个模块和一个类:

module SimpleModule
  def self.class_hello
    puts "hello from #{@@name}"
  end
end

class SimpleClass
  @@name = 'StackOverFlow'
  def self.test
    SimpleModule.class_hello
  end
end

然后我通过从类调用模块方法进行测试:

SimpleClass.test

我遇到异常:

  

SimpleModule(NameError)中未初始化的类变量@@ name

我在这里知道,因为模块的范围与类范围不同。所以我的问题是:如何为SimpleModule范围共享SimpleClass范围?

我放了元编程,因为这里只是一个简单的例子,之后我将通过动态类调用动态模块来提升。 (这就是我不想使用某些关键字的原因,例如包含扩展

@Edit 实际上我想自己实现Ruby扩展。这是我已经开发的版本:

# implementation
class Class
  def custom_extend(module_name)
    module_name.methods(false).each do |method|
      define_singleton_method(method) do |*args, &block|
        module_name.send(method, *args, &block)
      end
    end
  end
end

这是我的自定义模块和测试类:

# -------------------------------------------------------------
# Demonstration
module SimpleModule
  def self.class_hello_world
    puts 'i am a simple module boss'
  end

  def self.class_hello_name
    puts "hello from #{@@name}"
  end
end

class SimpleClass
  custom_extend  SimpleModule
  @@name = 'StackOverFlow'
end

这是我的两个测试:

SimpleClass.class_hello_world  # work
SimpleClass.class_hello_name   # not work

3 个答案:

答案 0 :(得分:4)

更新回答

以下是您的代码的略微修改版本。不需要includeextendappend_featuresmodule_function。添加具有相同结构的custom_include并不困难。

更新:请务必阅读@ 7stud的answer,其结构和解释非常好。

class Class
  def custom_extend(module_name)
    module_name.instance_methods(false).each do |method|
      define_singleton_method(method) do |*args, &block|
        module_name.instance_method(method).bind(self).call(*args, &block)
      end
    end
  end
end

module SimpleModule
  def class_hello
    puts "hello from #{@name}"
  end
end

class SimpleClass
  @name = 'class'
  custom_extend SimpleModule

  def self.test
    class_hello
  end
end

SimpleClass.test
#=> hello from class

原始答案

通常的方式

通常的方式是:

module SimpleModule
  def class_hello
    puts "hello from #{@name}"
  end
end

class SimpleClass
  @name = 'StackOverFlow'
  extend SimpleModule

  def self.test
    class_hello
  end
end

SimpleClass.class_hello

但你不想要它。 (为什么?)

你的方式

在您的代码中,SimpleClassSimpleModule完全相互独立。很明显你得到NameError。您需要以某种方式传递name信息。

作为名称参数:

module SimpleModule
  def self.class_hello(name='')
    puts "hello from #{name}"
  end
end

class SimpleClass
  @@name = 'StackOverFlow'
  def self.test
    SimpleModule.class_hello(@@name)
  end
end

使用klass参数:

module SimpleModule
  def self.class_hello(calling_class=self)
    calling_class.class_eval{
      puts "hello from #{@name}"
    }
  end
end

class SimpleClass
  @name = 'StackOverFlow'
  def self.test
    SimpleModule.class_hello(self)
  end
end

SimpleClass.test

使用binding参数:

module SimpleModule
  def self.class_hello(b)
    puts "hello from #{b.eval('@@name')}"
  end
end

class SimpleClass
  @@name = 'StackOverFlow'
  def self.test
    SimpleModule.class_hello(binding)
  end
end

SimpleClass.test

使用my_ruby_extend SimpleModule

肯定可以使用自定义my_ruby_extend来完成。您需要显示所需的语法,以及您已经实现的内容。

这样,您可以告诉Ruby SimpleClassSimpleModule已关联。如果在SimpleModule中找不到方法或变量,可以在SimpleClass中找到它。

答案 1 :(得分:4)

  

我只是尝试重新实现extend作为练习。

ruby​​的extend()不能像这样工作:

module SimpleModule
  def self.class_hello_world
    puts 'i am a simple module boss'
  end

  def self.class_hello_name
    puts "hello from #{@@name}"
  end
end

class SimpleClass
  custom_extend  SimpleModule

例如,以下内容不起作用:

module Dog
  def self.greet
    puts "hello"
  end
end

class Cat
  extend Dog
end

Cat.greet

--output:--
`<main>': undefined method `greet' for Cat:Class (NoMethodError)

extend()的工作原理如下:

module Dog
  def greet
    puts "hello"
  end
end

class Cat
  extend Dog
end

Cat.greet

--output:--
hello

换句话说,extend()将模块实例方法 - 而不是模块方法(例如,前面带有self的方法名称) - 插入到Cat的单例类中(这是Cat的类方法所在的位置) 。在ruby中,include()extend()与模块方法无关(同样,方法名称前面有self)。模块在ruby中有两种用途:

  1. 作为命名空间,例如包含def self.method_name
  2. 作为混合物,例如包含def some_method
  3. include()和extend()处理#2。

    以下解决方案不适用于@@variables,但试图找出@@variables在红宝石中展示的所有扭曲和转弯并不值得付出努力 - 只是不要使用它们。使用类实例变量,即在任何def之外指定的@variables

    def my_extend(some_module)
      singleton_class.include some_module
    end
    
    module Dog
      def greet
        puts @greeting
      end
    
      private
      def sayhi
        puts "hi"
      end
    end
    
    class Cat
      @greeting = "hello"
      my_extend Dog
    end
    
    Cat.greet
    #Cat.sayhi  #=>`<main>': private method `sayhi' called for Cat:Class (NoMethodError) 
    
    Cat.class_eval {sayhi}  #Change self to the Cat class so the implicit
                            #self variable that calls sayhi is equal to Cat
    
    --output:--
    hello
    hi
    

    现在,您只需要实施my_include并将其替换为include。 :)

    以下是my_include()的一个镜头:

    class Class
    
      def my_include(module_)
        #For public and protected methods:
        module_.instance_methods(include_super=false).each do |meth_name|
          meth = module_.instance_method(meth_name)
          define_method(meth_name) do 
            meth.bind(self).call
          end
        end
    
        #For private methods:
        module_.private_instance_methods(include_super=false).each do |meth_name|
          meth = module_.instance_method(meth_name)
          define_method(meth_name) do
            meth.bind(self).call
          end
          private :"#{meth_name}"
        end
    
      end
    end
    
    module Dog
      def greet
        puts "hello"
      end
    
      def go
        puts "run, run run"
      end
    
      private
      def sayhi
        puts "hi"
      end
    
    end
    
    class Cat
      my_include Dog
    end
    
    c = Cat.new
    c.greet
    c.go
    c.sayhi 
    
    --output:--
    hello
    run, run run
     #=>`<main>': private method `sayhi' called for #<Cat:0x007fc014136f60> (NoMethodError)
    

    使用my_extend()

    class Class
    
      def my_include(module_)
        #For public and protected methods:
        module_.instance_methods(include_super=false).each do |meth_name|
          meth = module_.instance_method(meth_name)
          define_method(meth_name) do 
            meth.bind(self).call
          end
        end
    
        #For private methods:
        module_.private_instance_methods(include_super=false).each do |meth_name|
          meth = module_.instance_method(meth_name)
          define_method(meth_name) do
            meth.bind(self).call
          end
          private :"#{meth_name}"
        end
    
      end
    
      def my_extend(module_)
        singleton_class.my_include module_
      end
    
    end
    
    
    module Dog
      def greet
        puts @greeting
      end
    
      private
      def sayhi
        puts "hi"
      end
    end
    
    class Cat
      @greeting = "hello"
      my_extend Dog
    end
    
    Cat.greet
    #Cat.sayhi  #=>private method `sayhi' called for Cat:Class (NoMethodError)
    Cat.class_eval {sayhi}
    
    --output:--
    hello
    hi
    

答案 2 :(得分:0)

简化问题

您可以通过修改代码来简单地解决您的问题。

module SimpleModule
  def self.class_hello
    puts "hello from #{@@name}"
  end
end

class SimpleClass
  @@name = 'StackOverFlow'
end

SimpleModule.class_hello
  # NameError: uninitialized class variable @@name in SimpleModule

哪个班级?

显然,模块的模块方法class_hello必须被告知需要类变量&&name的类。因此,我们必须将该课程作为class_hello的论据。然后我们使用[Module#class_variable_get](https://ruby-doc.org/core-2.2.1/Module.html#method-i-class_variable_get )提取类变量的值。

module SimpleModule
  def self.class_hello(klass)
    puts "hello from #{ klass.class_variable_get(:@@name) }"
  end
end

SimpleModule.class_hello(SimpleClass)
  # hello from StackOverFlow