有如下定义的类 Bird, Crow, Duck 。
require 'digest/md5'
class Bird
attr_accessor :name
private
def digest(food)
d = Digest::MD5.new
d << food
d << name
d.hexdigest
end
end
class Crow < Bird
def eat(food)
if food.kind_of?(String) && food.match(/^edible:/)
digest(food.reverse)
else
food
end
end
end
class Duck < Bird
def eat(food)
if food.kind_of?(String) && food.match(/^edible:/)
digest(food.upcase)
else
food
end
end
end
Crow 与 Duck 的 eat 方法基本相同。
为了尽量减少两个 eat 方法间的冗长性,请修改源代码。
--
黒田努
答案与说明的显示・隐藏
答案和说明
类 crow 和类 duck 的 eat 方法,除去在 digest 参数指定的这部分外完全相同。
如果共同部分围绕在不同部分的外侧的话,如何取出共同的部分,正是问题所在。
笔名是 MTG 的先生如下答道。
require 'digest/md5'
class Bird
attr_accessor :name
private
def digest(food)
d = Digest::MD5.new
d << food
d << name
d.hexdigest
end
def edible?(food)
food.kind_of?(String) && food.match(/^edible:/)
end
end
class Crow < Bird
def eat(food)
if edible?(food)
digest(food.reverse)
else
food
end
end
end
class Duck < Bird
def eat(food)
if edible?(food)
digest(food.upcase)
else
food
end
end
end
因为母类中定义了 edible? 方法,所以简化了 eat 方法。
下面是我的示范解答。
require 'digest/md5'
class Bird
attr_accessor :name
private
def digest(food)
d = Digest::MD5.new
d << food
d << name
d.hexdigest
end
def eat(food)
if food.kind_of?(String) && food.match(/^edible:/)
digest(yield(food))
else
food
end
end
end
class Crow < Bird
def eat(food)
super(food) {|f| f.reverse }
end
end
class Duck < Bird
def eat(food)
super(food) {|f| f.upcase }
end
end
为了抽取出共同部分利用了块。
Crow 类的 eat 方法内用中括号扩着的部分
{|f| f.reverse }
就是块
块是添加到方法调用的“代码团”。
对Ruby语言的初学者来说块是很容易掌握的概念,但是还是要注意块的要点不在 “功能”,而是以“代码团这样的“物”的形式存在。
这个“代码团”和参数一起被传递到方法。
super(food) 中,变量 food 的内容被作为参数传递到和母类同名的方法 eat 中。和它一起被传递的还有 {|f| f.reverse } 块。
可以通过 yield 方法在收到块的方法内调用块。
总之,通过 yield(food) 代码 {|f| f.reverse } 被执行。
作为参数传递到块的 food 的值,在块中存储在被管状字符(|)包起的变量 f 里。
这次的问题可能有些勉强的假设。
现实的开发中,MTG的解答方法可能比较好理解。
但是,经常可以看到2个内容大体相同,但是中间又有一点差异的方法。
如果记住今天的内容的话,遇到这种情况时一定会派上用场。