はじめに
本について
Amazon: https://www.amazon.co.jp/dp/4822284654
題材として選択したのは、「オブジェクト指向でなぜつくるのか-第2版-」という本です。13章で構成されており、今回は第2章について、まとめました。
この本を選んだきっかけ
私はプログラマーとして業務に携われるように、実践ベースで Ruby を学び、半年足らずで開発ができるようになりました。オブジェクト指向言語である Ruby を使ってプログラムをかけるようにはなったのですが、 オブジェクト指向言語の良さや特徴 まではしっかり学ばないままでいました。
以前に読んでいた本の中で「オブジェクト指向プログラミングを活用できていない」という文言を見て、 そもそもオブジェクト指向プログラミングの良さや特徴ってなんなんだろう と考えさせられました。
そんなときに出会ったのが、「オブジェクト指向でなぜつくるのか-第2版-」です。レビューによると、オブジェクト指向で使われている技術がどういったもので、なぜ必要なのかを、歴史的な背景を含めつつ解説している本とのことだったので、この本を選択しました。
ゴール
オブジェクト指向と現実世界が似て非なるものであると理解すること。
理由
ネットでよく見かける「オブジェクト指向を使うと、現実世界をそのままプログラムとして表現できる」という説明が混乱を招いているかもしれないため。
解説
この章では「オブジェクト指向を使うと、現実世界をそのままプログラムとして表現できる」というよくある説明は、混乱を招いてしまうかもしれないと主張しています。その根拠として、オブジェクト指向の世界と現実世界の違いを列挙しています。
以下が詳細となります。
よくある説明
クラス
オブジェクト指向の最も基本的な仕組みであるクラスとインスタンスは、数学の集合論における集合と要素の関係に相当します。つまり、クラスは「種類」を示し、インスタンスは「具体的なモノ」を意味します。
現実世界であれば以下のような例があげられます。
犬: ラン, モカ, ブブ...
アイドルグループ: KARA, TWICE, BIGBANG...
料理: キムチチゲ, サムゲタン, サムギョプサル...
では実際にコーディングしてみます。今回は犬(Dog)クラスを Ruby で実装します。
class Dog
def initialize(name)
@name = name
end
def cry
puts "ワン"
end
end
次に犬(Dog)クラスから2匹の犬を作って、鳴かせてみましょう。
ran = Dog.new("ラン")
moka = Dog.new("モカ")
# ランとモカに「鳴け!」と命令しましょう
ran.cry # => "ワン"
moka.cry # => "ワン"
「鳴け」とメッセージを送ると、2匹とも「ワン」と鳴いてくれましたね。
まさに現実世界で、犬に対して芸をさせているかのように感じます。
ポリモーフィズム
ポリモーフィズムとは、「命令する相手がどのクラスのインスタンスであるかを意識せずにメッセージを送れる仕組み」です。
この仕組みを現実世界になぞらえて例をあげるとすれば、「鳴け」という同じ命令でも、命令を受ける側によって、例えば犬であれば「ワン」、猫であるば「ニャー」、猿なら「ウキー」と応じるという状況でしょうか。
ではこれをコーディングしてみます。まずは犬・猫・猿に共通するクラスである、動物(Animal)クラスを定義します。
class Animal
def cry
end
end
次に具体的な動物として犬(Dog)・猫(Cat)・猿(Monkey)クラスを、動物(Animal)クラスを継承して定義します。
class Dog < Animal
def cry
puts "ワン"
end
end
class Cat < Animal
def cry
puts "ニャー"
end
end
class Monkey < Animal
def cry
puts "ウキー"
end
end
これでポリモーフィズムの準備は完了しました。
あとは命令する側のクラス、今回はトレーナー(Trainer)クラスを定義します。
class Trainer
def command_cry(animal)
animal.cry
end
end
では様々な動物を泣かせてみましょう。
trainer = Trainer.new
# 犬を鳴かせる
dog = Dog.new
trainer.command_cry(dog) # => "ワン"
# 猫を鳴かせる
cat = Cat.new
trainer.command_cry(cat) # => "ニャー"
# 猿を鳴かせる
monkey = Monkey.new
trainer.command_cry(monkey) # => "ウキー"
継承
継承とは、「似たクラスの共通点と相違点を整理する仕組み」です。クラスとインスタンスが集合と要素の関係であるのに対し、継承元クラスと継承先クラスは全体集合と部分集合という関係です。
オブジェクト指向では、全体集合をスーパークラス、部分集合をサブクラスと表現します。
現実世界でいえば、動物の分類が適切な例として挙げられます。動物は「哺乳類」「鳥類」「魚類」などに分けられます。動物という全体集合の中に、「哺乳類」や「鳥類」、「魚類」といった部分集合が含まれることになります。オブジェクト指向では、「動物」をスーパークラスといい、「哺乳類」や「鳥類」、「魚類」をサブクラスといいます。
では実際にコーディングしてみましょう。
# 動物クラス
class Animal
# 行動する(ロジックは省略)
def move
...
end
# 鳴く(ロジックは省略)
def cry
...
end
end
# 哺乳類クラス
class Mammal < Animal
# 出産する(ロジックは省略)
def bear
...
end
end
# 鳥類クラス
class Bird < Animal
# 飛ぶ(ロジックは省略)
def fly
...
end
end
つまり、共通する性質は動物(Animal)クラスに定義し、固有の性質は哺乳類(Mammal)クラスや鳥類(Bird)クラスに定義して、共通点と相違点を整理しているわけです。
説明と現実世界の異なる点
現実世界では人や動物はクラスから作られない
よくある説明でも挙げたように、オブジェクト指向の世界ではますクラスを定義してから、インスタンスを生成するという仕組みです。
犬の例だと、オブジェクト指向の世界では犬(Dog)クラスから「ラン」や「モカ」といった名前の犬が生成されます。しかし、現実世界はお父さん犬とお母さん犬が発情期に仲良くなった結果としてお母さん犬が妊娠することで、生まれてくるわけです。
現実世界では、決してクラスから人や動物が作られる訳ではありません。
クラスの位置付けが同じではない
オブジェクト指向では、クラスはインスタンスを生成するための仕組みであり、インスタンスが帰属するクラスもひとつだけです。
しかし、現実世界は先に具体的なモノ(インスタンス)があり、見る側の対場によって分類(クラス)します。さらに見方によって、分類のされ方は様々です。
例えば「ある男性」であっても、会社内だと「会社員」、営業先では「セールスマン」、家庭では「父親」という分類ができます。つまり、現実世界ではインスタンス(具体的なモノ)が、複数のクラス(分類)に帰属する場合があります。
現実世界では命令に応じないときもなる
オブジェクト指向の世界では、命令された指示には決して逆らいません。メッセージパッシングをすれば、必ず用意された処理が実行されます。
しかし、現実世界では命令されても、命令された側が自分で判断して、指示に従うかどうかを決めます。つまり、命令に逆らう場合もあるわけです。
結論
オブジェクト指向は現実世界とは似て非なるものである。
クラスやポリモーフィズム、継承はソフトウェアの保守性や再利用性を向上させるための仕組みであり、オブジェクト指向の説明でよく使われる現実世界になぞらえた説明は、あくまで比喩だと割り切るべきだということです。
所感
今までの開発を振り返ると、クラスやポリモーフィズム、継承の機能にだけ着目してしまい、誤った判断で技術を使ってしまうことが多かったなと感じました。
例えば、全体集合と部分集合の関係であるという概念的な理解をせず、コードの重複を消すためだけに継承を使おうとしたことがあります。
つまり、機能ベースの理解にとどめるのではなく、概念的な理解も絶対に必要だなと実感しました。また、その説明として比喩が使われている場合は、あくまで比喩だと割り切りことも重要だと感じまいした。