導入部
ロバート・L・グラスは、理論よりも実践が先であると主張しました。特にソフトウェア開発においては、実践が理論に先行している代表的な分野であり、開発者は具体的なコードに触れて手を汚す際に最も多くのものを得ます。そのため、理論と概念は一旦脇に置き、簡単なプログラムを1つ見ていきたいと思います。
チケット販売アプリケーションの実装
- 小劇場の宣伝のために、小さなイベントを企画しようとしています。
- イベント内容:抽選で選ばれた観客に、公演を無料で観覧できる招待状を送付
- イベントに当選した観客と、当選しなかった観客を区別する必要があります。
- イベント当選者:招待状をチケットと交換
- イベント落選者:チケットを現金で購入
何が問題なのか
ロバート・マーチンは、ソフトウェアモジュール(サイズに関係なく、クラス、パッケージ、ライブラリなど、プログラムを構成する任意の要素)が持つべき3つの機能について説明しています。
- 実行中に正しく動作する。
- 変更のために存在する。
- 簡単な作業だけで変更が可能でなければならない。
- コードを読む人とコミュニケーションをとることである。
- 開発者が簡単に読み、理解できるものでなければならない。
先のチケット販売アプリケーションは、正しく実行するという最初の制約は満たしていますが、変更容易性とコミュニケーションという目的は満たしていません。
予想を裏切るコード
コミュニケーションという目的を満たしていない理由を見てみましょう。
- Theaterクラスのenter()メソッドが実行すること
- 小劇場は観客のバッグを開けて、中に招待状が入っているか確認します。
- バッグの中に招待状が入っていれば、販売員にチケット売り場に保管されているチケットを観客のバッグの中に入れるように指示します。
- バッグの中に招待状が入っていなければ、観客のバッグの中からチケット代分の現金を取り出してチケットを購入し、チケットをバッグに入れます。
- 観客の立場からすると、小劇場という第三者が勝手にバッグを漁って、お金を取り、チケットを入れているのを見守らなければならない。
- 販売員の立場からすると、小劇場という第三者が許可もなくチケット売り場のチケットと現金の値を操作しているのを見守らなければならない。
- 理解しやすいコードとは、動作が私たちの予想を大きく外れないコードを意味しますが、上記の劇場は私たちの予想を裏切ります。
- 観客は自分でバッグの中からお金を取り出して販売員に支払い、チケットを受け取るべきである。
- 販売員は自分でチケット売り場にあるチケットを直接取り出して観客に渡し、観客からお金を受け取ってチケット売り場に保管するべきである。
- また、enter()メソッドを理解するためには、さまざまな詳細な内容をすべて覚えておく必要があります。
- AudienceがBagを持っている。
- Bagの中には現金とチケットがある。
- TiketSelletがTicketOfficeでチケットを販売し、TicketOfficeの中にはお金とチケットが保管されている。
変更に脆弱なコード
enter()メソッドは2つの条件を前提としています。
- 観客は現金と招待状を保管するために、常にバッグを持ち歩いている。
- 販売員はチケット売り場でのみチケットを販売する。
では、以下の状況はどうでしょうか?
- 観客はバッグを持っていない場合がある。
- 観客は現金ではなくクレジットカードを使用できる。
- 販売員がチケット売り場以外でチケットを販売できる。
例えば、最初の要件を満たすには、AudienceのBagオブジェクトを削除し、それに関連するTheaterクラスのenter()メソッドを変更する必要があります。これは、Theaterクラスが観客はバッグを持っており、販売員はチケット売り場でのみチケットを販売するという、あまりにも詳細な事実に依存しているためです。これらの詳細な事実のいずれかが変更されると、該当するクラスと依存しているクラス(例:Theater)をすべて修正する必要があります。
これは、オブジェクト間の依存性に関連する問題であり、依存性は変更に対する影響を示唆しています。しかし、オブジェクト指向設計は、相互に依存し協力するオブジェクトのコミュニティを構築することを目的としているため、無闇矢鱈に依存性をなくすのではなく、アプリケーションの機能を実装するために必要な最小限の依存性のみを維持し、不要な依存性は削除する必要があります。
オブジェクト間の依存性が過剰な場合を結合度が高いと言いますが、2つのオブジェクト間の結合度が高いほど、一緒に変更される可能性も高くなります。したがって、設計の目標は、オブジェクト間の結合度を下げて、変更が容易な設計にすることです。
設計の改善
観客がバッグを持っているという事実と、販売員がチケット売り場でチケットを販売しているという事実を、Theaterが知る必要はありません。Theaterは単に観客が小劇場に入場することを望んでいます。したがって、観客が自分でバッグの中の現金と招待状を処理し、販売員が自分でチケット売り場のチケットと販売料金を扱うように、自律的な存在にする必要があります。
自律性を高めよう
密接に関連する作業のみを実行し、関連性のない作業は他のオブジェクトに委譲するオブジェクトを、凝集度が高いと言います。自分のデータを自分で処理する自律的なオブジェクトを作成すると、結合度を下げて凝集度を高めることができます。
手続き型とオブジェクト指向
- 手続き型
- Theaterのenter()メソッドはプロセスであり、Audience、TicketSeller、Bag、TicketOfficeはデータです。
- プロセスとデータを別々のモジュールに配置する方式を、手続き型プログラミングと呼びます。
- 私たちの直感に反するコードが多いです。(例:観客は自分でお金と招待状を管理する。)
- データの変更による影響を狭めるのが難しい。
- 責任を中央集権的に管理する。(Theaterがすべて管理する)
- オブジェクト指向
- データとプロセスが同じモジュール内に配置される方式を、オブジェクト指向プログラミングと呼びます。
- 私たちの直感に沿ったコードを書くことができます。
- データの変更による影響をカプセル化によって効果的に狭めることができます。
- 各オブジェクトが自身を自分で責任を持つ。
さらに改善できる
- AudienceクラスのBagクラスはまだAudienceクラスに引っ張られる受動的な存在であるため、Bagクラスを自律的なオブジェクトにする必要があります。
- TicketSellerクラスのTicketOfficeもTicketSellerによって勝手に管理されています。TicketOfficeを自律的なオブジェクトにする必要があります。
- しかし、変更後、TicketOfficeはAudienceと追加の結合度が発生しました。
- このように、設計はトレードオフをよく検討する必要があります。この場合、Audienceとの結合度を下げるために、TicketOfficeをある程度受動的なオブジェクトにするという合意に至る可能性もあります。
そうだ、嘘だ!
- 現実では、受動的な存在であっても、オブジェクト指向の世界に入ると、すべてが能動的で自律的な存在に変わります。
- 擬人化を使って、受動的なオブジェクトをまるで笑い、騒ぎ、怒る対象のように考えるのが良いでしょう。
オブジェクト指向設計
設計がなぜ必要なのか
- 設計とは、コードを配置することです。
- 良い設計とは、今日要求される機能を完全に実行しながら、明日の変更をスムーズに受け入れることができることです。
オブジェクト指向設計
- 変更可能なコードとは、理解しやすいコードです。
- オブジェクト指向パラダイムは、私たちが世界を見る方法と同じようにコードを書くことができるようにします。
- オブジェクトは、自分のデータを自分で責任を持つ自律的な存在です。
- 優れたオブジェクト指向設計とは、協力するオブジェクト間の依存性を適切に管理する設計です。
出典
- オブジェクト
コメント0