次のように定義されたクラス 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
メソッドはほとんど同じである。
2つの 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)
で、親クラスの同名メソッド eat
に変数 food
の中身が引数として渡されるのですが、それと一緒に {|f| f.reverse }
というブロックも渡されるのです。
ブロックを受け取ったメソッド内では、yield
メソッドでこのブロックを呼び出すことができます。
つまり、yield(food)
によって {|f| f.reverse }
というコードが実行されるのです。
ブロックに対して引数として渡した food
の値は、ブロックの中ではパイプ文字(|
)で囲まれた変数 f
に格納されます。
今回の問題は、やや無理のある設定だったかもしれません。
現実の開発では、MTG さんの解答の方が分かりやすくてよいかもしれませんん。
しかし、2 つのメソッドの中身がほとんど同じなのに、真ん中辺りが少しだけ違っている、という状況はよくあるものです。
その場合のひとつの対処法として今回のテクニックを覚えておくと、きっと役に立つはずです。