『Head First オブジェクト指向設計』第5章 前半

第5章 良い設計=柔軟なソフトウェア

この章で学ぶこと

ソフトウェアの変更が困難になっていれば顧客からの変更要求に対応することが難しくなる。既存のソフトウェアの設計を改善することを通して、柔軟な設計を学ぶ。

ギターだけでなくマンドリンも扱えるようにする

  • 変更前のクラス図

diagram-782802414211719472

  • 変更後のクラス図

diagram-218270032049871976

  1. 抽象クラスは、振る舞いを定義し、サブクラスがそのふるまいを実装する。
  2. 同じ振る舞いが2箇所以上の場所に存在するのであれば、その振る舞いを1つのクラス内に抽出し、再利用する。
    Instrumentクラス・InstrumentSpecクラス

クラス図の詳細

  • クラス名の斜線は抽象クラスを表す
  • 白い三角形は汎化を表す ClassA ◁-- ClassB
    Guitarなどのクラスが、Instrumentなどの汎用的なクラスから振る舞いを拡張および継承することを示す
  • 白い菱形は集約(aggregation)を表す ClassA ◇-- ClassB
    集約は、あるものが(部分的に)別のものから構成されることを意味する。
    Instrument は部分的に InstrumentSpec から構成される。

     論理的に「全体と部分」の意味を持つものです。例えば、会社クラスと社員クラスの関係として会社は社員によって構成されるという概念から集約として表すことができます。ただ、このようにして使う集約は非常に曖昧(あいまい)で、集約にすべきか、関連にすべきか、悩むこともあります。なぜなら、実世界の会社と社員という概念はそれぞれ自立して存在しているという側面もあるからです。ここでは、これを「論理的な全体-部分構造」とします。
    【改訂版】初歩のUML:第4回 少しだけ高度なモデリング技術(その1)関連クラスと集約、コンポジション

  • 黒い菱形はコンポジションを表す ClassA ◆-- ClassB

    コンポジションは、集約の一種です。2つのクラスの依存関係は、両オブジェクトのライフサイクル(生成と消滅)がほぼ一致する場合に使います。コンポジションについても、集約かコンポジションのどちらを使うかは曖昧(あいまい)です。「両オブジェクトのライフサイクルが同じ」「物理的に1つのものを分けた」ということを強調したいときに使ってください。
    【改訂版】初歩のUML:第4回 少しだけ高度なモデリング技術(その1)関連クラスと集約、コンポジション

現在の設計の問題点

  • 新しい楽器を追加することが難しい。
  • Inventoryクラス
    Instrumentのサブクラスが追加されるたびにコードを変更しなければいけない
  • Instrumentクラス
    楽器を追加するたびに新しいサブクラスがどんどん増えていく
  • InstrumentSpecクラス
    異なるプロパティを持つ楽器を追加する際にコードを変更しなければいけない
    コードが重複しやすい

すべてが密に結合し、InstrumentSpecはInstrumentの一部(集約)の関係にある。

オブジェクト指向の3つの原則

1. インターフェース

diagram-8264521749727867540

実装ではなく、インターフェースに対してコードを作成するようにすると、ソフトウェアの拡張が容易になる。
インターフェースに対してコードを作成すれば、インターフェースを実装したあらゆるサブクラスでそのコードを扱えるようになる。
固有の実装はサブクラスに記述する

2. カプセル化

カプセル化はクラスを不要な変更から守るのに役立つ。
変更が生じやすそうな振る舞いがあれば、振る舞いを必ず分離(= 変動しやすい部分をカプセル化)する。

diagram-3394751198258345913

paint()は色々な描き方があるので、変更が生じやすい。

diagram-3138915151318724466

Painterクラスから変動するものをカプセル化し分離する。

3. クラスごとに変更の理由を1つだけにすること

良い設計のアプリケーションは変更が容易だが、そうではないアプリケーションはあっさり崩壊する。
クラスが変更される原因を減らし変更を最小限に留めること。

diagram-9095652297658069928

変更される理由が複数存在するクラスは、1つのクラスで多くのことを行っている可能性が高い。

diagram-6927912838860376318

機能を複数のクラスに分割し、各クラスが一つのことだけを行うようにする。