オブジェクト指向
データと操作をオブジェクトとしてまとめ、現実の概念に近づけて設計する考え方
オブジェクト指向とは
オブジェクト指向は、データ (状態) とそれを操作する手続き (振る舞い) を「オブジェクト」という単位にまとめて、プログラムを組み立てる考え方だ。利用者・注文・商品といった対象をオブジェクトとして表し、それらのやり取りとしてシステムを記述する。
「現実世界をそのまま写す」という説明は入口としては分かりやすいが、実際の設計でまとめる基準は現実の見た目ではない。一緒に変わるデータと処理を 1 つの単位に閉じ、変更が及ぶ範囲を狭めることが目的だ。だから同じ「注文」でも、扱う業務が違えば持たせる状態も操作も変わる。
3 つの柱
| 概念 | 意味 |
|---|---|
| カプセル化 | データを隠し、決められた操作経由でのみ触れる |
| 継承 | 既存の性質を引き継いで新しい種類を作る |
| ポリモーフィズム | 同じ呼び出しで対象ごとに異なる振る舞い |
カプセル化が効くのは、公開した操作の一覧が呼び出し側との約束になるからだ。内部でデータを配列で持つか連想配列で持つかを後から変えても、公開した操作の見え方が同じなら呼び出し側は直さずに済む。逆に内部のデータを直接読み書きさせると、その表現を変えた瞬間に触っている箇所すべてが修正対象になる。
ポリモーフィズムの効き方も同じ向きだ。呼び出し側が相手の具体的な種類を知らずに同じ呼び出しで済むなら、種類が 1 つ増えても呼び出し側の条件分岐を増やさずに追加できる。種類ごとの違いを「呼び出し側の分岐」から「オブジェクト側の実装」へ移し替える仕組みだと捉えると、どこに効くのかが見えやすい。
起源と系譜
クラスと継承を備えた最初の言語とされるのが Simula 67 で、Ole-Johan Dahl と Kristen Nygaard がノルウェー計算センターで開発した。前身の Simula I については 1966 年の論文「SIMULA: an ALGOL-based simulation language」があり、ALGOL 60 を土台にシミュレーションを記述するための言語として出発したことが分かる。オブジェクトという単位は、抽象的な設計論から降りてきたのではなく、船や港のように多数のものが並行して動く対象を書き表す必要から生まれた。
「オブジェクト指向」という言い方は、Smalltalk の研究を率いた Alan Kay が 1967 年ごろから使っていたものだ。Kay はこの語の父と呼ばれることについて、自分の考えは一般に理解されているオブジェクト指向とは異なると述べており、クラスや継承よりもオブジェクト間のメッセージ送信こそが中心概念だと繰り返し語っている。経緯は Kay 自身の論文「The Early History of Smalltalk」(1993 年) に記されている。Smalltalk 自体は Xerox PARC で開発され、外部に公開されたのは 1980 年の Smalltalk-80 からだった。
カプセル化の理論的な背骨は、オブジェクト指向より先にある。David Parnas が 1972 年の論文「On the criteria to be used in decomposing systems into modules」で示した情報隠蔽の考え方で、モジュールを分ける基準は処理の順番ではなく「変わりやすい決定をどれだけ内側へ隠せるか」だと論じた。オブジェクト指向のカプセル化は、この基準を言語の機能として持ち込んだものと読める。
継承が難しくなる理由
継承には「実装を再利用する手段」と「型の階層を宣言する行為」という 2 つの役割が同居している。この 2 つが噛み合わないときに設計が壊れる。
判断の目安になるのがリスコフの置換原則で、Barbara Liskov が 1987 年の講演録「Data abstraction and hierarchy」で示した考え方だ。派生した型は、基底の型を期待している場所へそのまま置いても期待を破らないこと。これが崩れると、呼び出し側に「相手が実際はどの種類か」を調べる分岐が生え始め、ポリモーフィズムで消したはずの分岐が戻ってくる。
そこで実務では、共通の処理を親クラスへ引き上げる代わりに、部品として保持して呼び出す組み合わせ (コンポジション) を先に検討する。この優先順位は書籍「Design Patterns」(1994 年) で広く知られたもので、継承を禁じる主張ではない。継承と組み合わせは併用するものであり、組み合わせだけでは書きにくい問題もある。目安としては、性質を引き継ぎたい (是が非でも同じ型として扱いたい) のか、機能を借りたいだけなのかを分けて考えるとよい。
常に最善の道具ではない
オブジェクト指向が合うのは、状態を抱えた部品が多数あり、種類が増える方向へ育っていく領域だ。入力を受け取って変換して出力するだけの処理に無理にクラスを立てると、実質 1 つの関数を包むだけの層が増える。データ変換の連なりは関数を並べた方が読みやすい場面も多い。
言語による違いも大きい。Java のようにクラスを基本単位とする言語では自然に従うことになるが、Python や JavaScript では関数を値として扱う書き方と混ぜて使うのが普通だ。パラダイムの純度を守ることが目的ではなく、変更に強く読み解ける構造を作ることが目的だという 設計 上の判断に立ち返りたい。
どこから手を付けるか
既存のコードで試すなら、クラスを 1 つ選び、外から直接読み書きされているデータがないかを確認するのが早い。見つかったら、それを触っている処理をクラス側の操作として引き取れるか検討する。次に継承の階層を 1 段だけ組み合わせへ置き換えてみて、呼び出し側の記述量と、変更したときに触るファイルの数がどう動くかを比べる。判断の勘所は、この差分の観察で身に付く。
この記事は役に立ちましたか?