新手问题 Ruby 語言的蠢問題

origin10 · 2016年01月07日 · 最后由 origin10 回复于 2016年01月08日 · 2466 次阅读

哈囉各位好~ 我又來問蠢問題了,剛剛學到 Ruby 裡 Class 與 Mudle 之間的區別

module 裡面的 function 定義本身不能被自己呼叫 (除非使用 self.function) 而他的 function 定義卻可以被 class 工廠所引用 (模仿?)

Class 是工廠可以生產任何東西, 而 Module 則只是樣品或設計圖 (不能直接拿來用)

於是我想到這樣的譬喻 今天在海綿寶寶的世界裡有一間名叫海之霸的餐廳 他們有"ChumBucket(海之霸)主打料理"的這個 class, 而皮老闆自己做的 "ChumBucket(海之霸)主打商品"是一種叫"海霸棒"的食物

irb>

class ChumBucket海之霸主打料理
  def 吃起來
    puts "會讓顧客上吐下瀉,必須送醫洗胃兩次。"
  end
end
>海霸棒 = ChumBucket(海之霸)主打料理.new
>海霸棒.吃起來
會讓顧客上吐下瀉,必須送醫洗胃兩次。

而就在有一天皮老闆終於拿到了他夢寐以求叫 "KrabbyPattySecretFormula(美味蟹堡秘方)"的 module

module KrabbyPattySecretFormula美味蟹堡秘方
  def 製作秘方
    puts "放入", bun: "小圓麵包", patty: "肉餅", cheese: "起司", ketchup: "番茄醬", mayonaise: "美乃滋", mustard: "黃芥末", pickles: "醃菜", lettuce: "萵苣", onions: "洋蔥"
    puts "最後再放上", top_bun: "麵包"
  end
  def 吃起來
    puts "會讓顧客回味無窮,這是因為小神廚─海綿寶寶以極快樂的心情,恰到好處的材料,最好的技巧,還有想要給顧客知道的用心,全部放在蟹堡哩,所以才會這麼美味吧!"
  end
end

於是皮老闆就把秘方佔為己有,並把秘方輸入凱倫的電腦記憶體裡

class ChumBucket海之霸的主打料理
  def 吃起來
    puts "會讓顧客上吐下瀉,必須送醫洗胃兩次。"
  end
  include KrabbyPattySecretFormula美味蟹堡秘方
end

然後製作”海之堡“來大賣~

>海之堡 = ChumBucket(海之霸)的主打料理.new
>海之堡.製作秘方
放入
{:bun=>"小圓麵包", :patty=>"肉餅", :cheese=>"起司", :ketchup=>"番茄醬", :mayonaise=>"美乃滋", :mustard=>"黃芥末", :pickles=>"醃菜", :lettuce=>"萵苣", :onions=>"洋蔥"}
最後再放上
{:top_bun=>"麵包"}
=> nil

現在我有一個問題就是 我該怎麼讓 海之堡。吃起來 可以是 "會讓顧客回味無窮..."?

請各位大大指點~

Ruby method dispatch 是一个不太好解释的东西,当执行 吃起来 的时候,会首先在 ChumBucket(海之霸)的主打料理 中找有没有这个 method,然后继续到 ancestors 中去查找。

> 海之堡.class.ancestors
=> [ChumBucket海之霸的主打料理,
 KrabbyPattySecretFormula美味蟹堡秘方,
 Object,
 Kernel,
 BasicObject]

然后可以查找执行的 method 会在那一层被执行

> 海之堡.method(:吃起來)
=> #<Method: ChumBucket(海之霸)的主打料理#吃起來>
> 海之堡.method(:製作秘方)
=> #<Method: ChumBucket(海之霸)的主打料理(KrabbyPattySecretFormula(美味蟹堡秘方))#製作秘方>

所以 吃起来 会在 ChumBucket(海之霸)主打料理 先被找到,于是就不会去到上层去执行 會讓顧客回味無窮... 了。

你可以试试这样

class ChumBucket海之霸的主打料理
  def 吃起來
    super
  end
  include KrabbyPattySecretFormula美味蟹堡秘方
end
海之堡 = ChumBucket海之霸的主打料理.new
> 海之堡.吃起來
會讓顧客回味無窮這是因為小神廚海綿寶寶以極快樂的心情恰到好處的材料最好的技巧還有想要給顧客知道的用心全部放在蟹堡哩所以才會這麼美味吧!

extra: https://blog.jcoglan.com/2013/05/08/how-ruby-method-dispatch-works/

Ruby 中的方法是按照继承链从左向右查找的,通过 Module#ancestors 方法可以看到该继承链。

ChumBucket海之霸的主打料理.ancestors
=> [ChumBucket海之霸的主打料理, KrabbyPattySecretFormula美味蟹堡秘方, Object, Kernel, BasicObject]

当此处调用 海之堡.吃起來 时,调用了 Class ChumBucket(海之霸)的主打料理 中定义的方法,而不是 Module KrabbyPattySecretFormula(美味蟹堡秘方) 中的方法。所以删除 Class ChumBucket(海之霸)的主打料理 中定义的 吃起來 方法即可:

class ChumBucket海之霸的主打料理
   remove_method :'吃起來'
end

海之堡.吃起來
=> 會讓顧客回味無窮這是因為小神廚海綿寶寶以極快樂的心情恰到好處的材料最好的技巧還有想要給顧客知道的用心全部放在蟹堡哩所以才會這麼美味吧!

关于楼主的问题 楼上两位说得很清楚了 不过我突发奇想,当我们用 new 实例化 海之堡 的时候,继承链中被覆盖的方法是否还有可能被通过某种方式再调用到 当然,讲道理的说,实例本身应该是不会包含已经被覆盖的内容的

4 楼 已删除

哈哈~謝謝 @lgn21st @reyesyang @xworm 各位大大對小弟蠢問題的回覆, 一下子收到太多有趣的新資訊~

需要消化一下,先在這裡謝過各位大大~~

#3 楼 @xworm 最简单的方法是 alias 一个方法别名。

Class 和 Module 本质上来说,区别不大,在 kernel 中以同一个 C 结构体来维护。

struct RClass {
    struct RBasic basic;
    VALUE super;
    rb_classext_t *ptr;
    struct method_table_wrapper *m_tbl_wrapper;
};

大致上来说这么一个结构

为了解决 Ruby 不存在多重继承的问题,Matz 给 Ruby 引入了 Mixin,具体用 Module 来实现。然而实现的方法很简约,就是直接把 Module 插入到 class 的祖先链里。

所以直接把 Module 理解为 Class,include 就是多重继承也无妨。比如有三个 Class,人、猫、狗都继承自动物这个 Class,现在想写一些所有宠物共用的代码。由于猫、狗是从动物继承过来的,所以这两个类不能再继承宠物这个类,也不可能在这两个类里独立封装这些方法。这时就写一个 Module 叫做宠物,让猫和狗这两个 Class 把这个 Module Mix 进去就可以了。

当然实际的使用中,即使是不复用的代码也可以使用 Module。把一个复杂问题拆解后,分成不同的 Module,然后一起 Mixin 到一个 Class,形成一个 Huge Class,这也是经常使用的技巧。

PS:图来自《Ruby Under a Microscope》,坛里有大神正在翻译,期待赶紧出版。

#5 楼 @origin10 实在是你的问题太有趣了,所以忍不住不回答。

这里应该用prepend

class ChumBucket海之霸的主打料理
  def 吃起來
    puts "會讓顧客上吐下瀉,必須送醫洗胃兩次。"
  end
  prepend KrabbyPattySecretFormula美味蟹堡秘方
end
放入
{:bun=>"小圓麵包", :patty=>"肉餅", :cheese=>"起司", :ketchup=>"番茄醬", :mayonaise=>"美乃滋", :mustard=>"黃芥末", :pickles=>"醃菜", :lettuce=>"萵苣", :onions=>"洋蔥"}
最後再放上
{:top_bun=>"麵包"}
會讓顧客回味無窮,這是因為小神廚─海綿寶寶以極快樂的心情,恰到好處的材料,最好的技巧,還有想要給顧客知道的用心,全部放在蟹堡哩,所以才會這麼美味吧!

美味蟹黄堡的配方你是怎么拿到的😡😡😡😡😡😡

prepend 就是把 美味蟹黄堡的配方 放到海霸棒配方的前面,这样皮老闆就不会拿错了

#6 楼 @adamshen 这个可以,alias 类似于把两个指针指向同一个方法体,其中一个指针的位置被重写更改了,另一个不受影响,大概是这样的吧? 那么有木有可能就是在它被覆盖的情况下,从实例里面找到这个已经被覆盖的方法体?(我觉是不行了)

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