使いやすくて、変化に強いコンポーネント保守性・拡張性に優れたシステムを作る(6)

» 2006年09月29日 12時00分 公開
[野村佳弘,日立ソフトウェアエンジニアリング]

前回「コンポーネント化でクラスをすっきり整理」は、パッケージとサブシステムによるコンポーネント化についてお話ししました。コンポーネントとは、どのように考えて作り上げていくのでしょうか。単純に似たようなクラスをまとめるだけでは、使いやすいコンポーネントにはならないでしょう。今回は使いやすく、保守しやすいコンポーネントを作るにはどのような考え方で設計するのかについてお話ししていきます。

コンポーネントとは

 最初に、コンポーネント(部品)とはどのようなものか考えてみましょう。

 自作パソコンを作ることを考えてみます。パソコンは電源ユニット、CPU、マザーボード、ビデオカード、メモリなどの部品から構成されています。製作者は、どのようなパソコンを作りたいかを考え、目的に合った部品を選択します。次に、それらの部品のインターフェイスを調べ、お互いのインターフェイスが合っているかを調べます。そして、実際に部品を購入しパソコンを組み立てます。

 このように、コンポーネントは明確な機能とインターフェイスが定義されています。目的に合った必要な機能が備わっていること、インターフェイスが、分かりやすくシンプルであることが必要です。また、部品の機能が分からなければなりませんが、部品の内部がどのように作られているかを知る必要はありません。そのため電子工学の専門家でなくても、必要最低限の知識でパソコンを簡単に組み立てることができます。

 そして、もう1つ考えなければならないことがあります。それはコンポーネントの独立性です。コンポーネント内の変更がほかのコンポーネントに影響しないように、コンポーネント間の依存を極力少なくする必要があります。マザーボード上のCPUを変更したら電源ユニットを変更するようでは困ります。

 機能をうまくまとめてコンポーネント化しないと、ほかに依存するような機能がコンポーネントの中に存在してしまい、不要なインターフェイスが増え、知らなくてもよい機能を知らないと設計できなくなってしまいます。

 このようにコンポーネント化に際しては、次のようなことを考える必要があります。

  1. 機能を整理し目的に合った最小完備な機能を持たせる
  2. シンプルで必要最低限の使いやすいインターフェイスを考える
  3. コンポーネント間の依存を少なくする。

 今回は、(3)のコンポーネント間の依存について、コンポーネント間で依存するクラスの整理の仕方という観点で、(1)(2)の内容を考えながらお話しします。

コンポーネント間の関係を定義する

 分析の段階では、主に関連や汎化を使ってモデルを分析していきます。分析が進み設計の段階に入ったときには、分析で明らかになったクラスの構造をどのようにしたら実現できるか考えていきます。その過程でコンポーネント化を考えコンポーネント間の関係の見直しを行います。

(1)関連と依存

 関連は、強い意味的な依存関係を持っていると考えます。例えば取引と取引明細の関係は強い依存関係を持っています。取引は1回の取引日や取引先などの情報を持ち、取引明細は商品コードや数量などの情報を持ちます。

 関連は、クラスの属性(インスタンス変数)として関連先のオブジェクトの参照を保持します。上記のように意味的な依存関係が強いので、クラスの複数の操作で参照先のオブジェクトを利用すると考えられます。

 このように、関連は構造的な関係を持つことが多く、クラス間の依存関係は強いものになります。関連のあるクラスを異なるコンポーネントに分けることは避けなければなりません。

 それに対し、依存は弱い意味的な関係を持っていると考えます。顧客と取引は、意味的には弱い関係です。顧客と取引は違う概念と考えられます。

 依存は依存先のオブジェクトの参照を操作中のローカル変数として持ちます。操作を実行するときに、関係するオブジェクトを操作のパラメータとして受け取るか、操作内部でオブジェクトを生成することによりローカル変数としてオブジェクトを参照します。関連先のオブジェクトを操作の内部に保持するので、操作の実行が終わればオブジェクトの参照はなくなります。

 このように依存は一時的な関係と考えることができます。このように設計において関連と依存は、

  1. 関連は構造的な関係であり、意味的に強い依存関係を持っている
  2. 依存はメッセージ(操作)を介した一時的な関係であり、意味的には弱い依存関係を持っている

と考えることができます。

(2)分析モデルから設計モデルへ

 オブジェクト指向分析では、機能要件に基づき普遍的な構造と固有な構造を分析し、実現したいシステムがどのような構造でできているかを明らかにしていきます。オブジェクト指向設計では、明らかにされた構造を、非機能要件(Javaなどの実装技術、性能、セキュリティ、分散性、拡張性・保守性など)に基づきどのように実装していくかを設計します。

 このときに、分析モデルについてクラス間の関係を見直し、関連や汎化を実現可能なクラスの構造に直していきます。ここでは、設計モデルについてすべては説明できないので、特にコンポーネントに関連したことについてお話しします。

汎化を実装可能な構造に直す

 最初に分析モデルの中の汎化を検討します。分析モデルでの汎化は、状態や分類を表しており、そのままでは実装できません。そのため、どのように実装するかを検討します。

ALT 図1 概念モデル

 図1の例では、会員と貸出クラスには汎化構造がありますが、貸出については、状態に貸出と返却、延滞中などがあり、将来この状態が増えることはないと考えられます。そのため、これらの状態をクラスの属性として持たせることとします。

 また、会員には、通常会員とゴールド会員があります。会員の種類は、当面増えることはないと思われますが、通常会員、ゴールド会員の貸出上限などのいくつかのビジネスロジックは異なること、および普通会員からゴールド会員へ動的に変更されることがあると考えられるため、 Stateパターンを利用します。

関連か依存かを検討しコンポーネントの境界を洗い出す

 次に、クラス間の関係が関連か依存かを検討します。例えば、貸出とレンタル商品には意味的に強い依存関係はありません。会員と貸出も同様です。そこで貸出とレンタル商品、貸出と会員の間の関連を依存に直します。ここで多重度を考えてみると1対多の関係になっています。それを依存にしてしまうと多重度は表現できなくなります。

 これは、依存が構造を表すのではなく一時的なメッセージの流れ、操作によるパラメータの受け渡しとなるためです。分析モデルは、モデルの構造を明らかすることが目的であるため多重度は重要ですが、設計モデルは、どのように実現するか(実装するか)を目的にするため、メッセージやパラメータをどのようにするかが重要になります。ただ、多重度がなくなることはないので、依存と関連を両方とも記述することも考えられますが、モデルの表記が複雑になってしまいます。

 コンポーネントは、機能的に結合度が高く、構造的で依存関係の強いクラス群で構成されます。そのために関連など依存関係の強いクラスは1つのコンポーネントにします。依存関係の弱いクラスがある場合は、コンポーネントを分けることができるか検討します。

 それに対しコンポーネント間は、結合度を低くする必要があります。機能的には分離され、お互いの関係を弱くします。これには、コンポーネント間にまたがるクラス間の関係を依存関係とします。もし依存にすることができないような関連がある場合は、結合度が高いと考えられ1つにまとめるなどコンポーネントの構成を考え直す必要があります。

 貸出クラスとレンタル商品クラス、会員クラスには関連が存在します。これらの関連はコンポーネント間をまたがるので、依存になるように検討します。レンタル商品クラスや会員クラスは、貸出クラスの操作「貸出する」内でローカルに使われるので、パラメータでそれらのオブジェクトを渡します。これで関連は依存に直すことができます。

ALT 図2 関連から依存へ

サブシステムを利用してコンポーネント化する

 次に、コンポーネント化の範囲を見極めたので、Facadeを利用してサブシステムを考えます。Facadeはコンポーネント内部のクラスが、ほかのコンポーネントから呼ばれる操作をまとめて定義します。コンポーネント内で利用するクラスのオブジェクトはFacadeで生成するようにすると、外のコンポーネントはFacadeを知っているだけでよく、コンポーネント内部のクラスを知る必要がなくなり、コンポーネントのブラックボックス化が図られます。

 コンポーネントにおけるFacadeは、コンポーネントとしての機能をまとめるだけでなく、内部クラスのオブジェクトのライフサイクルを管理する役割があります。オブジェクトをどこで生成するかは、結構悩むことがあります。パラメータで渡すにしてもどこかで生成する必要があります。ただ、コンポーネント化に当たっては、できるだけ内部のクラスを外部に公開したくありません。そのためFacadeに外部に公開する操作を集めて持たせ、外部にインターフェイスを経由して公開します。そして、内部のクラスのオブジェクトを生成し処理を委譲します。

 Facadeの操作のパラメータはオブジェクトではなく基本型のデータを渡すようにできるか検討します。例えば、貸出クラスの操作「貸出する」のパラメータは会員オブジェクト、レンタル商品オブジェクトですが、Facadeの操作「貸出する」のパラメータは、基本型の会員コード、商品コード等とします。会員オブジェクトやレンタル商品オブジェクトは会員コード、商品コードを基にFacade内でオブジェクトを生成して、貸出クラスの操作「貸出する」にパラメータとして渡します。その際、 Facadeはステートレスになるようにします。

 図3は簡単な例ですが、複数のクラスで機能を実現しているような複雑な機能の場合は、Facadeにより機能をまとめると、クライアントはコンポーネント内部のクラスを知ることなく機能を利用できます。

ALT 図3 コンポーネントの設計 (クリックすると拡大)

相互の依存関係や依存関係の循環を避ける

 次にコンポーネント間の相互の依存関係と依存関係の循環を検討していきます。相互の依存関係とは、パッケージAのクラスはパッケージBのクラスに依存しており、パッケージBのクラスもパッケージAのクラスに依存しているような関係です。パッケージAの変更がパッケージBに影響し、さらにパッケージAに伝播するような関係になることがあります。

ALT 図4 パッケージ・サブシステム間の関係

 また、依存関係の循環とは、パッケージAはパッケージBに依存し、パッケージBはパッケージCに依存し、パッケージCはパッケージAに依存する関連です。これもどれか1つの変更がほかのすべてのパッケージに影響を与え、影響が循環してしまいます。

 パッケージ間の関連は、前述したように依存になるように設計します。

パッケージやサブシステムを設計する際は、依存関係は1方向のみとします。

ホットスポットによる拡張性を定義する

 ホットスポットとは、「予想される変更に対する保守性・拡張性の仕組み」です。例えば、料金計算などビジネス環境の変化により、頻繁にロジックを変更する可能性が高いようなビジネスロジックが考えられます。

 ホットスポットはフレームワークに対する変更可能な個所をいいます。このホットスポットは、継承で実現します。概念モデルのような普遍的な構造は、一般的にフレームワークとして考えることができ、固有な機能をホットスポットとして作り込んでいきます。

ALT 図5 ホットスポット

 図5では貸出料金のビジネスロジックを料金クラスとして定義しています。料金クラスとその操作である「料金を計算する」は抽象クラス、抽象メソッドとし、具象クラスとしてキャンペーン料金クラスを定義しています。これにより、料金の計算が変更になった場合は、新たに料金クラスを作成するだけで、貸出クラスには影響がありません。

 ホットスポットの具象クラスもFacadeでオブジェクトを生成し、貸出オブジェクトに渡します。どの具象クラスを利用するかは、外部パラメータで指定できるようにするのもよいでしょう。

 今回は、変更に強いコンポーネントをどのようにして作るかをお話ししました。実際にコンポーネントを設計するには、このほかにも永続化などの非機能要件など検討しコンポーネントを洗練していきます。これらはアーキテクチャ設計を通して考えます。次回はこのアーキテクチャの設計についてお話をしたいと思います。


参考文献
▼UMLに基づくオブジェクト指向分析設計実践(IBM Rational)
▼ビジネスオブジェクト フレームワーク作成手順書(ビジネスオブジェクト推進協議会フレームワーク開発部会)


Copyright © ITmedia, Inc. All Rights Reserved.

注目のテーマ