よもやま話β版

よもやま話を書きます。内容はぺらぺら。自由に書く。

PugからShibainuはうまれない(※継承の話)

※初学者さん向けです。
継承がよくわからない、という話を時々聞くので書いてみようと思った。Rubyです。

例えばイヌのパグを表現してるClassがあるとする。

class Pug
  def bark
    "ワンワン"
  end

  def tail
    "巻き尾"
  end
end

さらに次にシバイヌが必要になった。すでに書いたパグと、大体特徴が一致しているので、継承を試したくなるかもしれない。

class Shibainu < Pug
end

shibainu = Shibainu.new
puts shibainu.bark #=> "ワンワン"
puts shibainu.tail #=> "巻き尾" 

だが、ここで踏み留まってみてほしい。継承は親子関係とも表現される。パグから継承してシバイヌを作るというのは、親子関係というには難しい。具体的な例として、例えばパグの耳の特徴を新しく定義したとする。

class Pug
  def bark
    "ワンワン"
  end

  def tail
    "巻き尾"
  end

  def ears
    "垂れ耳"
  end
end

このとき、パグを継承したシバイヌは、親が持っているメソッドを使えるようになる。すると、メソッドを呼び出した時に決定的な問題が表層に浮き出てくることになる。

class Shibainu < Pug
end

shibainu = Shibainu.new
puts shibainu.ears #=> "垂れ耳"

シバの耳は一般的には立ち耳と言われているので、shibainu.ears の戻り値は適さない。これを手っ取り早く是正するならば、Shibainuクラスの中に ears メソッドをオーバーライドすればOKだ。しかしそれでは根本の解決にはなっていない。追加で新しいクラスをPugから継承して作ろうとすると、常に「誤った結果を返すメソッドも継承される」問題もついて回るので、バグの要因になりうる。

PugとShibainuの機能をうまく取りまとめたいときは、二つの共通点としてDogというクラスを定義して、そこから継承でPugとShibainuを作るのがよい。

class Dog
  def bark 
    "ワンワン"
  end
end

class Pug < Dog
  def ears
    "垂れ耳"
  end
end

class Shibainu < Dog
  def ears
    "立ち耳"
  end
end


dog1 = Pug.new
puts dog1.bark #=> "ワンワン"
puts dog2.ears #=> "垂れ耳"

dog2 = Shibainu.new
puts dog2.bark #=> "ワンワン"
puts dog2.ears #=> "立ち耳"

継承を扱う時は、扱っているクラスの関係に注目して考えていくと吉。


以下、余談。

Dogの継承先へ、常に tail ears のメソッドを持たせることを約束させたい場合は、次のようにすると便利。

class Dog
  def bark 
    "ワンワン"
  end

  def ears
    NotImplementedError
  end
end


class Corgi < Dog
end

corgi = Corgi.new
corgi.tail #=> NotImplementedError