新手问题 Ruby 語言的蠢問題

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

哈囉各位好~ 我又來問蠢問題了,剛剛學到 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 类似于把两个指针指向同一个方法体,其中一个指针的位置被重写更改了,另一个不受影响,大概是这样的吧? 那么有木有可能就是在它被覆盖的情况下,从实例里面找到这个已经被覆盖的方法体?(我觉是不行了)

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