1
/
5

モデリング・DDD#14 「モデルの意味的な誤り(Ⅱ)」

Photo by NeONBRAND on Unsplash

はじめに

今回も前回の記事に引き続いて、「表記法としては間違っていないけれども、表現している内容(モデルの意味)に{おかしな|あやしい}ところがある」というケースをいくつか見ていきたいと思います。ここではモデルの意味的な部分/解釈に関わってくることになるので、「明らかに間違っている」とは言えない(見方によっては十分に正しい)ケースも出てきます。
そのため、この記事の内容が「どんなときも絶対的に正しい」などとは思わず、あくまでも参考程度に読んでみてください。

今回の記事では、以下のようなヘルスメーター(体重・体脂肪率計)の概念モデルを考えてみます。

  • 「株式会社 豆蔵ヘルスケア」では、新型ヘルスメーターMZ-HM01(図 1)を開発しようとしている。このヘルスメーターは素足で乗るだけで体重と体脂肪率を計測することができる。
  • ユーザ情報(身長、誕生日、性別)は4名まで登録可能。1~4のユーザ番号が割り当てられる。
  • 内部にひずみゲージ(Strain Gauge)をひとつ持ち、抵抗値の変化によって重量(体重)を測定する。
  • 4つの電極から弱電流を流し、電気抵抗を測定することによって体脂肪率を推定する(BI法)。どのような周波数の電流をどの方向に何回流して計測するかについては秘密。
  • 計測したデータは履歴としてユーザ1名あたり最大64件まで記録しておくことができる。最大記録件数を超える場合、自動的にもっとも古いデータから削除される。
  • 計測した体重および体脂肪率は、液晶パネルにデジタル表示される。
  • 体重/体脂肪率の計測中にエラーが発生した場合、「ピー」というブザー音を鳴らして液晶パネルにエラー・コードを表示する。
  • 直前に計測した体重より、今回計測した体重の方が1.5kg以上重かった場合、「ピピピピピッ」というブザー音を鳴らして警告する。
  • その他、細かい仕様については適宜想定を加えてください。

図1:新型ヘルスメーター MZ-HM01の外観図

図2:新型ヘルスメーター MZ-HM01のユースケース図

図3:ユースケース「体重・体脂肪率を計測する」のアクティビティ図

その1:コンテキストの不在

上述したヘルスメーターの概念モデル(クラス図)を描いてみるといった課題を実施してみると、最初は図 4 のような感じのクラス図が出てくることが意外と多いようです。

図4:概念モデルの例(イマイチな例)

この例では、主要なクラス群はそれなりに適切に抽出されているのですが、クラス間の関連が入り組んでいて「なんだかイヤな感じ」がします(クラス間の知り合い関係が多い=モジュール間の結合度が高い)。概念モデルとして「間違い」とは言えないのですが、クラス間の構造が整理できるともっと良くなりそうです。

上記のような概念モデルが作られる背景には、暗に「対象となっているシステムの中身をモデリングする」という意識があるように思われます。その意識自体は悪くはないのでしょうが、問題なのはそのシステムを構成する個々の要素間を安易に直接結びつけた構造にしてしまったという点でしょう。「ある構成要素の機能を利用するためには、その構成要素への直接の関連が必要だ」という思い込みがある場合、特にこのような傾向が強くなるように思えます。

少し発想を変えて、個々の構成要素が存在する「場(バ)」に相当するようなクラスを導入することでクラス間の直接の関連を切り離して整理してみた例を示します(図 5)。

図5:コンテキスト・クラス「ヘルスメーター」を置いて整理してみた例

このクラス図では最上位に「ヘルスメーター」というクラスが導入されており、「ヘルスメーター」のインスタンスがそれを構成する部品群を管理しているという考え方をしています(「体重計測部」⇒「体重計」、「体脂肪率計測部」⇒「体脂肪率計」といったクラス名の修正も行われています)。ある構成部品が他の構成部品の機能を利用する必要がある場合、「ヘルスメーター」クラスがそれらのオブジェクト間の仲介をします。このような構造であれば、個々の構成部品間の結合度を下げることができますし、対象となるシステムの基本骨格の理解・整理もしやすくなります。反面、「ヘルスメーター」クラスが仲介役となるので、メッセージの委譲が多くなったり、「ヘルスメーター」クラス内部の処理が若干複雑になってくる場合もあります。

この例での「ヘルスメーター」クラスのように、対象となるシステムそのものを代表するクラスのことを、私は「コンテキスト・クラス」と呼んでいます(注:「コンテキスト・クラス」という名称は一般に広く通用する用語・概念ではありません)。「システムの内側の世界」に存在する要素の独立性を高めることができるだけでなく、「システムの外側の世界」との境界を明示的に意識できるようになるので、ある程度大きいシステムのモデリングを行う際、私はよくコンテキスト・クラスを置いて整理する方法をとります。

その2:情報隠蔽の意味

さて、前述した 図 5 のクラス図ですが、ちょっと見直してみるとまだもう少し整理ができそうに思えてきます。特に「ヘルスメーター」が直接管理しているオブジェクト群が結構多いので、この構造のままだと「ヘルスメーター」クラス内の処理がちょっと複雑になってきてしまいそうです。そこで、さらにクラス間の構造を見直ししてみました(図 6)。

図6:仮想的な部品「UIパネル」を導入して整理してみた例

このクラス図では、「UIパネル」という仮想的な部品を導入して、「液晶パネル」「ボタン」「ブザー」をその管理下に置くような構造にしています(その他、いくつかの関連も変形しています)。ハードウェア的には「UIパネル」という独立した部品は存在しないのですが、ソフトウェア・モジュールの構造として有利なためこのような概念的なクラスを差し挟んでみた...という例になっています。

ここで本記事の冒頭で示したヘルスメーターの外観図(図 1)を見直してみてください。「液晶パネル」と各種「ボタン」は製品上面の近い位置に配置されているのでこれらがグルーピングされるのはそれほど違和感がないのですが、「ブザー」は底面側の物理的に離れた位置に配置されている部品で、もしかしたら「液晶パネル」や「ボタン」を制御しているボード(基盤)とは別のボードで制御されている部品なのかもしれません。『ならば「ブザー」は「UIパネル」の下ではなく「ヘルスメーター」の直下に置いたままの方が良かったのでは?』という考え方が出てきそうです。図 6 の概念モデルでは、なぜ「ブザー」を「UIパネル」の下に配置しているのでしょうか?

今回モデリングの対象としているシステムで『「ブザー」が何のために使われているか』を考えてみると、これは『ユーザに対してエラーの発生や警告を伝えるための手段として使われている』と言って良いでしょう。このような手段の実現方法は仕様変更を受けやすいものであると考えられます。今対象としているMZ-HM01という機種ではエラーや警告の通知のためにブザー音を鳴らしていますが、これはたとえばLEDランプの点滅や液晶パネルでの表示や音声合成モジュールによる発声などであってもおかしくありません。

一方、「UIパネル」はユーザ{から|へ}の入出力を管理するバウンダリ・クラスとしての役割を持っており、これを利用する「ヘルスメーター」からはその入出力を具体的にどのような手段によって行っているかは隠されています(「UIパネル」クラスを置くことによって、「ヘルスメーター」クラスは「液晶パネル」や「ボタン」などという部品があることさえ知りません)。このように、同種の目的を達成するための手段として存在する具体的なクラス群やそれらの内部構造を隠すクラスを導入することによって、手段の変更に対して強いオブジェクト間の構造が得られます。

このような考え方から、図 6 の概念モデルでは「ブザー」を「UIパネル」の下に配置して隠すような構造にしています(ただし、設計モデルとして詳細化していく際には、処理のオーバーヘッドや物理的な通信手段等の制約条件に応じて構成が変わっていくことがあるので注意してください)。

オブジェクト指向の特性のひとつとして「情報隠蔽(じょうほういんぺい)」が挙げられますが、多くの人がこれを『あるオブジェクトが保持している属性を他のオブジェクトから見えないようにすること』と理解しているようです。この認識は間違っていないのですが、情報隠蔽は属性だけでなく関連を辿って参照/保持されているオブジェクト群に対しても同じように有効であるという意識も明確に持って欲しいと思います。つまり、『あるオブジェクトから先にどんな種類のオブジェクトがいくつ存在するか、そのオブジェクトを利用する他のオブジェクトからわからないように隠す』という考え方です。

その3:さらにもう一歩先へ

実際のシステム開発では、図 6 のようなクラス図に対していくつかのシナリオについてシーケンス図などを記述して動的な側面の検討も加えた段階で、『よし、これで分析完了』とみなして、そのまま設計・実装へと進んでいくようなケースが少なからずあるようです。ところが、このレベルのモデル(概念モデル)では実は分析が足りていないのです。概念モデルをベースとして設計・実装へ進んでしまうケースでは、実にいやらしいことに、(特に問題が起こらなければ)それなりのコストで実際に動作するシステムが出来あがってしまいます。なので、そのシステムの開発だけに限って言えば、一見成功したかのように思えてしまいます。

問題はそのシステムの機能拡張や後継機種の開発の際に顕在化してきます。『一度オブジェクト指向でシステム開発したのだから、きっと拡張性/再利用性が高い構造になっているに違いない』と目論んで、拡張や再利用を試みようとするのですが、普通、これはそれほど上手くいきません。『せっかくオブジェクト指向にしてみたのに、なんだかオブジェクト指向としてのうま味がほとんど出せないなぁ...』といった感じになってしまうのです。

なぜ上手くいかないのかというと、最初に作られたシステムがMZ-HM01という「特定の機種」の「現在の仕様」だけを考えて作られた概念モデルをベースに設計・実装されているからです。その概念モデルに登場している個々のクラスの中には、MZ-HM01だけでなくもっと一般的に多くのヘルスメーターで共通に使える構造・振る舞いの部分と、MZ-HM01固有の都合に依存する構造・振る舞いの部分が混ざった状態になっています。この状態のまま設計・実装されたコードでは、内部構成や都合(制約条件)が異なる後継機種の開発や機能拡張の際にうまく再利用/拡張ができないことが多いのです。オブジェクト指向と言っても別に魔法ではないので、単にクラスの形式でソフトウェア・モジュールの構造を構築しただけでは、本来のオブジェクト指向が狙っているうま味である再利用性/拡張性はなかなか上げられない...ということです。

これらのうま味を出せるようにするには、概念モデルに対して他の機種の構成や将来適用される可能性の高い機能拡張などについての想定を加味し、汎化関係や委譲などオブジェクト指向特有の機構・技法を上手く利用して、一般に通用する部分(ドメイン・フレームワーク)と特定機種に依存する部分とをクラスとしてもしっかり分離・整理する...という「ひねり」を加える必要があります(このように分解・解析・整理されたものを、私は「分析モデル」と呼びます)。

このあたりのトピックは抽象化やドメイン・フレームワークの話になってきて、この記事の範囲を超えつつあるように思いますから、分析モデルとして抽象レベルが少し上げられたクラス図の一部の例(図 7)を示すにとどめて、ここではこれ以上深く突っ込んでいかないようにします。

図7:フレームワーク的な抽象化が行われた例(分析モデル)

このクラス図(いくつか細かい部分は省略されています)では、おもにいくつかの抽象クラスで構成される枠組みの部分の一部のイメージが示されています。ある特定の製品では、この枠組みのいくつかのクラスを具象化(サブクラス化)して特定の製品固有の構成・振る舞いを表現することになります。この枠組みでは、「体重計」と「ひずみゲージ式体重計」がクラスとして分離されているので、ひずみゲージを使わない別方式の計量手段を持つ製品が登場してきても、「体重計」を使う側(「ヘルスメーター」)はその方式の違いを意識することなく取り扱うことができます。同様に、「体脂肪率計」と「BI式4極型体脂肪率計」がクラスとして分離されているので、将来6極型の体脂肪率計を搭載する製品が登場してきても対応しやすくなっています。

おわりに

今回の記事では、仮想的な組み込み製品を例として、概念モデルや分析モデルを作成していく際の考え方や注意点をいくつか紹介してきました。実際にUMLによるモデル・ベースのシステム開発を上流からやっていこうとする際には、ここで示したもの以外にもさまざまな見方や落とし穴があります。表記法の正誤レベルの議論を超えて、モデルの意味的な解釈やモジュールの構成指針に関するノウハウが蓄積され一般化されていければ、「文化としてのモデリング」に一歩近付くことができ、さらに踏み込んだ議論ができるようになってくるでしょう。この記事が、そのような流れにつながる何らかのお役に立てれば幸いだと思います。

※転載元の情報は上記執筆時点の情報です。
 上記執筆後に転載元の情報が修正されることがあります。


モデルの意味的な誤り(Ⅱ)
本記事は、2010年10月15日公開のものを再掲載しています。 ...
https://www.mamezou.com/techinfo/modeling_ddd/modewaza_08
株式会社豆蔵では一緒に働く仲間を募集しています
同じタグの記事
今週のランキング