2021-03-12

『オブジェクト指向入門 第2版』の14章を読んだ

第14章 継承入門

12章と13章は飛ばす.

継承はオブジェクト指向の中核となる技術である.継承によって,あるクラスが他のクラスの拡張,特殊化,組み合わせであることを表現でき,再利用性と拡張性を高めることができる.

継承の例として,多角形クラスを考える.多角形クラスには,いくつかの操作(面積や座標の計算)が実装されている.ここで,長方形クラスが必要になったとする.多角形と長方形には重複する操作がいくつか存在しており,長方形は多角形の特殊なケースだと考えられる.この場合,多角形と長方形で別々のクラスを作成するのではなく,多角形クラスの後継者として長方形クラスを作成することで,長方形クラスが多角形クラスを特殊化したものであることを表現できる.

継承の用語

  • クラスCの子孫
    C自身を含み,Cを直接,間接的に継承するクラス
  • クラスCの真の子孫
    自身を除く子孫
  • クラスCの祖先
    CAの子孫である場合のA
  • クラスCの真の祖先
    CAの真の子孫である場合のA

継承を図で表すとき,

Polygon(多角形クラス) <- Rectangle(長方形クラス)

のように,後継者から親に矢印が向く.後継者はどのクラスが親なのがわかるのに対し,親はどのクラスが後継者なのかはわからないため,このような向きになっている.

多相性

多相性によって,実行時に,エンティティに型が異なるオブジェクトをアタッチできる.ただし,エンティティの型とオブジェクトの型が適合していなければならない.適合とは,アタッチ先xとアタッチ元yがあるとき,yxの子孫であることを示す.

p: Polygon
r: Rectangle
s: String

p := s // 誤り.StringはPolygonの子孫ではない.
r := p // 誤り.PolygonはRectangleの子孫ではない.

p := r // 正しい.RectangleはPolygonの子孫である.
r := r // 正しい.RectangleはRectangleの子孫である.

動的束縛

動的束縛は,オブジェクトの動的形式によって,行う操作を変更する.例えば,以下のようなprintメソッドが実装されているとする.

p: Polygon
r: Rectangle

p.print() // "Polygon"と出力
r.print() // "Rectangle"と出力

以下のような関数fには,引数にPolygonはもちろん,Rectangleも渡すことができる.fPolygon型のインスタンスを受け取り,printを呼び出す.ここで呼び出されるのはprintだが,渡された引数の型によって異なる動作をする.

function f(p: Polygon) {
	p.print()
}

f(p) // "Polygon"と出力
f(r) // "Rectangle"と出力

暫定クラス

実装されていない特性(暫定特性)を持つクラスを暫定クラスという.暫定クラスのインスタンスは作れないため,暫定特性を呼び出すことはできない.

暫定クラスを継承するクラスが有効クラスになるためには,親の暫定特性を実装しなければならない.親の暫定特性は,子孫がそれを具体的な実装に置き換えることを強制する(そうでなければインスタンスが作成できない).

継承の利点

モジュールの観点

継承を用いることで,祖先には変更を加えずに,祖先を拡張した子孫を作成することができる(開放/閉鎖の原則を満たせる).

型の観点

動的束縛によって,拡張性と再利用性を高めることができる.

継承は「is」の関係を表す.例えば,「すべての長方形は多角形である」といった感じ.間違った「is」の関係には,例えば,SAN_FRANCISCOクラスをCITYクラスからの継承にしてしまうこと(SAN_FRANCISCO is a CITY)が挙げられる.より正確に言えば,継承は,「x is an A」ではなく,「Every B is an A」が成り立たなくてはならない.長方形の例では,「すべての長方形」が多角形に含まれているので継承関係は正しい.一方,サンフランシスコはひとつしかないので,「Every SAN_FRANCISCO」は誤りである.SAN_FRANCISCOCITYクラスのインスタンスとして考えるのが正しい.