【问题标题】:What is self in class << self and why is this different from the Class instance that is defining this block of code?class << self 中的 self 是什么,为什么这与定义此代码块的 Class 实例不同?
【发布时间】:2016-10-02 14:06:47
【问题描述】:

下面的代码尝试创建一个 MyClass 类,它将调用 self.add 方法委托给调用方法 self.newAdd 返回的对象。

class MyAdd
      def add(a,b)
        a + b
      end
    end


class MyClass
  def self.newAdd
    MyAdd.new
  end

  def self.delegate(*methods, options)
    return unless options.has_key?(:to)

    methods.each do |method|
      define_method method, ->(*args, &prc) do
        delegated_to = self.send(options[:to])
        delegated_to.send(method, *args, &prc)
      end
    end
  end

  class << self
    debugger;
    delegate :add, to: :newAdd
  end
end

运行这段代码出现的错误是

NoMethodError: #&lt;Class:MyClass&gt; 的未定义方法“委托”

如果我导航到保存文件的目录并打开解释器,执行将在第四行到最后一行的调试器处停止。然后我可以查看 self 和 MyClass 的可用方法

require_relative './test.rb'
MyClass.methods - Object.methods  #[:newAdd, :delegate]
self.methods - Object.methods  #[:nesting]
self  # #<Class:MyClass>
self.class  # Class
MyClass.class  # Class

为什么selfMyClassclass &lt;&lt; self 范围内不同?更具体地说,为什么 class &lt;&lt; self 内部的 self 不能使用 delegate 方法?

【问题讨论】:

    标签: ruby


    【解决方案1】:

    为什么 self 与类内部的 MyClass 不一样

    因为class关键字总是改变作用域:

    class MyClass
      puts self  #=> MyClass
    
      class <<self
        puts self  #=>MyClass’s singleton class
      end
    end
    

    更具体地说,为什么方法委托对 self 不可用 在类 内

    class MyClass
    
      def self.delegate
        puts "executing MyClass.delegate()"
      end
    
      class <<self
        delegate 
      end
    
    end
    
    --output:--
    1.rb:8:in `singleton class': undefined local variable or method `delegate' for #<Class:MyClass> (NameError)
      from 1.rb:7:in `<class:MyClass>'
      from 1.rb:1:in `<main>'
    

    请注意,以下构造是等效的:

    class MyClass
    
      def self.delegate
        puts "executing MyClass.delegate()"
      end
    
    end
    
    MyClass.delegate
    
    --output:--
    executing MyClass.delegate()
    

    和:

    class MyClass
    
      class <<self
        def delegate
          puts "executing MyClass.delegate()"
        end
      end
    
    end
    
    MyClass.delegate
    
    --output:--
    executing MyClass.delegate()
    

    因此,您的代码相当于:

    class MyClass
    
      class <<self
        def delegate
          puts "executing MyClass.delegate()"
        end
    
        delegate
      end
    
    end
    

    如果你暂时忽略外部的 MyClass,那么你定义了一个这样的类:

    class <<self
      def delegate
        puts "executing MyClass.delegate()"
      end
    
      delegate
    end
    

    同样的结构可以这样复制:

    class Dog
      def bark
        puts “woof”
      end
    
      bark
    end
    

    这将产生相同类型的错误:

    1.rb:7:in `<class:Dog>': undefined local variable or method `bark' for Dog:Class (NameError)
        from 1.rb:1:in `<main>'
    
    1. 当你调用一个方法并且你没有指定一个接收者时,ruby 使用当前分配给 self 变量的任何对象作为接收者。

    2. 在方法内部,ruby 将调用该方法的对象分配给 self 变量。调用方法的对象和定义方法的类(对象)不是一回事。

    3. 在类内部,但在任何方法定义之外,ruby 将类(对象)分配给 self。

    请注意,可以在 Dog 类中调用 def 的是 Dog 类的 实例,例如吠()。类似地,单例类的实例可以在单例类中调用def,例如delegate()——单例类本身不能在单例类中调用def。它们被称为单例类的全部原因是因为单例类只有一个实例——在您的情况下,单例类的一个实例是 MyClass。结果MyClass可以调用delegate(),但是单例类不能调用delegate()。

    我真的不明白 eignclass 上的类方法是什么 但是。

    就我个人而言,我不使用术语eigenclass。在我看来,ruby 已经决定这个词是singleton class。如果您查看 Object 类的文档,其中没有包含 eigenclass 的方法名称,但其中有包含 singleton class 的方法名称。

    所有对象都有一个单例类。单例类是一个对象。因此,每个单例类也有一个单例类——这意味着单例类的链是无限的:

    class Dog
    end
    
    s1 = Dog.singleton_class
    puts s1  
    
    s2 = s1.singleton_class
    puts s2
    
    s3 = s2.singleton_class
    puts s3
    
    --output:--
    #<Class:Dog>
    #<Class:#<Class:Dog>>
    #<Class:#<Class:#<Class:Dog>>>
    

    这意味着你可以做这样的事情:

    class Dog
      class <<self  #s1
        class <<self #s2
          def greet  #Instances of s2 can call greet, and the only instance of s2 is s1.
            puts "hello"
          end
        end
      end
    end
    
    
    class Dog
      class <<self
        #Inside here self = Dog's singleton class = s1
        greet  #equivalent to Dogs_singleton_class.greet
      end
    end
    
    --output:--
    hello
    

    但是,我以前从未见过有人在他们的代码中使用过单例类 (s2) 的单例类。很久很久以前,我曾经回答过一个问题,但没人知道我在说什么。

    有一些方法查找图here,可能很有用。

    【讨论】:

      【解决方案2】:

      这是您的代码的一个重新编写的部分,可以正常工作:

      class MyClass
        def self.newAdd
          MyAdd.new
        end
      
        def self.delegate(*methods, options)
          return unless options.has_key?(:to)
      
          methods.each do |method|
            target = self
      
            define_method method, ->(*args, &prc) do
              delegated_to = target.send(options[:to])
              delegated_to.send(method, *args, &prc)
            end
          end
        end
      
        delegate :add, to: :newAdd
      end
      

      请注意,尽量强调new_add 之类的命名方法,并避免在其中使用大写字母。

      您不能使用const_get 来检索方法。您正在寻找的是method,但这并不总是最好的计划。 send 通常就足够了。

      【讨论】:

      • 您上面的代码委托了一个实例方法:添加到类方法:newAdd,对吗?我的意图是将类 MyClass 上调用的方法:add 委托给类方法:newAdd。实际上,我的意图更笼统:如果我在单例类中调用委托方法,那么define_method(委托内部)将在单例类内部定义一个实例方法,从而在类方法之间进行委托。如果我像你一样调用委托方法,我应该在实例方法之间获得委托。
      • 这让我想到了最初的问题(与您指出的关于在委托中使用 const_get 的错误无关):为什么我无法访问类中的委托方法
      • 此时我意识到我仍然对单例类到底是什么以及与普通类的区别感到困惑。毕竟,当我在类语句中调用define_method 时,我得到了一个实例方法,而当我在class
      • 顺便说一句,如果我没记错的话,我的目的是获得类似于 Rails 委托方法工作方式的功能。当用户向模型输入查询命令时,我看到了与我在 ActiveRecord 中使用的示例类似的内容:这些命令(where、on、select 等)被委托给另一个方法。此委托发生在 class railscasts.com/episodes/…
      • 在类的上下文中调用class &lt;&lt; self 最终会以一种可能会混淆哪些方法可用的方式切换上下文。我宁愿避免这种情况,细微差别很难解释,而且往往很容易被忽视,导致像你所拥有的那样的问题。你在这里绊倒的是Ruby eigenclass layer
      【解决方案3】:

      我发布的实际有效的代码版本如下:

      class MyAdd
        def add(a,b)
          a + b
        end
      end
      
      module Delegate
        def delegate(*methods, options)
          return unless options.has_key?(:to)
      
          # methods are the methods that should be delegated to 
          # the object returned by calling the method passed in options[:to]
          methods.each do |method|
            define_method method, ->(*args, &prc) do
              delegated_to = self.send(options[:to])
              delegated_to.send(method, *args, &prc)
            end
          end
        end
      end
      
      class MyClass
        extend Delegate
      
        def self.newAdd
          MyAdd.new
        end
      
        class << self
          extend Delegate
          delegate :add, to: :newAdd
        end
      end
      

      现在,如果我们从class &lt;&lt; self 中调用delegate :add, to: :newAdd,我们将对类方法add 的调用委托给类方法newAdd。原因是在class &lt;&lt; self 内部,其中selfMyClass 类,我们处于selfMyClass 的单例类的范围内。通过在这里扩展模块Delegate,我们在这个单例类上定义了一个类方法。该方法称为委托,并在其中调用define_method,它在单例类(例如add)上创建实例方法,因此它们是原始类(MyClass)上的类方法。

      如果我们从类 MyClass 语句中调用委托,我们将对实例方法 add 的调用委托给实例方法 newAdd(如果存在)。原因是在class MyClass创建的范围内,self是MyClass,通过在这里扩展模块Delegate,我们创建了一个类方法delegate(MyClass的单​​例类上的一个实例方法delegate)。此类方法调用 define_method,它在 MyClass 上创建实例方法(例如 add)。

      我们最终得到了两个方法,称为delegate,一个是MyClass的类方法,一个是MyClass的单​​例类的类方法。

      【讨论】:

      • Delegate 模块代码可能很聪明,但它真的不可读!好的代码很容易理解,所以这里需要做很多重构
      猜你喜欢
      • 1970-01-01
      • 2012-08-03
      • 2011-06-06
      • 2014-01-28
      • 2020-03-07
      • 2016-04-02
      • 2014-09-07
      • 2013-10-10
      • 1970-01-01
      相关资源
      最近更新 更多