Rails 元编程里的一句话

ane · 2014年06月19日 · 最后由 leafree 回复于 2016年01月19日 · 5983 次阅读
“Can an object call a private method on another object of the same class?” or “How can you define class meth- ods by importing a module?” 

中文意思是"一个对象可以调用同属一个类的其他对象的私有方法吗?或者"可以通过导入一个模块来创建类方法么?""

我猜想作者既然说这话,那么应该两句话都是肯定的才对吧。

第二句可以用 extend 这样的钩子证明。(之前我写的是 include,不对,include 的结果是实例方法) rails 中随处可见,举例

[98] pry(main)> module Mou
[98] pry(main)*   def my_method
[98] pry(main)*     'my'
[98] pry(main)*   end
[98] pry(main)* end
=> :my_method
[100] pry(main)> class C
[100] pry(main)*   include Mou
[100] pry(main)* end
=> C
[101] pry(main)> class D < C
[101] pry(main)* end
=> nil
[104] pry(main)> D.new.my_method
=> "my"
[105] pry(main)> C.new.my_method
=> "my"
[106] pry(main)> class E
[106] pry(main)*   extend Mou
[106] pry(main)* end
=> E
[108] pry(main)> E.my_method
=> "my"
[109] pry(main)>

但是,第一句话,有点迷惑,我的理解是一个类A,的两个实例对象a1,a2,他们可以互相调用对方的私有方法。对于一个学了老么长 java 的同学,理解起来真的很困难。即便我知道 A 也算是个对象,但是,我也不明白为什么a1要调用,而且居然能调用a2的私有方法,难道这个私有方法不是共存在类中吗? 这似乎也算多线程了吧。

追加第二句一个对象的方法也是类的实例方法。一个类方法似乎就应该是class的实例方法 那么如果我采用 extend moudel 的方式话,module 中的方法似乎就是类方法了,但却不是 class 的实例方法.

这个追问,我自己回答吧,这种情况,应该算作打开了一个类,然后追加方法。因此这些追加的方法应该是存在类中,而非 class 中。就好比你打开一个普通对象追加方法是一样的。(好像叫影子吧,不是很肯定,查查书再确定)

tip one:

在Java中,如果我们定义一个方法为私有方法,即用private关键字修饰,则此方法对子类是不可见的,子类不会知道超类中存在这样一个私有方法,自然也无法调用,如果我们试图访问,会收到错误信息。但是在Ruby中,这种情况改变了。如果我们在superclass中定义一个方法为private的私有方法,则其subclass依然可以继承此私有方法,在subclass内部可以访问此私有方法!这是和Java语言的机制截然不同的。

tip two:

我后来在《元编程》一书中发现这样一段文字
 private methods come from two rules working together: first, you need an explicit receiver to call a method on an object that is not yourself, and second, private methods can be called only with an implicit receive.”
私有方法由两条规则一起控制: 第一条,如果调用方法的接收者不是自己,这必须明确指定接收者。第二条:私有方法只能被隐含接收者调用。

Can object x call a private method on object y if the two objects share the same class? The answer is no
如果对象X和对象Y都是同一个类的对象,则X能调用Y的私有方法吗?答案是“不能”(结合规则得出的)

tip three:


obj.send(:private)这种方式,不知道到底算不算

第一句应该是不行的吧... C++ 才行吧

#1 楼 @blacktulip 我也不知道啊,我也不清楚,作者把两句话合在一起,用个反问句表达出来,是为了肯定了?还是疑问了?我是理解成肯定了。

send 不就可以调用对象的私有方法吗? 为啥要限定同一个类. 等 meta programming 高手.

[1] pry(main)> class Klass
[1] pry(main)*   private
[1] pry(main)*   def test
[1] pry(main)*     puts "private test"
[1] pry(main)*   end  
[1] pry(main)* end  
=> :test
[2] pry(main)> a = Klass.new
=> #<Klass:0x00000101206d98>
[3] pry(main)> a.test
NoMethodError: private method `test' called for #<Klass:0x00000101206d98>
from (pry):8:in `__pry__'
[4] pry(main)> a.send(:test)
private test
=> nil

#3 楼 @debugger 这是 a 在 call 自己的私有方法,楼主说的是,比方说 b = Klass.new 然后用 a 去 call b 里面的私有方法,我其实也没怎么理解这是什么意思...

第一句话必然是不行的

第一句话是对的,a,b 都是 A 的实例,那么在 a 的方法内部是可以调用 b 的私有方法的。这个写个 Java 程序验一下就知道了。

Java 的方法,都是随着类加载的,都在栈,整个系统都只有一份。简单来说,每个类都有一个 method lookup table,用来查询某个方法的入口地址在哪。当 a,b 不是同一个类时,a 只能查询到 b 的公开方法列表,所以,没法 binding 到 b 的私有方法。如果 a,b 同一个类,那么他们其实共用一个 lookup table,所以,就能找到私有方法入口。

这种调用私有方法跟多线程没有任何关系,对象的实例都是方法和变量分离的,变量可以简单理解为 C 里面的 Struct,通过一个指针引用。每次方法调用就是正常的进栈出栈。另外,java 虚拟机规范里面有介绍,当调用某个实例方法时,虚拟机会默认的把当前对象作为第一个参数压入栈,即如果调用 a.p_m(b),那么实际上是传了两个参数给方法 p_m,第一个参数是 a,也就是 p_m 方法内部的 this 引用,第二个参数是 b。所以,对于这样的代码。

class A(){
  private String name;
  public A(String name){
    this.name = name;
  }

  public void m(A b){
    p_m(b);
  }

  private void p_m(A b){
   System.out.println("i am "+name);
    b.p_m(a);  
  }
}

new A("a").m(new A("b"));

当调用 a.m 方法时,这是第一层栈;当调用 a.p_m 时,就是第二层的栈,这个栈内部的 this 是 a;当调用 b.p_m 时,就是第三层的栈,这个栈内部的 this 是 b。简单说,这就有点像递归,只是不断的压栈,然后让 CP 指回到方法入口,跟多线程没有任何关系。

#6 楼 @nickcen 不懂 Java ... 同样的结论在 Ruby 也是成立的么?

@blacktulip 自己写一个 ruby 程序测试吧。

#8 楼 @nickcen @blacktulip 我不认为 Ruby 成立

class A; end
a = A.new
b = A.new
def a.a_method; end
b.send(:a_method) # => NoMethodError

@neverlandxy_naix 你的代码不对啊,是在 a 的内部调用 b 的私有方法。直接这样调用私有方法,在哪个语言里面都不能够的。

# encoding:utf-8

class A
  def initialize(name)
   @name = name
  end

  def p(b)
    m_p(b)
  end

  private
  def m_p(b)
    puts "inside #{@name}"
    b.m_p(self)
  end
end

A.new("a").p(A.new("b"))

#10 楼 @nickcen 你的代码都跑不通啊, a 的内部也不能调用 b 的私有方法, 你这样定义的私有方法, 是所有 class A 的实例的私有方法啊

#11 楼 @neverlandxy_naix #10 楼 @nickcen #7 楼 @blacktulip

无论怎样,都跑不通啊 ,java 的类和 ruby 的类,是两码事

#4 楼 @blacktulip 了解了. #10 楼 @nickcen 我这报错? ruby 2.1.1p76 (2014-02-24 revision 45161) [x86_64-darwin12.0]

2.1.1 :016 > 
2.1.1 :017 >   A.new("a").p(A.new("b"))
inside a
NoMethodError: private method `m_p' called for #<A:0x0000010190a6f8 @name="b">
    from (irb):13:in `m_p'
    from (irb):7:in `p'
    from (irb):17
    from /Users/bob/.rvm/rubies/ruby-2.1.1/bin/irb:11:in `<main>'

提示在 a 里面调用了 b 的私有方法,好像不行呢?

#10 楼 @nickcen @ane 我无法理解怎么在 a 的里面调用 b 的私有方法, 刚才写的代码是有误, 我写了个新的 这个 b_method 方法, 就是只属于 b 的私有方法, 定义在 b 的 eigenclass 中, 而在 class A 里面定义的私有方法, 是这个 class A 的所有实例的私有方法, 即如果在 class A 中定义私有方法, 那这个私有方法就是 实例 a 和 实例 b 共有的私有方法

class A; end
a = A.new
b = A.new

class << b
  private
  def b_method; end
end
p b.private_methods.grep(/b_method/) # => [:b_method]
a.send(:b_method) # => NoMethodError

#14 楼 @neverlandxy_naix make sense. 这句话语义上怪怪的,不知道怎样定义那个私有方法才算是满足前提条件,这样 a,b 的 class 都是 A,算是同一个类的其他对象了,但是无法 call.

#6 楼 @nickcen


public class Main {
    private String name;
    public Main(String name){
        this.name = name;
    }
    public void m (Main b){
        p_m(b);
    }
    private void p_m (Main b){
        System.out.println("i am "+name);
        b.p_m(a);
    }
    public static void main(String[] args) {
    // write your code here
      Main  a = new Main("a");
      Main  b = new Main("b");
      a.p_m(b);
    }
}

这么写出来的代码也不对啊.编译都通不过的,哈哈

Error:(13, 15) java: cannot find symbol
  symbol:   variable a
  location: class com.company.Main

ps :我写的时候一会忘记分号,一会忘记写参数类型。

class A
   def call_pri(obj)
      obj.send 'pri'
   end
   private
   def pri;end
end

a = A.new
b = A.new
a.call_pri b

我是这样理解的

#6 楼 @nickcen 第一没有什么 class A(){}.第二都编译不过去。呵呵

#14 楼 @neverlandxy_naix #13 楼 @debugger #1 楼 @blacktulip #6 楼 @nickcen #17 楼 @5swords

各位:这个算吗

[91] pry(main)> class A
[91] pry(main)*   def initialize(name)
[91] pry(main)*     @name = name
[91] pry(main)*   end
[91] pry(main)*   def call_pri(obj)
[91] pry(main)*     obj.send(:pri)
[91] pry(main)*   end
[91] pry(main)*   private
[91] pry(main)*   def pri
[91] pry(main)*     @name
[91] pry(main)*   end
[91] pry(main)* end
=> :pri
[92] pry(main)> a = A.new('a')
=> #<A:0x000001014b17a0 @name="a">
[93] pry(main)> b = A.new('b')
=> #<A:0x000001015e6e40 @name="b">
[94] pry(main)> a.call_pri b
=> "b"

@ane, 这个不算。public method 里面可以随意使用 private methods, 毕竟 private methods 的主要目的之一就是为了 public methods 服务的。你可以直接def call_pri; pri; end,都没有必要 send。

我觉得你没有必要为了这一句话纠结,等看到了具体代码部分再详细讨论比较有价值。

#20 楼 @billy 也不算纠结啊,抛出来,看看大家的看法评论,才是重点

#19 楼 @ane 哈哈, 算吧, 不过这个 pri 也是 a 自己的私有方法.

a.private_methods.grep(/pri/)

在 class A 里面定义的私有方法, 是这个 class A 的所有实例的私有方法, 即如果在 class A 中定义私有方法, 那这个私有方法就是 实例 a 和 实例 b 共有的私有方法, 如果想定义只属于 b 的私有方法, 那就要进入 b 的元类来定义.

#20 楼 @billy 而且如果用def call_pri; pri; end,那就真是只是调用当前 self 的 private method 了

#19 楼 @ane 我觉得满足条件了吧.

  1. 同一个类的不同对象.
  2. 是私有方法.

前提没有说这个私有方法是不是 2 个都有或者都没有.这个私有方法放在 A(Class 的实例) 这个类实例里面的,方法是共享的,#22 楼 @neverlandxy_naix 如果进入 b 的元类定义就产生了特征类 (鬼魂类,单例类),这样岂不是算不上同一个类的不同对象了? 不知道这样理解对不对.

太囧了.

26 楼 已删除

#24 楼 @debugger 进入了 b 的元类定义了只属于 b 的方法, 但是 a 和 b 还是 class A 的对象啊

#26 楼 @neverlandxy_naix 也许你是对的,我看元编程里面有这么一句.

Ruby 中每个对象都有其自己的匿名类,一个类能拥有方法,但是只能对该对象本身其作用:当我们对一个具体的对象添加方法时,Ruby 会插入一个新的匿名类于父类之间,来容纳这个新建立的方法。

#26 楼 @neverlandxy_naix #24 楼 @debugger 我觉得作者的原意应该就是在 a 的方法中调用 b.send(:private)。而不应该是所谓的 a.b_private 或者它的变形 a.send(b_private) 等等

#28 楼 @ane 嘿嘿, send & public_send.

@ane

ruby 这种调用是不能这样调用的。

java 是可以的。之前代码只是示意。可编译和 run 的代码是这样的。

package test;

public class Main {
    public static void main(String[] args){
        new A("a").m1(new A("b"));
    }
}

-------------------
package test;

public class A {
    private String name;

    public A(String name) {
        this.name = name;
    }

    public void m1(A b){
        m2(b);
    }

    private void m2(A b) {
        System.out.println("inside "+name);
        if (b != null) {
            b.m2(null);
        }
    }
}

#30 楼 @nickcen 我原来一直思考为什么

public void m1(A b){
        b.m1(null);
    }

不对。后来看到错误提示java.lang.NullPointerException,于是我似乎想起什么了

楼主不用太费心研究这个啦,要是程序竟然需要去调另一个对象的私有方法,那还是直接重构好了。

#32 楼 @blacktulip 没,昨天想重看一下《元编程》,所以遇到疑惑的就贴出来,看看别人这么看的。学而思嘛。其实也没太费心思。至少在这过程中我发现,原来 include module 的本质是给类添加了一个父类,等等一系列被以前一眼带过的知识,到写 rails 的时候,发现很多问题都是盲目的写

#32 楼 @blacktulip 就好比,读薄,读厚的过程了

第一个<元编程>书里明确说了不能,参考私有规则。为什么作者这么问就一定是肯定的意思呢?

#35 楼 @eva 请仔细看完我的帖子后批判。断章取义,要不得

哪里有批判的意思。。。。。

#37 楼 @eva 随便用的一个词,不带感情色彩,请不要在意这些细节。呵呵

#38 楼 @ane 那也请忽略我的评论吧~ 我只是好奇了下,为什么这个问句需要一个肯定的回答,原谅我语文没学好吧,呵呵

关注技术讨论就好了,言语之争没什么意思 #39 楼 @eva #38 楼 @ane

#39 楼 @eva 你可以看看 19 楼的方法,算不算数,其实也没那么重要。我表示,本帖,重点是评论

#41 楼 @ane 个人意见:不算,我更认同 #14 楼的理解

c# 里可以用反射玩私有的东东

#43 楼 @huobazi 对于 c#,我的态度就是 “山寨 java”

#44 楼 @ane 不举个牌子站 MS 门口抗议么 O(∩_∩) O~

#45 楼 @huobazi 不了啊。我记得我上完第一节 C++ 课后,很蒙,就去图书馆看看书。结果发现,有 C,C++,C#.我当时以为这都是一门语言,吓尿了,于是我就学了只有一个名字的 java

#46 楼 @ane java 还有 javascript 呢

#47 楼 @blacktulip 对,当时也有记得有它,只是 2 个比 3 个少点。而且,没有一本 ruby 的书

@ane 回答: 追加第二句一个对象的方法也是类的实例方法。一个类方法似乎就应该是 class 的实例方法 那么如果我采用 extend moudel 的方式话,module 中的方法似乎就是类方法了,但却不是 class 的实例方法.

这里应该是 singleton methods 吧。感觉 singleton methods 是介于 class methods 和 instance methods 之间的东东,而且不能 instatilize.

我画了一张图,剩下的就可以推导出来啦。

需要 登录 后方可回复, 如果你还没有账号请 注册新账号