Edited at

『Effective Ruby』を読んだのでまとめました - 第2章 クラス、オブジェクト、モジュール - 前編

前回の第1章まとめに引き続き、今回は第2章をまとめようと思います。

内容が興味深いものばかりで読んでいて面白いのですが、項目ごとに内容が詰め込まれており、しかも翻訳記事なので少々読み辛く感じるところがあります。

なんとか自分のRuby力を上げたいので、うまく内容を整理してまとめ記事にしていきたい!

地道な努力が必要。


項目6 Rubyが継承階層をどのように組み立てるかを頭に入れよう

この項目ではRubyの継承階層を探るためにメソッドを探す仕組みを掘り下げています。


Rubyの継承階層について

例として以下のようにAnimalクラスとそれを継承したCatクラスがあるとします。

class Animal

def name
end
end

class Cat < Animal
end

Cat.new.nameでCatのインスタンスからnameを呼び出す場合、以下の継承階層を上ってAnimalクラスでnameを発見し、呼び出されます。

マッチするメソッドが見つからなければ、出発点から再びスタートし、今度はmethod_missingメソッドを探します。


特異クラスについて

継承階層について更に深く掘り下げるためにmoduleをincludeした場合について見ていきます。

新しくAnimalNamesというmoduleを作り、そこにnameメソッドを移動します。

module AnimalNames

def name
end
end

class Animal
include AnimalNames
end

class Cat < Animal
end

AnimalクラスはAnimalNamesをincludeした時、何をするかというと、目には見えないAnimalNamesクラスを継承階層に差し込みます。差し込んだAnimalNamesにnameメソッドをロードして使用可能にします。

この目に見えないクラスを特殊クラスといい、特殊クラスのメソッドを特殊メソッド呼びます。

moduleをincludeした場合の継承階層は以下のようになります。


目に見えないクラスというのは、一体、どういう事を指すのか?

この答えを得るためにAnimalのsuperclassを呼び出してみます。

p Animal.superclass

#実行結果
Object

特殊クラスのAnimalNamesを飛び越してObjectクラスになってしまいます。

実際にはAnimalNamesクラスが差し込まれているのですが、存在しないかのような振る舞いをしてしまうのです。


項目7 superのふるまいが一通りではない事に注意しよう

class AnimalBase

def method(x, y)
end
end

class Cat < Base
def method(x, y)
super(1, 2) # 1,2を渡して呼び出し
super(x, y) # x,yを渡して呼び出し
super x, y # x,yを渡して呼び出し
super # x,yを渡して呼び出し
super() # 引数なしで呼び出し
end
end

ここで混同しそうなのは、何もつけないsuperは、元のメソッドに渡された引数を渡して呼び出すのに対し、()付きのsuper()は引数なしで呼び出すところ。注意して使いましょう。


項目8 サブクラスを初期化するときにはsuperクラスを呼び出そう

Rubyは親クラスのinitialize内で定義されたインスタンス変数の初期化を自動では行わず、その判断をプログラマーに委ねています。


class Animal
attr_accessor(:name)

def initialize
@name = 'animal'
end
end

class Cat < Animal
attr_accessor(:age)

def initialize
@age = 5
end
end

Animalクラスを継承したCatのインスタンスでは、親クラスの@nameはnilになってしまいます。

p Cat.new.name

# 結果
nil

そんな時プログラマーは、子クラスのinitialize内でsuper(引数)で親クラスのinitializeを呼び出して、インスタンス変数を初期化する必要があります。


class Animal
attr_accessor(:name)

def initialize(name)
@name = name
end
end

class Cat < Animal
attr_accessor(:age)

def initialize(name, age)
super(name)
@age = age
end
end

p Cat.new('name', 5).name

# 結果
"name"


項目9 Rubyの最悪に紛らわしい構文に注意しよう

Rubyでは、代入式の形式で使えるセッターメソッドが定義できるよう、=付きのメソッド名が認められています。

class Cat

attr_accessor(:age)

def initialize
@age = 5
end

def age
@age
end

def age=(age)
@age = age
end
end

=はメソッド名の一部なのですが、呼び出す場合に限り、=とメソッド名や()の間に空白を入れる事を許しています。

p Cat.new.age = (3)

# 結果
3

=とメソッド名や()の間に空白を入れる事を許すという部分ですが、以下のようにinitialize内で呼び出した場合はどうでしょうか。

def initialize

age = 5
end

セッターメソッドと変数への代入は混同しやすいのですが、この書き方だとセッターメソッドは呼ばれずにageというローカル変数に代入してしまう事になります。セッターメソッドを使用したい場合は、selfを書いてメソッドであることを明確にしましょう。

def initialize

self.age = 5
end


項目10 構造化データの表現にはHashではなくStructを使おう

クラスにするまでもない構造化データを扱う場合は、HashではなくStructを使った方が良い場合が多いです。

例えば以下のような飲み会の費用と参加人数が入っているHahがあるとします。

nomikai = { price: 20_000, number_of_people: 5 }

運用しているとメソッドで加工した値をデータとして持ちたくなる場合があります。

以下のような、費用を人数で割った割り勘の金額を出すメソッドを追加したとします。

def warikan

price / number_of_people
end

クラスが小さいうちはまだ良いのですが、クラスが大きくなるとメソッドを追加する際にどのキーが使えるのか、どんな値を入れるのかわかりにくくなる場合があります。

以下のようにStructを使用し、情報をまとめておいた方が扱いやすいでしょう。

nomikai = Struct.new(:price, :number_of_people) do

def warikan
price / number_of_people
end
end

Structはクラスジェネレーターのような役割をしており、Struct.newを入れた変数nomikaiはクラスのように使用出来るようになります。キーの追加もStruct.newの引数に追加していけば問題ありませし、メソッドも追加する事ができます。

p nomikai.new(20_000, 5).warikan

# 結果
4000

また、Structを使っておくと、属性名をタイプミスした場合、NoMethodErrorを返します。

ハッシュで無効なキーにアクセスしようとした場合nilを返すだけです。


今回のまとめ

2章は長いので、前編、後編に分けます。

細かい話が多いのですが、後編もしっかりまとめてアウトプットしたいです。