今日、オブジェクト指向について1時間ほど語りました。整理するため自分用に書いたメモを公開します。大まかな構成はメモどおりに話しましたが、メモに書いていないこともたくさん話していますし、書いていても話さなかったこともあります。
前提として自分自身のオブジェクト指向へのスタンスを書いておきます。
自分のプログラマとしてのキャリアとオブジェクト指向の隆盛の重なりを考えると客観的に見て自分はオブジェクト指向世代のプログラマなんだと思います。一方で、世間で過剰にもてはやされる技術には反発してきました。オブジェクト指向も例外ではありません。オブジェクト指向を否定はしませんが、金科玉条のように扱う人の前では、オブジェクト指向なんて技法のひとつに過ぎないと、冷たく突き放してきました。
ただここ数年、かつてに比べてオブジェクト指向の威光は下がっている気がします。関数型プログラミング支持者から、オブジェクト指向なんて副作用をベースにした技法だと揶揄されることすらあります。過去10年でプログラミング技法がそれほど進歩したとも思っていませんし、人間の思考能力が大きく進化したとも思えないので、オブジェクト指向の(相対的な)影響力の低下は、価値の低下ではなく、単なるかつてのブームへの反動だと思います。ブームの反動だとしても、オブジェクト指向は時代遅れで無価値だと主張する人は(寡聞にして)ほとんど知りません。要は、オブジェクト指向は過剰に持ち上げられるでもなく、過剰に蔑まれるでもなく、冷静に価値が評価される時代になったのだと思います。これでぼくも無理することなく、普通にオブジェクト指向を話せる時代になりました。
改めてぼく自身のオブジェクト指向への評価は、全能の技法ではないけどれど優れた技法のひとつ、です。率直に言って過剰な思い入れはありません。
大部分のプログラミング技法の価値はコードの依存性の整理をいかに補助するかだと思っています。コードの依存性を整理するひとつの有効な策が、コードの中に不変の部分を作り込んでいくことです。マクロに見ればインターフェースです。ミクロな技法を挙げれば不変オブジェクトや副作用の排除などがあります。コードの中に不変の部分を作っていくことで依存の連鎖を断ち切って、変更の影響を管理可能な範囲に押し込めます。
不変の部分を作っていくにはコードの中の変わりやすい部分とそうでない部分を見極める目も必要です。多くのプログラマはある規模までは無意識にできています。何かを設定ファイルに追い出したり、外部パラメータに追い出したりするのは自然にできていると思います。そうやって無意識に追い出しているのは変わりやすい部分だと認識しているからです。コードの規模が大きくなっても変わりやすい部分を外に追い出す感覚は重要です。
不変を作り込む時にも注意があります。本質的に変わりやすい部分を扱うコードに早々と不変を持ち込むと融通の効かないコードになるからです。早すぎる最適化というプログラミングのアンチパターンがありますが、同じような言い方をすると、早すぎる不変の作り込みと呼んでもいいかもしれません。早すぎる不変の作り込みをすると、回避するためのラッパーコードや無駄なフラグを持ち込む羽目になります。形式的にはインターフェースを使った固いコードで美しいのに、現実的には足かせにしかならないコードができあがります。
だらだら書いていたら前置きのほうが長くなりました。以下がオリジナルメモです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
オブジェクト指向プログラミングのオブジェクトとは メモリ上の実体 => データとしての見立て => インスタンス化できるデータ データと手続きを一体化 => 関数ポインタのメンバ(手段) => this参照。レシーバオブジェクト => カプセル化 => アクセス制御(手段) ここまで、モジュール化/情報隠蔽/状態機械の素直な発展(OO概念の20%ぐらい(感覚値)) クラス => 型による分類 => 階層管理(手段) オブジェクトは自分自身の振る舞い(型)を知っている => オブジェクトからクラスへのポインタ(手段) => 実例 Shape Rectangle Circle => 仮想テーブル(手段) => 仮想テーブル経由でメソッドを呼ぶメリットは? => 呼ぶ側のコードを変更せずに、呼ばれる側を変更できる => ベースクラスを変更せずに、子クラスを追加できる => どちらも依存性の軽減(OOの本質) => 変数の型(静的なアドレス解決)ではなくオブジェクトの型で、呼ばれるメソッドが決まる => 実行時の型チェックによる分岐処理(=>ポリモーフィズムへ発展) => fyi, C++では静的アドレス解決の手段もある => 変数の型の意味は? => 基本型変数の型は変数のメモリ領域の値の意味(サイズやビット列の意味)だが、 => 参照型変数の型は極論すると人間のための修飾子(本当の型情報を持つのは参照先オブジェクトだから) => 一部の純粋OO主義者が動的型言語を推す根拠 => cf. 純粋主義: tell, don't ask (メッセージパッシング) => cf. 現実派: クエリとコマンドの分離 => 静的型言語を支持する理論 => インターフェースを明示する修飾子(参照先オブジェクトの振る舞いの明示化) => 修飾子(public, private, final, etc.)はなくても書けるから不要か? => 制約(契約)を課すことでコードを堅牢にできる cf. 純粋OOではクラスもオブジェクト => newもメソッド => クラスもnewで生成 継承 実装の継承 => 共通処理を共有 => モジュール化の素直な発展 => 子クラスを変更しても親クラスの変更不要(親は子を知らない。知っていても知らないふりして書く) => fyi, 継承図の矢印の方向(共有のhasの方向。子は親を知っている。逆は知らない) => 委譲(delegation)でも共通処理の共有は可能(has-aの関係) => 一部の急進派は実装の継承を否定して委譲を支持 => テンプレートメソッドパターン(継承) vs. ストラテジパターン(委譲) => テンプレートメソッドパターンは麻薬(決まると頭が良くなった気がする) => メソッドのオーバーライド => 具象メソッドのオーバーライドは怪我の元(濫用された歴史)。@Override 型(振る舞い)の継承 => 呼ぶ側から見て同じに見えること => Liskov則(is-aの関係) => Javaのinterface(cf. C++の抽象基底クラス) => ポリモーフィズムとは? => ひとつのコードで異なる型を扱える => 実行時の型チェックによる分岐処理の素直な発展 => cf. 広義にはジェネリック型もポリモーフィズム(静的な型チェックによる分岐処理) => 呼ぶ側と呼ばれる側の依存性の軽減(呼ぶ側のコードの改変不要) => 呼ぶ側は実装詳細を知らないふり(知っていても知らないふりして書く) => インターフェースとしての契約 補足: <a href="http://www.amazon.co.jp/exec/obidos/ASIN/4774139904/arielnetworks-22/">「パーフェクトJava」</a>拾い読み p80 変数と型の規則 p95 言語仕様をシンプルにした結果、クラスを多義的に活用(p100)。クラスをオブジェクトのように使う弊害(p123、p129) p95 用語の整理 p99 オブジェクトのライフサイクル管理(ファクトリ、DI)。コンストラクタ(p117) p143 アクセス制御 p145 ネストしたクラス まとめ 本質は「依存性の整理」 (副作用はゼロにできても依存性はゼロにできない) オブジェクト指向は依存性を整理するための一技法 <a href="http://dev.ariel-networks.com/articles/workshop/perfect-java/">次の一歩</a> |
最近のコメント