映画予約システム
要件の確認
- オンライン映画予約システムを実装しようとしています。
- 映画は、映画に関する基本情報を表します。
- タイトル、上映時間、料金情報など
- 上映は、実際に観客が映画を鑑賞するイベントを表します。
- 上映日、時間、順番など
- 人々は映画を予約すると言いますが、実際には特定の上映される映画を予約すると言うべきです。
- 割引条件
- 料金の割引の可否
- 順番条件:上映の順番を使用して割引の可否を決定
- 期間条件:映画の上映開始時間を使用して割引の可否を決定
- 割引ポリシー
- 割引料金を決定
- 金額割引ポリシー:予約料金から一定の金額を割引
- 割合割引ポリシー:正規料金から一定の割合の料金を割引
- 映画ごとに1つの割引ポリシーを割り当てるか、まったく割り当てないこともできます。割引条件は、複数の割引条件を組み合わせることができます。
- 割引ポリシーが適用されているにもかかわらず割引条件を満たしていない場合、または割引ポリシーが適用されていない場合は、料金は割引されません。
オブジェクト指向プログラミングに向けて
協調、オブジェクト、クラス
- オブジェクト指向は、オブジェクトを指向することです。
- クラスを決定した後に、クラスにどのような属性とメソッドが必要なのかを考えるべきではありません。
- どのようなオブジェクトが、どのような状態と動作を持つのかを決定する必要があります。
- オブジェクトを独立した存在ではなく、協力する共同体の一員として見なす必要があります。
ドメインの構造に従うプログラム構造
- ドメインは、問題を解決するためにユーザーがプログラムを使用する分野を指します。
<span class="image-inline ck-widget" contenteditable="false"><img src="https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2Fe7d22a03-4a24-40e9-8068-bd03b9fd816b%2FUntitled.png?table=block&id=3f1931fc-8ef9-470a-8189-3727146638f6&spaceId=b453bd85-cb15-44b5-bf2e-580aeda8074e&width=2000&userId=80352c12-65a4-4562-9a36-2179ed0dfffb&cache=v2" alt="Untitled" style="aspect-ratio:2000/438;" width="2000" height="438"></span>
- 一般的に、クラスの名前は対応するドメイン概念の名前と同じか、少なくとも類似したものにする必要があります。
- クラス間の関係も、可能な限りドメイン概念間の関係と類似したものにすることで、プログラムの構造を理解し、予測しやすくする必要があります。
クラスの実装
- クラスは内部と外部に分けられ、優れたクラスを設計するには、どの部分を外部に公開し、どの部分を隠すかを決定する必要があります。
- オブジェクトの属性はprivateで隠し、内部の状態を変更するために必要なメソッドをpublicで公開します。
- クラスの内外を区別することで、オブジェクトの自律性を保証し、プログラマーに実装の自由を提供します。
自律的なオブジェクト
- オブジェクトは、状態と動作を持つ自律的なオブジェクトである必要があります。
- データと機能をオブジェクト内部にまとめてカプセル化することをカプセル化と言います。
- アクセス制御を通じて外部からの干渉を減らすことで、オブジェクトは自らの行動を決定できるようになります。
- インターフェースと実装の分離原則は、オブジェクト指向プログラミングにおいて従うべき主要な原則です。
- パブリックインターフェース:外部からアクセス可能な部分
- 実装:内部からのみアクセス可能な部分
プログラマーの自由
- プログラマーの役割は、クラス作成者とクライアントプログラマーに分けられます。
- クラス作成者は、新しいデータ型を追加します。
- クライアントプログラマーは、クラス作成者が追加したデータ型を使用します。
- 実装の隠蔽
- クラス作成者は、必要な部分のみをクライアントプログラマーに提供することで、内部実装を隠すことができます。
- クライアントプログラマーはインターフェースのみを知っていればよいため、知識の量を減らすことができます。
協力するオブジェクトの共同体
- お金を表す際に、単にLong型の変数を宣言するのではなく、Moneyのようにオブジェクトにラップする方が良いでしょう。オブジェクトを使用すると、意味の伝達がより明確になり、重複した演算処理を1箇所で実行できるからです。
- システムの特定の機能を実装するために、オブジェクト間で行われる相互作用を協調と呼びます。
協調に関する短い話
- オブジェクトが他のオブジェクトとやり取りできる唯一の方法は、メッセージを送受信することです。
- 受信したメッセージを処理するための独自のメソッドをメソッドと呼びます。
- メッセージとメソッドを区別することは重要であり、ここから多態性の概念が始まります。
割引料金の取得
割引料金計算のための協調開始
- Movieクラスには、割引ポリシーに関する詳細なロジックは含まれておらず、DiscountPolicyインターフェースに委譲しました。継承と多態性、抽象化を活用することが非常に重要です。
割引ポリシーと割引条件
<span class="image-inline ck-widget" contenteditable="false"><img src="https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2F7b9b27a5-0dac-4ba7-9552-05b7f8fbdecd%2FUntitled.png?table=block&id=3264f189-6e12-4a55-94d2-75b851320c7a&spaceId=b453bd85-cb15-44b5-bf2e-580aeda8074e&width=2000&userId=80352c12-65a4-4562-9a36-2179ed0dfffb&cache=v2" alt="Untitled" style="aspect-ratio:2000/535;" width="2000" height="535"></span>
継承と多態性
コンパイル時依存性と実行時依存性
- コードの依存性と実行時の依存性が異なる場合があります。
- 両者の依存性が異なるほど、コードを理解するのが難しくなりますが、柔軟性と拡張性のあるコードになります。
差異によるプログラミング
- 継承は、既存のクラスを基に新しいクラスを簡単かつ迅速に追加でき、親クラスの実装を再利用できます。
- 親クラスとは異なる部分のみを追加して新しいクラスを作成する方法を、差異によるプログラミングと呼びます。
継承とインターフェース
- 継承は、親クラスが提供するすべてのインターフェースを子クラスが継承することを可能にします。
- インターフェースは、オブジェクトが理解できるメッセージのリストを定義します。
- MovieはDiscountPolicyにcalculateDiscountAmountメッセージを送信しています。Movieの立場では、どのクラスのインスタンスが応答するかは関係なく、応答が成功すればよいのです。
- したがって、AmountDiscountPolicyとPercentDiscountPolicyはどちらもDiscountPolicyに代わってMovieと協調できます。
- このように、子クラスが親クラスに代わることをアップキャストと呼びます。子クラスが上位にある親クラスに自動的に型キャストされるように見えるためです。
多態性
- 多態性とは、同じメッセージを受信した場合に、オブジェクトの種類に応じて異なる応答ができる能力のことです。
- Movieは同じメッセージを送信しますが、実際にどのメソッドが実行されるかは、メッセージを受信するオブジェクトのクラスが何かによって異なります。
- 多態性は、プログラムのコンパイル時依存性と実行時依存性が異なる可能性があるという事実に基づいています。
- 多態性は、実行されるメソッドを実行時に決定するため、遅延バインディングまたは動的バインディングと呼ばれます。
インターフェースと多態性
- 実装を共有する必要はなく、純粋にインターフェースのみを共有したい場合は、抽象クラスではなくインターフェースを使用すればよいでしょう。
抽象化と柔軟性
抽象化の力
- 抽象化のメリット
- 抽象化の階層のみを分けて見ると、要件のポリシーを高レベルで記述できます。
- 設計がより柔軟になります。
柔軟な設計
- 抽象化は、設計が具体的な状況に結合されることを防ぐため、柔軟な設計を作成できます。
抽象クラスとインターフェースのトレードオフ
- 現在、NoneDiscountPolicyは実際にはなくてもDiscountPolicyのcalculateDiscountAmount()メソッドで割引条件がない場合は0を返すため、問題ありません。しかし、ユーザーの立場では、Movieの引数に割引ポリシーがないNoneポリシーを指定する必要があるため、追加しました。
- したがって、概念的にNoneDiscountPolicyクラスが分かりにくいので、以下のように修正すると理解しやすくなります。
- 上記のように、DiscountPolicyをインターフェースで抽象化し、その実装であるNoneDiscountPolicyと一般的な割引ポリシーを表すDefaultDiscountPolicyに分割することです。
<span class="image-inline ck-widget" contenteditable="false"><img src="https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2F712983a9-3937-4265-82fe-66a144c44a0f%2FUntitled.png?table=block&id=f5ca651c-d03c-4a6c-97e9-7443ee5649a0&spaceId=b453bd85-cb15-44b5-bf2e-580aeda8074e&width=2000&userId=80352c12-65a4-4562-9a36-2179ed0dfffb&cache=v2" alt="Untitled" style="aspect-ratio:2000/886;" width="2000" height="886"></span>
- NoneDiscountPolicyのみに対してインターフェースを追加することが、増加した複雑さに比べて効率が悪いと考えるかもしれません。
- 実装に関連するすべてのものが、トレードオフの対象になり得ることを認識して設計する必要があります。つまり、記述するすべてのコードには、妥当な理由が必要です。
コードの再利用
- 継承は、コードを再利用するために使用できます。
- しかし、コードの再利用のためには、継承よりも合成を使用する方が良いでしょう。
- 現在のコードでは、MovieがDiscountPolicyのコードを再利用する方法がまさに合成です。
- もし、Movieを上位クラスとし、AmountDiscountMovieとPercentDiscountMovieに分割するならば、それは継承を使ったと言えるでしょう。
継承
- 継承のデメリット
- カプセル化に違反する。
- 親クラスの内部構造をよく理解する必要がある。
- 親クラスを変更すると、子クラスも一緒に変更される可能性が高い。
- 設計を柔軟性に欠くものにする。
- 親クラスと子クラスの関係をコンパイル時に決定する。
- 実行時にオブジェクトの種類を変更することができない。
- しかし、合成は、使用するオブジェクト側でchageDiscountPolicy()のようなメソッドを通じて、実行時にインスタンスを交換することができます。
- カプセル化に違反する。
合成
- インターフェースに定義されたメッセージを通じてのみコードを再利用する方法を合成と言います。
- カプセル化を実装し、疎結合を維持できます。
- 多態性のためにインターフェースを再利用する場合は、継承と合成を組み合わせて使用する必要があります。
- 例えば、DiscountPolicyインターフェースは、下位実装を構築するために継承を使用する必要があります。
出典
- オブジェクト
コメント0