メンバページ: Go
やさしいクラス設計「活きのいいクラス見つけます」 by Java and C#
Enjoy Class Design "How to detect Fresh Classes" by Java and C#
このページは、やさしいクラス設計の一つとして「活きのいいクラス見つけます」をテーマにしての話を掲載していきます。
- A 君: え、まずはっと、クラス設計って?
- G 君: うーん、クラスベースのオブジェクト指向におけるベースじゃ。
- A 君: それって、トートロジーぽいですが、結局、なんなんですか。
- G 君: まぁ、それは置いておいてじゃ、「活きの良さ」がすべてじゃ、クラスは。
- A 君: 置いておかれても困るんですが、まぁ、いいか。
| 1日目 | クラス設計とは |
| 2日目 | クラス設計の生産量とその設計生産性 | 3日目 | オブジェクトの発見方法の歴史 |
| 4日目 | 新規システムにおけるクラスの発見 |
| 5日目 | レンタルビデオショップのクラス発見(現物クラス編) |
| 6日目 | レンタルビデオショップのクラス発見(抽象クラス編) |
| 7日目 | レンタルビデオショップのクラスの詳細設計 |
| 8日目 | オブジェクト指向機能の効率 |
| 9日目 | リファクタリング(デザインパターン編) |
1日目 --- クラス設計とは
ここではクラスとその設計について書いていきます。
オブジェクト指向プログラミング言語の一つであるクラスベースの言語において、クラスの設計はプログラム設計の重要なものの一つです。
例えば、Java や C# はクラスベースのオブジェクト指向言語であり、そのクラス設計は重要です。
クラスとは?
まず、クラスとは何でしょう。 クラスとは、具体化されたインスタンスオブジェクトの一部を抽象化してカテゴライズしたオブジェクトです。 例えば、数値の 1や 2 のようなオブジェクトを考えます。このオブジェクトは + や - に反応するオブジェクトです。 これらのオブジェクトで値を抽象化したオブジェクトを考えます。これを integer オブジェクトと呼んでみます。この integer は + や - に反応するオブジェクトで値が抽象化されている オブジェクトの総称になります。逆に考えればカテゴライズしたことになります。この integer のようにしたものをクラスオブジェクトと呼び、ここでは単にクラスと呼びます。
- A 君: なーんか、わからんぞ、これって、今までのタイプではないんですか
- G 君: まぁ、そうじゃ。ただ 「+ や - に反応する」というフレーズが少し怪しいがの
- A 君: 怪しいって、オブジェクト指向の香りがするってこと・・・ふーん
クラス設計とは?
クラス設計とは、クラスの発見とそのカテゴライズ、分類になります。これはオブジェクトの発見と分類から始まります。
手続き型の言語では統治分割を行うのに、モジュール分割という武器を使います。これに対し、 クラスベースのオブジェクト指向ではモジュール分割に加え、 クラス分割という武器を使えるようになります。両者の武器は融合され、 より強力なモジュール分割も含んだクラス分割の武器になります。
- A 君: え〜、オブジェクト指向って、手続き型でやってきた武器を含んでいるんですか、なんかずるい
- G 君: まぁ、後出しじゃんけんみたいなもんじゃ、後で出す方が有利なんじゃ
- A 君: で、クラス設計ってなんですか、
- G 君: クラスの名前を決めることじゃ、それがすべてじゃ。名前で体を表現すれば自然に関係が生まれるんじゃ。
- A 君: ええっ、また変なことを・・・まぁ、いいか、でオブジェクト設計と違うンですか
- G 君: まぁ、なんじゃ、総称としてのオブジェクト設計には当然クラス設計もあるんじゃが、まぁクラス設計で。
このドキュメントにおけるクラス設計とは、必要ではありますがトリビアルなクラスを見つけることでなく、 エレガントなクラス、つまり「活きのいい」クラスを見つけることを中心に書いていきます。
- A 君: またトリビアルって言って、面倒なクラス設計は逃げる。
- G 君: まぁ、そこはじゃ、ストレートフォワードなんじゃ、クリアなんじゃ。きっと。
- A 君: 「エレガントつまり活きのいい」も我田引水ぽいし。
活きのいいクラスとは?
ではエレガントな、または「活きのいい」クラスとは何でしょうか。以下にいくつかの必要条件を書いていきます。
- 生きていること --- 使われること
- 活動的であること --- 能動的操作を行なうか、能動的操作を誘引する受動的クラスであること
- 人生の目的がはっきりしていること --- 何のためのクラスかが一目でわかること
- 粋であること --- 小気味のいいサイズで、多くのことを欲せず、余計な事をしないこと
- そして、発見されにくいこと --- 気が付きにくいクラス、これは必須ではなくオプショナルな条件
他にもあるかも知れませんが、ここではこのようなクラスの発見から始めてみたいと思います。
- A 君: 粋が分かりにくいですが。他も抽象的であいまいですが。
- G 君: まぁ、おいおい、わかってくることじゃろうって。江戸っ子は粋じゃ。
- A 君: え、江戸っ子でしたっけ。粋の定義も、まるこのおじいちゃんレベルだし。
クラス設計の流れ
クラス設計の流れとしては
- (前段階)ユーザヒアリング、要求分析
- オブジェクトの発見
- オブジェクトの分類、関係の発見
- クラスの発見、関係の発見
- 属性や操作の発見
を行い、以下のことを繰り返します。
- 妥当性、有効性の確認
試作、評価も含む
- オブジェクトなどやそれらの関係の再発見
- 再構成
設計フェーズも繰り返しになります。妥当性や有効性の確認のために、試作やその評価を含むことになるでしょう。 絶えることのない繰り返し作業により、クラス設計は幕を閉じるでしょう。
- A 君: 何となく、無限ループのような気が
- G 君: 絶え間ない改良じゃよ。それに最初に実装コードを思いつくようじゃと駄目じゃ。
- A 君: え、いいんじゃないですか
- G 君: それでは進歩がないんじゃ、逆にゆっくりとデザインを漂わせるのがいいんじゃ。
- A 君: 漂わせる?よくわかりませんが、まぁいいか
明日は、最初のクラス設計に入る前に、クラス設計の生産量やその設計生産性について書いていきます。
See you again !!
2日目 --- クラス設計の生産量とその設計生産性
今日は、活きのいいクラスの見つけ方の話の前に、クラス設計の生産量をどのように計測するか、 その結果の生産性について考えていきたいと思います。
クラス設計がどの程度進んでいるのかの進捗管理にも必要でしょうし、「設計段階の生産性」評価にも必要になるものです。 「計測できないものは制御できない、制御するためには計測する必要がある」になります。
- A 君: それに上司や同僚、後輩に威張るためでしょう
- G 君: 汚い設計をいくら生産してもマイナスになるだけじゃ、この意味では「意味のある設計」というものが必要じゃ
- A 君: それもここでやるんですか
- G 君: それはまたの機会じゃ、きっと
- A 君: ない方に 500 カノッサ
まず、考えられるクラス設計の生産量を測るメジャーとして、
原始的なクラス数- クラス数
- クラス間の関連数
- その他のクラスの属性や操作数
があります。ここでは簡単化のために特に1のクラス数に注目して、他の項目は今後は現れませんが、 例えば、継承数などの2のクラス間の関連数、ゲッタ、セッタなどのアクセッサの操作数なども計算します。
この原始的なクラス数による生産量の計測は、プログラムの生産量を Lines of Code (LOC) などで計測することに似ています。また、これで良さそうに思うかも知れません。
しかし、プログラムは実行可能なコードであるため、その粒度や詳細化レベルは一定です。 一方、クラス設計におけるクラスの粒度やその詳細化レベルは一定ではありません。 つまり、概要レベルのクラスからプログラムと等価なクラスまで存在します。
例えば、クラス設計の生産量をクラス数で計測するのであれば、詳細化とクラス設計の問題のカバーした部分が 一緒になってしまい、実際感覚の生産量には合致しません。
- A 君; えー、めんどいから、クラス数でいいじゃん
- G 君: それじゃ、シングルトンクラスばっかり作るものが現れるんじゃ
- A 君: またまた、レアなケースを反論材料に・・・まぁいっか
そこで以下のメジャーが考えられます。
詳細化レベルを統一したクラス数- クラス設計が終わった問題のカバー率
- 詳細化レベルを揃えたときのその詳細化レベルのクラス数
しかし、1は率であるため、問題に影響されすぎて、例えば時にマイナスの生産量になることもあるでしょう。 2 の方法は詳細化レベルがあいまいになるため、機械的に計測することは困難になります。しかし工学的な話では、 これでも「運用」により、実施は可能でしょう。少なくとも前述の単なるクラス数よりもましでしょう。
オブジェクト指向メトリクスとして、キーとなるクラスに注目するというものがあります。つまり、メジャーの候補としては
キークラス数- キークラスの個数
があります。キークラスは概要レベルのクラス設計でも詳細レベルのクラス設計でも同形になる傾向がありますので、 詳細化のレベルに影響を与えません。
しかし、逆にキークラスを手動では発見できるとしても、自動的には発見できません。 キークラスそのものの定義も自然言語で記述しますので、あいまいです。例えばキークラスの定義は以下のものがあります。
キークラスの定義(自然言語による定義)- ビジネスドメインの中心となるクラス
- それがないと開発や保守が著しく困難になるクラス
- エンティティクラス(モデルクラス)のサブセット
- ユーザが重要視しているクラス
- シナリオに多く含まれるクラス
1 が定義で、2 がその妥当性確認に使うものです。3 は必要条件の一つであり、傍証として 4, 5 があります。
- A 君: 確かにあいまいですね、2 なんか。また 4 は定義することを捨てていますね
- G 君: プログラムの評価は結局は何でも保険金額にする保険屋さんの査定が一番じゃということじゃ
- A 君: えー、まぁそうかも知れませんが。これは議論になりますね、まぁ他もそうか
- G 君: この定義はある本に書いてあるものをそのまま移したんじゃが駄目かの
この定義を基本の考え方としますが、キークラスを自動的に発見できる方法を考えます。 つまり、これが自動判断するときのキークラスの定義になります。
キークラスの定義(機械的な判断が可能な定義)- 継承関係が2個以上か、または
- 関連数が 4個以上のクラス、でかつ以下の条件を満たすクラス
- 属性を 4個以上持っているクラス
つまり、 (1 and 3) or (2 and 3) になります。これは例えば、UML 表記されたクラス図に等価なテキスト表現(XMI など)から、プログラムで自動的に判断できます。
しかし、クラス設計が詳細化すると、本来はキークラスではない、つまらないクラスもキークラスになる危険性があります。
- A 君: うーん、自然言語による定義と機械的な定義では、やっぱ違うんじゃ
- G 君: まぁ、本人も反省していることようじゃし、なんとかしてやろうではないか
- A 君: え、ここでキークラスの閾値なんかを考えるんですか
- G 君: いや、もっと哲学的にじゃ。実は長年研究してきた成果があるんじゃ
- A 君: 何?それは
- G 君: 実はキークラスになるクラスの名前には、ある傾向性があるんじゃ。
- G 君: しかしこの話は長くなるでな、また別の機会にでも
- A 君: ふーん
ここでは、クラス設計の生産量として、以下のメトリクスによる生産量を使うことにします。
- キークラス(機械的な判断が可能な定義)の個数 c
- キークラス間の継承数 i
- キークラス間の関連数 a
- クラス設計の生産量 = c1・c +c2・ i + c3・a (c1,c2,c3 は係数)
- A 君: また急にクラス間の関係数が復活していますね、本文にも書けばいいのに
- G 君: 係数が問題じゃ、まさかマイナスはないにしてもじゃ
- A 君: そう言えば、本文には係数のことについては書いてありませんね、また秘密なのかな
この生産量の特徴は以下のようになります。
- 機械的に計測可能
- クラス設計の詳細化レベルに依存しない
- A 君: ほんとかな、嘘っぽい
- G 君: いやいや、このレベルだけでは駄目じゃ、もっと「微調整」が必要じゃ
- A 君: さては試験運用しておかしいところがあって、直したんでしょう、教えてくださいよ
- G 君: いやいや「微調整」じゃ、トリビアルじゃ、だからここもプログラムで見ておくれ
- A 君: 説明が面倒になっただけでしょ、またはこれからも変更しようとしているんでしょ
しかし、前述したように、クラス設計を詳細化するとつまらないクラスもキークラスになる可能性があることに注意する必要があります。
今日は設計段階の生産量とその計測方法について書いてきました。これに掛かった時間で割り算すれば、時間当たりの設計生産量、 つまり設計生産性が計測できることになります。明日は本来の「活きのいいクラス見つけます」の本論に入っていく予定です。
See you again !!
3日目 ---(クラスの発見1) オブジェクトの発見の歴史
今日はクラスの候補を発見する方法を書いていきます。
クラスとは、前に書いたとおり、オブジェクトを抽象化したものです。 そこで抽象化したクラスを発見する前に、 または同時にオブジェクトを発見する方法について書いていきます。 オブジェクトからクラスは抽象化すべきかどうかで導き出せることになります。
その前に、まずオブジェクトの発見方法の歴史を振り返ってみたいと思います。
- A 君: 歴史って、また年寄りくさいことを・・・
- G 君: まぁ、年の暮れでもあるからして、歴史の紐を
オブジェクトの発見方法の歴史
1980年代前半にオブジェクト指向という言葉がもてはやされたころ、 非オブジェクト指向からオブジェクト指向へどのようにパラダイムシフトを行うかが興味の的になっていました。 ここでは、そのいくつかを紹介します。
1980年代に流行っていたオブジェクトの発見方法 - 機械的発見とレガシー変換
- 名詞句からの発見
問題に出現する名詞と名詞句からオブジェクトを発見します。- 固有名詞 --- オブジェクト候補
- 一般名詞 --- オブジェクトまたはクラス候補
- 単体名詞 --- オブジェクト候補
- 複数名詞 --- オブジェクトまたはクラス候補
- 代名詞 --- 名詞に置き換える
- 動詞になるべき名詞 --- メソッド候補
本来は動詞の意味「する」が略されている場合が多い
例. 設定や運用、サービス、機能 - 動詞 --- メソッド候補、複雑なときはオブジェクト候補
- DFD からの発見・変換
作成済みの DFD (Data Flow Diagram) から、オブジェクトを発見・変換します。- CD(Context Diagram)のターミネータは、そのインタフェースをカプセル化するオブジェクトの候補
- データストア(ファイル)は集約オブジェクトの候補
- 特定のデータストアに関連するオブジェクトはクラスの候補
- データフローはメソッドの候補
- 複雑なデータフローはオブジェクトの候補
- 構造体からの発見・変換
作成済みの非オブジェクト指向プログラムに出現する構造体から、オブジェクトを発見・変換します。- その構造体専用の操作(関数)があるときは、クラス候補
- 構造体専用の操作(関数)はメソッド候補
- 構造体を含む構造体は、集約オブジェクトのクラス候補
- 抽象データタイプは、クラス候補
- 構造体はクラス候補
- CRC による妥当性チェック
CRC (Class Responsibility Collaboration)カードによるクラス発見の妥当性をチェックします。- 数人のグループにより、協議して、クラス設計の妥当性をチェックする方法
- 各自が CRC カードにクラス名、責任、協調者を書く
- 各自作成の CRC カードをホワイトボードなどに示す。協調者とクラスを線で結ぶ
- 不足分やおかしいところをディスカッションして修正する
この時代のオブジェクト候補の発見方法は、オブジェクト指向の定義から発見するというよりも、 与えられた問題から機械的に発見する方法や、 既存の手続き型プログラムまたはデータフロー型プログラムから変換する方法が模索されていました。
もちろん、この時代の論文や書籍などの文献は、オブジェクト指向時代の黎明期にふさわしく 「純粋オブジェクト指向技術」や「オブジェクト指向原理主義」ばかりでした。実務的な手法や方法を論述されたものは少なく、 わずかに上記に挙げた方法が紹介されていたに過ぎません。
そして、手続き型指向などの非オブジェクト指向からオブジェクト指向への「パラダイムシフト」を行う「方便」として、 これらの方法を使っていました。しかし、方便と言っても、非オブジェクト指向プログラムやその技術者が多かったために、 この方法は主流であったのも事実です。
- A 君: 悩んでいたんでしょうね、当時の人は
- G 君: いきなり、1+1の意味論が変わったからのぉ
- A 君:それは置いておいて、CRC のわいわいがやがやはいいですね、でも当時も CRCって言っていました?
- G 君:言ってないが、同様のものじゃ。そのときにクラス図がばらばらであったのが大変じゃったわい
- A 君:確かにそうでしたよね。親から子なのか、子から親なのか、四角か丸かなどですね
1990年代前半から主流になったオブジェクトの発見方法 - オブジェクト指向の定義と関係
- オブジェクトの定義によるオブジェクトの発見
正統的な方法です。
既にオブジェクト指向を「最初」に学んだ技術者が普通になってきたために、パラダイムシフトが不要です。 - 経験的なモデリング対象によるオブジェクトの発見
オブジェクトを発見するときの、モデリング対象を経験的に把握し、そのモデリング対象から発見する方法です。 経験が必要になります。また人(経験)によってモデリング対象は異なります。
例.人間、役割、組織、場所、画面、ログ - 関係によるオブジェクトの発見
オブジェクト間の関係に注目して発見する方法です。- 関係を発見します
is_a 関係、has_a 関係、use_a 関係、is a service by using 関係、is a role played by 関係など
慣れてくれば直接的に、継承、インタフェース、委譲、コンポジションなど - 関係の両端のオブジェクトを発見します
- さらに関係を発見します
継承関係に注目しているときにはさらに抽象化して継承関係が新たにないかを発見します - これを繰り返します
- 関係を発見します
オブジェクト指向が特別のものでなく、また「銀の弾丸」でもなく、単なる使いやすい設計・プログラミングのツールとして、 自然に受け入れられてきた時代です。 そのために、レガシーなものからの変換や機械的な発見方法は廃れ、定義に基づいた発見が主流となってきました。
モデリング対象を経験によって、フィルタリングするなど、経験による方法が出てきました。
また、オブジェクト間の関係として、80年代のように「継承」一本槍でなく、他の関係も注目されてきた時代です。 80年代に行われてきた関係による発見は「継承だけのツリー構造」をしているクラス設計が多かったようです。(これは Small Talk-80 の影響を受けたのでしょう。)
- A 君: また、Small Talk 批判してる、またコメントが一杯送りつけられますよ
- G 君: !オブジェクト指向の悲劇は Small Talk から始まったのじゃ
- A 君: 長くなりそうなので、次へ行きましょう
1990年代後半からのオブジェクトの発見方法 - パターンと UML
- デザインパターン、アーキテクチャパターンなどの各種パターンの利用による発見方法
既に実効性が確認されている優れた各種パターンから、類推して、オブジェクトを発見する方法です。 - UML ダイアグラムからの発見
UML のユースケース図などから、オブジェクトを発見する方法です。- アクターとユースケースの境界(システム境界との接点)は、境界オブジェクトの候補
- ユースケースはメソッドの候補
- 複雑なユースケースは制御クラス(機能クラス)の候補
- ユースケースの受動的な操作対象になるものは、モデルクラス(エンティティクラス)の候補
UML 2.0 のトレーサビリティにより、さらにオブジェクトの発見はユースケースに依存するようになっています。 - コンポジション中心
方法論ではないですが、オブジェクト間の関係として、コンポジション中心の考えが流行しました。
この時代からデザインパターンが流行しました。オブジェクト指向の経験が豊富になり、 クラス設計においても、自然と取捨選択が行われた時代でした。
一方、UML も同時期に流行しました。UML はオブジェクト指向での表記法の一つですが、UML に内在する暗黙的な意味論により、クラス設計の方法にも影響を与えました。上記の方法は UML のユースケース図などから、オブジェクトを発見する方法です。
- A 君: 流行りましたよね。って、今でもまだ流行っていますよ。
- G 君: それは置いておいてじゃ、継承悪人説が出てしまったのぉ。やはり。
- A 君: そんなこと書くと、またオブジェクト信者から、コメントを一杯頂くことに。
- G 君: いや、いや、決して、継承が悪いと言っているのではなくて・・・
- A 君: まぁ、置いておいて、次へ行きましょう。
2000年代前半のオブジェクトの発見方法
- フレームワーク主導のオブジェクトの発見方法
モデルオブジェクト以外のプレゼンテーションオブジェクトやデータアクセスオブジェクトなどは、 システムに導入したフレームワークに依存するようになってきています。
例. .NET Framework, Java Web Service 系
新規となるエポックメイキングな方法論はなく、ますます、フレームワーク中心のものになっています。
- A 君: え、もう終わり?
- G 君: 2004年もまだ少しある、つまり2000年代前半はまだあるのじゃ、これからじゃ。
- A 君: ということで、まだだそうです。
- A 君 & G 君: 2004年も、これをお読みいただきありがとうございました。
明日(来年になります)は、オブジェクトの発見方法そのものについて書く予定です。
See you again !!
4日目 ---(クラスの発見2) 新規システムにおけるクラスの発見
今日は、新規システムにおけるオブジェクト・クラスの発見方法について書いていきます。
新規システムですので、過去のしがらみなどを考慮する必要もなく、いわゆる「教科書」通りの施策が行えます。 しかし、システム的には新規であっても、それを作成するメンバが過去の非オブジェクト技術で、 オブジェクトの発見作業をすることもあり、パラダイムシフトが求められることがあります。
1. 発見方法の選択と実施
3日目のオブジェクト発見の歴史で書きましたように、過去からいくつかの方法が提案されています。
- A 君: 久しぶりですね。オブジェクトの発見の歴史だけで終わるのかと思いましたよ。
- G 君: えーと、まぁ、色々とあったんじゃろ。
それでは、どの発見方法を選択するのがいいのかを考えていきます。
基本的な考えとしては、「オブジェクト・クラス設計を担当するメンバが一番行いやすい方法」が一番いい方法になります。
「名詞からの発見」でも「DFD からの発見」でも、「定義による発見」、「関係による発見」、 「経験による発見」などから、やりやすいものを選択します。
もし何もないのであれば、「定義による発見」と「関係からの発見」 を並立的に使うのが、発見の効率がいいでしょう。もちろん、経験者は過去の「経験による発見」が効率がいいでしょう。
- A 君: 何でもいいということですか?
- G 君: まぁ、「パラダイムシフト必要論」ではなく、漸近的なアプローチじゃ。
- A 君: また、政治家みたいに。
2. クラスの妥当性
クラスの発見の妥当性をチェックする方法として、チェックリストを手にしての 「CRC によるチェック」がいいでしょう。
副次的効果として、メンバのレベルの共通化・向上が図れます。
チェックリスト
ここで、チェックリストは、CRC の専用ではありませんが、クラスの過不足をチェックするために用意します。特に CRC のように複数人数で行うときは、追加は容易ですが、削除は面倒であるということから、クラスの個数が多くなりがちです。
そこでチェックリストとしては、削除基準を作成する必要があります。「これはクラスとして必要か? メソッドや単なるオブジェクトでいいのではないか?」を判断するチェックリストが必要になります。
- A 君: 削除のためのチェックリストですか。作成のためのチェックリストは必要ではないのですか。
- G 君: もちろん、そちらが本来的なのじゃ。
- G 君: しかし、複数のメンバでやると「いいところ」を見せようとしてクラスは一杯出てくるんじゃ。
- A 君: それで削除のチェックリストですか。
3. クラスの洗練
発見したクラスを関係も含めて、洗練させます。このときには「デザインパターンからの発見」や 「フレームワークからの発見」などの過去の優れたクラス設計を手本に使います。
ここで抽象的なクラスや共通化クラス、または詳細化されたクラス、特定の役割を持ったクラスなどを新規に追加するようにします。
4. クラスの詳細化
次に発見したクラスやその関係を詳細化します。
概要レベルのクラスから、実装レベルのクラスに段階的に詳細化します。
この過程で新たなクラス設計が必要になる場合もあります。新たなパターンを作成・適用する場合も出てくるでしょう。
- A 君: 洗練と詳細化を別にするんですか。
- G 君: そうじゃ、一緒くたにして、魑魅魍魎になったクラス設計を多く見かけたもんでな。
クラス発見作業の繰り返し
上記の作業「1. 発見方法の選択と実施」から「4. クラスの詳細化」までを繰り返すことになります。
例えば、クラスの洗練中にクラスの発見に戻ることもあるでしょう。
- A 君: やはり繰り返しですか。一発で決められないのですか。
- G 君: まぁ、この手は繰り返すことが繰り返されるのじゃ。
- A 君: 最初はいい加減でいいということになりません?
- G 君: 修正は効くということになるが、最初は方向付けになるから「いい加減」にしないと駄目じゃ。
クラスの発見の演習「レンタルビデオショップ」
それでは、クラス発見を例題に従ってやってみましょう。
以下のレンタルビデオショップのシステムでの、クラスの発見をやってみましょう。
レンタル用ビデオの在庫管理と顧客管理システム
在庫/貸出管理
ビデオのタイトル名、購入本数、種別、購入価格、購入日、在庫本数、貸出本数、廃棄本数
20回レンタルを行なったものは廃棄。?購入日から3ヶ月間は新作扱い。
顧客の年齢により貸し出しの規制を行なう。(15,18,20才)
1ヶ月以上の長期返却のないものは不良貸し出し扱いとする。
3ヶ月以上平均貸し出し率が 0.1 以下のものは不良在庫扱いとし、店頭から、1本を残し、引き上げる。
顧客管理
会員制、1年更新、年会費無料(管理しない)
顧客の名前、年齢、性別、住所、認証方法、お知らせ送付の有無
貸し出し記録、不良貸し出し記録
貸し出し毎に上記の情報を管理する
不良貸し出しがあるか、過去1ヶ月間にあったときは貸し出しを行なわない
自分の好きな方法でクラス発見を行ってみてください。
詳細化レベルは、主要なクラスとそのクラスのクラス名と主要な操作と属性だけの概要レベルにします。
クラス設計においてのアピールポイントや注目ポイントがあれば、それを記載してください。
次回は、このクラス設計から行うようにします。
- A 君: え、ここで終わり。
- G 君: そのようじゃ。まぁ、テレビ番組から学んだのじゃろぅ。困ったもんだ。
- A 君: そうすると、次は演習の説明からになるの?困ったもんだ。
See you again !!
5日目 ---(クラスの発見3) レンタルビデオショップのクラス発見(現物クラス編)
今日は、レンタルビデオショップのクラス発見を行っていきます。
まずは現物クラスの候補を発見します。
手法は、取り敢えず、「名詞句による発見」でやってみましょう。
現物クラス by 名詞句による発見
名詞句の分類
与えられた問題を眺めて、名詞句を抜き出し、それを「普通名詞」「固有名詞」、「単数形」「複数形」に分類します。 「代名詞」のときはそれを指す名詞に変換してから、分類します。
また、本来は動詞であることを名詞で表現しているもの、例えば、「初期設定」「運用」「管理」などは省くために別に分類します。
レンタルビデオの問題では、以下のように分類しました。
- 普通名詞・単数形
タイトル名、購入本数、購入価格、購入日、在庫本数、貸出本数、廃棄本数
期間(問題には、「間」「長期」のように出現)
不良貸し出し、不良在庫、平均貸し出し率
顧客の名前、年齢、性別、住所、認証方法、お知らせ送付
貸し出し記録、不良貸し出し記録 - 普通名詞・複数形
ビデオ、会員(顧客は会員制であるので、同じものとして分類) - 固有名詞
(出現した名詞の1状態を表す固有名詞)
15才、18才、20才、3ヶ月間、1ヶ月以上、3ヶ月以上、0.1 以下、1本、1年、有無、過去1ヶ月間 - 動詞になる名詞
購入、廃棄、新作扱い、貸し出し、規制、長期返却、不良貸し出し扱い、更新 - 外部または無視していい名詞
年会費(管理しないから)
日本語では単数形と複数形は明示されていませんが、文意からそれらを分類します。
問題からは、ビデオと会員別に分類されていましたが、上記にまとめるときに一緒にしています。これを再分類します。 このときに共通のクラスが発見できることもあります。
この段階で、動詞になるべき名詞(上記 4)やシステム外部の名詞や無視していい名詞(年会費)、 同じものを別の名詞で表現しているもの(会員と顧客)を既に分類しています。 通常は名詞の分類が終わった後に、さらにこの分類を行い、名詞の分類をリファインします。
- A 君: 問題に与えられているものより、見にくくなっていません?
- G 君: 実際のユーザとのヒアリング結果は、今回の問題のように「まとまって」はいないんじゃ。
- G 君: 今回はまぁ、もう一度、まとめなおすという感じじゃ。
- A 君: あー、そうですか。想定の範囲内でしたが、言い訳ぽいですが。
現物クラスのスケッチ
次に分類された名詞句から、クラスのスケッチ(概略のクラス構成)を抽出します。
すべての名詞は、「オブジェクト」か「オブジェクトの属性」の候補になります。
クラス候補は、「普通名詞・複数形」のものになります。ここでは、「ビデオ」「会員」になります。
「普通名詞・単数形」からクラス候補がないかを調べてみます。
このシリーズの最初にも書きましたが、効率化のために、クラスは一定の大きさが必要になります。 一定の大きさの例は、属性の種類や個数が4個以上というものがあります。 ここでは「普通名詞・単数形」のものから属性として4個以上持つ可能性があるものを調べてみましょう。
「貸し出し記録」と「不良貸し出し記録」は、会員やビデオ、貸し出し日付、返却日付など、 4個以上の属性を持つことが予想されますので、これらもクラス候補にします。
「住所」も住所を細かく管理すれば、複数の属性を持ちますが、レンタルビデオショップではそのような管理をしないと仮定して、 ここではクラス候補にしません。
それでは、発見したクラスの属性であるものとをマッチングしてみましょう。
クラスと属性のマッチング
- ビデオ
タイトル名、購入本数、購入価格、購入日、在庫本数、貸出本数、廃棄本数、平均貸し出し率 - 会員
名前、年齢、性別、住所、認証方法、お知らせ送付 - 貸し出し記録
- 不良貸し出し記録
これは与えられた問題と同じ内容になっています。逆に言えば、問題の段階で「現物クラス」 のスケッチがまとまっている段階のものになっていたことになります。
それでは、上記に出現しない普通名詞はどういうものになるかを見ていきましょう。
属性からのクラスの発見
「期間」を考えてみます。これはビデオの貸し出し期間の意味と、ビデオの在庫期間の意味の二つがあります。
貸し出しや在庫はビデオに関係するものです。この意味で、ビデオクラスの属性に「貸し出し期間」と 「在庫期間」を追加するという考えもあります。
しかし、前述したビデオクラスは、購入本数などの属性があることから、 同一タイトルのビデオ全体をインスタンスとするクラスになります。 そこで1本1本のビデオをクラスにすることを考えます。 これを前述のビデオクラスと混同しますので、前述のものを「ビデオタイトル」クラスにして、 1本1本のクラスを「ビデオ」クラスにします。
ビデオクラスの属性としては、「在庫期間」や「貸し出し期間」を与えます。またビデオタイトルも一つの属性にします。
- A 君: ビデオタイトルとビデオですか、少し分かりにくいネーミングでは?
- G 君: ネーミングは大切じゃ、でもこうじゃ。
機能クラスの抽出
名詞に相当するクラスは、いわゆるデータを持つモデル系クラスになります。このモデルクラスとは別に、 操作を中心とした機能クラスを作成する場合があります。
レンタルビデオショップのシステムでは、「貸し出し」は中心となる機能で、仕様変更も発生する可能性も高いと予想されます。 そこで、「貸し出し」クラスを機能クラスとして、ビデオクラスの操作から抽出して、独立のクラスにします。
貸し出しクラスの属性としては、貸し出し期間、ビデオ、会員を抽出します。
ビデオの属性であった貸し出し期間を廃止し、その代わりに貸し出しとの関連にします。
まとめ
名詞句によるクラス発見による現物クラス+α(機能クラス)を以下にまとめます。
- ビデオタイトル
タイトル名、購入本数、購入価格、購入日、在庫本数、貸出本数、廃棄本数、平均貸し出し率 - 会員
名前、年齢、性別、住所、認証方法、お知らせ送付 - ビデオ
貸し出し、在庫期間、ビデオタイトル - 貸し出し
貸し出し期間、ビデオ、会員
- A 君: 操作はないんですか?
- G 君: これからの楽しみじゃって、へへへ。
- A 君: 嘘、考えていないんでしょ。
明日は、抽象クラスを発見し、クラス構成をリファインするようにしたいと思います。
See you again !!
6日目 ---(クラスの発見4) レンタルビデオショップのクラス発見(抽象クラス編)
今日は、レンタルビデオショップのクラス発見のうち、抽象的なクラスの発見を行っていきます。
現実の世界に「物」として存在するクラス(=現物クラス)は、比較的発見が容易なクラスです。このときは5日目に書きましたが、クラスにするか属性にするか、操作をクラスとして独立させるかなどの選択はありましたが、ほぼ一直線に発見できました。
ここで抽象クラスとは、「インスタンスオブジェクトを生成しないクラス」だけに留まらずに、「直接的な現物」を持たないクラスとして考えます。
抽象クラスの役割
ここでは、まず抽象クラスがなぜ必要か、その役割や目的を紹介します。
- 分割統治クラス
分割統治することを目的として、上位に位置する分割統治クラスとしての役割。トップダウン設計のときに用いる。機能クラスになりやすい。 - 共通クラス
共通のデータ構造(属性)や共通の機能(操作)を取り出した共通クラスとしての役割。差分を継承や委譲で実装する。 - 抽象概念クラス
共通クラスの目的とも関係するが、概念的に抽象化したクラスであり、純粋な抽象概念クラスとしての役割。80年代の抽象クラスの本来的な姿。 - 部品用クラス
再利用を行うために、属性や機能を抽象化する部品クラスの共通クラスやインタフェース用クラスとしての役割。部品クラス自身をさらに抽象化したクラスを発見しないとまとまりが悪くなる。
部品クラス抽出のためのインタフェースクラスの場合はインスタンスが実在する現物クラスの場合もあるが、一緒に検討するためにここに加える。 - 抽象インタフェースクラス
外部との連携を行うために、一部機能を変更したり(アダプタクラス)、インタフェースを共通化したり(ファセードクラス)、などのためのインタフェースクラスを抽象化インタフェースクラスとしての役割。
現物系のインタフェースクラスも、一緒に検討するためにここに加える。 - シンプルクラス
機能(操作)が複雑であるとか、データ構造(属性)が複雑であることを解消するために導入する「シンプル設計用クラス」の役割。2 の共通クラスや 3 の抽象概念クラスなどは、結果的には、シンプルクラスの一つになりますが、ここでは、シンプルにする目的だけに特化したクラスを意味しています。
- A 君: 下へ行くほど、抽象クラスぽくないですが・・・
- G 君: 現物クラスを発見するときに見つけにくかったクラスの「再発見」「再構成」の意味もあるんじゃ。
- A 君; 「二つのことを同時に行ってはならない」に反しますが、まぁ、いいでしょう。
手続き型指向では、一般的に、機能はトップダウン的に統治分割し、データ構造はボトムアップ的に作成することが多いです。一方、オブジェクト指向では、機能とデータ構造の両者をまとめて、クラス設計します。このため、上記のようにトップダウン的抽象クラスとボトムアップ的抽象クラスがあり、これらを発見する必要があります。
ビデオレンタルショップの抽象クラス
ビデオレンタルショップの例に戻ります。
現物クラスとして、ビデオタイトル、会員、ビデオ、貸し出しの4個を発見しました。すべて現物クラスですので、それぞれ実体を格納するデータ構造や操作があり、そのインスタンスオブジェクトが存在します。
例. 現物クラスのインスタンスオブジェクトの例: 「Back to the Future Part 2」、「小津安二郎」、「シリアル番号 12345 の StarWars episode III」、「貸し出し:2005年5月30日貸出、2005年5月31日返却・・・」
- A 君: なんか古すぎ。
- G 君: 今回の突っ込むところはそこか。
これらの現物クラスから、このシステムで必要か、または将来必要となる抽象クラスを考えてみます。
記録クラス
貸し出しクラスの主要な属性である「貸し出し記録」は、「記録」の部分は他にも利用可能なものであるでしょう。ここでは、これを抽象クラスとして考えてみましょう。つまり、抽象クラス「記録」クラスです。
この記録クラスは、抽象化したデータを記録することを目的とします。このため、役割としては、3の抽象概念や実際に共通化できたときは 2 の共通クラスの役割を果たします。また、部品クラスとして機能(操作)を抽象化すれば、部品クラスとしての役割も持ちます。
- [貸し出し] ---コンポジション---> [貸し出し記録] ---汎化---> [記録]
の関係になります。(UML 用語を使って表現しています)。
会員クラス
会員クラスは現物クラスとして、既に発見しています。しかし、このシステムで将来、優良会員(お得意様)や未成年会員、家族会員などの会員種別が必要になってくると「予想」して、会員クラスを現物クラスではなく、抽象クラスとして定義し、現物クラスであった会員クラスを「一般会員」クラスにします。
これは、将来的な共通クラスになる役割や抽象概念の役割を果たす抽象クラスになります。
- [一般会員] ---汎化---> [会員]
の関係になります。将来の別の会員クラスの会員は、会員クラスのサブクラスにします。
- A 君: 現物クラスのときに、会員クラスの発見が失敗だったのでは?
- G 君: そうではないのじゃ。クラス設計は「気楽に」「やり直すことを前提に」する方がいいんじゃ。
商品クラス/商品情報クラス
ビデオタイトルとビデオの関係は、商品情報と商品の関係です。このように抽象化することによって、外部システムと連携を取るときのインタフェース層として、適当なものになります。
これは、抽象概念クラスの役割を果たします。ビデオレンタルショップで、ビデオ以外を扱うときは共通クラスの役割も果たしますが、現時点ではそこまでを予想しません。
- ビデオ ---汎化---> 商品
- ビデオタイトル ---汎化---> 商品情報
- ビデオ ---コンポジション---> ビデオタイトル
- 商品 ---コンポジション---> 商品情報
の関係になります。外部システムと連携するときには使用することを役割の一つとしますが、アダプタクラスなどのインタフェースクラスを明示的に作成する必要があるかも知れません。
抽象クラスのまとめ
以上の4個の抽象クラスを発見してきました。実装段階に入ると、シンプルにするための役割を持ったシンプルクラスを導入することがあります。典型的な例では、デザインパターンのクラスになります。
次回はクラス設計の詳細化を行っていく予定にしています。
See you again !!
7日目 ---(クラスの発見5) レンタルビデオショップのクラスの詳細設計
今日は、レンタルビデオショップのクラスの詳細設計をしていきます。
前回までで現物系のクラスと抽象クラスを発見してきました。それをまとめたものを以下に示します。
+---------------+ +---------------+ +---------------+
| 商品情報 | | 商品 | | 記録 |
+---------------+ +---------------+ +---------------+
↑汎化 ↑ 汎化 ↑ 汎化
+---------------+ +---------------+ +---------------+ +---------------+
|ビデオタイトル | | ビデオ | | 貸し出し | | 貸し出し記録 |
+---------------+ +---------------+ use +---------------+ has +---------------+
| タイトル名 | | 貸し出し | → | 貸し出し記録 | → | ビデオ |
| 購入本数 | | 在庫期間 | | | | 会員 |
| 購入価格 | ← | ビデオタイトル| +---------------+ +--|------------+
| 購入日 | has +---------------+ |
| 在庫本数 | |
| 貸出本数 | |
| 廃棄本数 | |
| 平均貸し出し率| |
+---------------+ |
|
+---------------+ |
| 会員 |←--------------------------------------------------+
+---------------+
| 名前 |
| 年齢 |
| 性別 |
| 住所 |
| 認証方法 |
| お知らせ送付 |
+---------------+
↑ 汎化
+---------------+ 将来のクラス +---------------+ +---------------+
| 一般会員 | | 特別会員 | | 返却 | ・・・
+---------------+ +---------------+ +---------------+
テキスト図形で見にくいですが、UML に準拠しています。 これを元にクラスの詳細化をやっていきましょう。
- A 君: なんで、テキストで図形を描くんですか? 手抜き?
- G 君: ノスタルジックを出してみたまでじゃ。決して手抜きではないぞ。
- A 君: それにしても、やや複雑のような気がしますが。
- G 君: ついつい、こういうところで作ると多めに作ってしまうんじゃ。これを「教科書効果」と呼んでおる。
クラスの詳細化に入る前に、日本人特有の問題について話します。
詳細化方針 1 --- (日本人特有の問題) 日本語と英語の名前
今までのクラスの発見では、クラス名や属性名など名前は「日本語」で発見してきました。 一方、Java などでは日本語の名前は技術的に使えますが、まだその文化になっていません。 また Java の予約語が英語ですので、日本語で書くとプログラムはある違和感があります。
それで「日本語の名前をいつ英語に変更するか」という問題が出てきます。
それに対する一つの解が、クラスの詳細化を行うときに「ついでに」やってしまう方法があります。 クラスの詳細化と名前の英語化とは、本来関係がないフェーズです。 しかし、このフェーズで英語化と詳細かを同時に行うことが多く、 「詳細化は英語化」また逆に「英語化は詳細化」という誤解があるのも事実です。
この英語化に対する施策には以下のものがあります。
- 概要設計は日本語で、詳細設計は英語
- 最初から英語のみの名前を使う
- 最初から日本語と英語の名前のペアを使う
- 最初から最後まで日本語の名前を使う
このうち、4 の最初から最後まで日本語というのは、見かけたことがありません。 (3) のものは、UML ツールが IBM/Rational ツールであるときはエイリアス機能が使えます。 また、そうでないときはノートで日本語または英語を記述しているものもありました。
ここでは、このような誤解を恐れずに (1) の英語化と詳細化を同時に行います。
- A 君: 誤解しますって。英語化が詳細化だって。
- G 君: まぁ、こちらも「見栄え=ごまかし」に使いたいから、いいんじゃ。
詳細化方針 2 --- プラットフォーム依存
いままでは、実装については、一切触れずに話を進めてきました。 例えば、実装するプログラミング言語は何か? 使うライブラリは何か? などに 全く触れませんでした。
設計思想の一つに「実装と切り離して設計する」のがあります。 これにより、設計の再利用やデザインパターンなどの 有用な技術に繋がっていきます。
一方、プログラミングするときには、プログラミング言語の決定や使用するライブラリの 決定が必要になります。概要設計では上記よりプラットフォーム独立にします。
もう一つの思想に「決めるのはなるべく後回しにする」というものがあります。 後回しにすることにより、自由度が高まり、変更容易性が高まり、さらに再利用の割合が 増加するという利点があります。
では、いつ、プラットフォーム依存にするのでしょうか?
- このクラスの詳細化で行うのでしょうか
- それともプログラミングフェーズになって初めて行うのでしょうか。
これはクラスの詳細化の「粒度」に依存します。 一番、詳細化が進んだ粒度では、プログラムコードと1対1対応していますので、 プラットフォーム依存になります。
つまり、プラットフォーム依存の詳細化は、詳細化の粒度を決めていくときに 決定できます。
- A 君: え、結局、答えは?
- G 君: 詳細化粒度に依存じゃ。
- A 君: でもアーキテクチャ設計で、プラットフォームは決定するのでは?
- G 君: 気のせいじゃ。概要のクラス設計が終わってからでも Java からアセンブラに変更可能じゃ
- A 君: え、アセンブラってオブジェクト指向でしたっけ。
詳細化方針 3 --- 詳細化の粒度
コード生成をツールで行うときなどは、プログラムと完全に1対1対応する詳細化 が必要になります。 しかしそうでないときは、プログラムコード自身で十分なことと、 逆にツールによって、プログラムコードからこの詳細化の粒度に対応した クラス設計の図を自動生成することも可能です。 このために、ここではプログラムコードと1対1対応する詳細化は行わないようにします。
段階的に詳細化を行う方法もありますが、最終段階の詳細化はどれくらいのものが必要 でしょうか。
端的に言えば、クラステンプレートが「プログラマがそれを見るだけでプログラミングできる」 程度の詳細化が必要になります。 但し、この文の中でプログラマやデザイナについて言及していませんので、 詳細化の粒度の決定には、この文だけではまだ不十分です。
ここでは詳細化の規則 R を以下のようにします。
- 名前を英語にする
- プラットフォーム依存にする。ここでは Java Web System にする。
- クラスはすべて記述する
- インタフェースはすべて記述する
- 汎化はすべて記述する
- 関連、コンポジション、依存は原則記述する
- private 以外の属性はすべて記述する
- 操作は、アクセッサ(セッタ、ゲッタ)は記述しない(原則記述する)
- コンストラクタは、興味深いものだけ記述する(原則記述しない)
- アクセス属性は興味深いものだけ記述する(原則記述しない)
- 引数名は記述する
- 引数のタイプは興味深いものだけ記述する
- 値のタイプは興味深いものだけ記述する
- A 君: え、これだけ。詳細化が弱いのでは。
- G 君: 詳細化はシステム規模に依存するんじゃ。大規模になれば詳細化は弱い
- A 君: 設計はスケーラブルではないの?
- G 君: 気のせいじゃ。設計の詳細度は中規模が詳細で、大規模も小規模も荒いんじゃ。
- A 君: でも大規模ではない。あ、小規模だからか。これなら設計もいらないのでは。
クラスの詳細化
それでは、上記の方針1から3に基づいた詳細化規則 R によって、クラス図を詳細化します。
+--------------------+ +-------------------+ +---------------+
| ProductInfomatioin | | Product | | Record |
+--------------------+ +-------------------+ +---------------+
| name | ← | productInfomation | | records |
| price |has | | | |
| wholesalePrice | | | | |
| date | +-------------------+ +---------------+
| purchaseNumber |
+--------------------+ ↑ ↑
↑汎化 汎化 汎化
+---------------+ +---------------+ +---------------+ +---------------+
| VideoTitile | | Video | | Lending | | LendingLog |
+---------------+ +---------------+ use +---------------+ has+---------------+
| genre | | lending | → | lendingLog | → | video |
| ageRate | | period | | | +-|-member |
| stockNumber | ← | videoTitle | +---------------+ | +---------------+
| lendingNumber | has +---------------+ | lend | | | lend |
| wasteNumber | | lend | | (member,video,| | | (member,video,|
| lendRateAvg | |(member,period)| | period) | | | period |
+---------------+ | waste() | +---------------+ | +----|----------+
| checkAgeRate | +---------------+ | |
| (age):boolean| | ↓
| waste() | | +------------+
| lend() | | | Priod |
| | | +------------+
+---------------+ | | start |
+---------------+ | | end |
| Member |←-------------------------------------------+ | holiday |
+---------------+ +------------+
| name | | period() |
| age | +------------+
| sex |
| address |
| certification |
| reportMail |
+---------------+
↑ 汎化
+---------------+
| RegularMember |
+---------------+
| cost |
+---------------+
新規のクラスとしては貸し出し期間を表す Priod を作成しました。 これを特別にクラスにした理由は、営業休日などの計算をカプセル化するためです。
属性の一部をスーパークラスの方へ移動したものがあります。 新たな操作として、貸し出しの中で年齢チェックを行う操作を追加しています。
ビデオ Video クラスの貸し出し操作 lend の周辺を見れば、貸し出しの 実行が以下のように推測できるかと思います。
Video.lend(member, period)
→ VideoTitle.checkAgeRate(member.age)
→ VideoTitle.lend()
→ Lending.lend(member, video, period)
→ LendingLog.lend(member, video)
それでは少しプログラミングもしてみましょう。 上記の Video クラスを作ってみます。
class Video {
Lending lending;
Period period;
VideoTitle videoTitle;
Video (VideoTitle videoTitle) {
this.videoTitle = videoTitle;
lending = New Lending();
}
void lend(Member member, Period period) {
if (videoTitle.checkAgeRate(member.age)) {
videoTitle.lend();
lending.lend(member, this, period);
} else {
System.out.println("This is XXX for you.");
}
}
}
クラス設計だけでは、 操作の実行に情報をあまり与えていませんので曖昧さがあり、上記はその推測の例です。
- A 君: クラス図の操作と関連だけで作るなんて。推測というより妄想のような
- G 君: これも常日頃、妄想しているからじゃ
- A 君: 途中で色々と追加したでしょ。詳細化という名を借りての仕様変更では?
- G 君: 外部仕様を変更せずのマイクロリファクタリングじゃ。
今日は、レンタルビデオショップのクラスの詳細設計をしてきました。 その中で詳細化の方針や、詳細化の粒度を決めて詳細化をしました。
See you again !!
8日目 ---オブジェクト指向機能の効率 (その方針)
今日は、オブジェクト指向機能の効率について考えてみましょう。
効率の面から、クラス設計にどんな影響を与えるか見ていきましょう。
オブジェクト指向の参考書などでは、「オブジェクト指向機能の効率は『それほど』悪くない」と書いてあるものが多いようです。 実際にサンプル(トイ)プログラムを作って実験して数値を出しているものもあります。確かにそれらを見れば「それほど」悪くありません。そして、オブジェクト指向設計本では、「最初は効率よりも設計の美しさ」と書いてあります。
さらにそれらの本を読み進めていきますと、その本の後半になっても「効率のための(再)設計」についてはあまり触れていません。その頃には読者も忘れてしまっているかも知れません。ここではこのような本をお手本にせずに、効率の面から見たオブジェクト指向設計を考えていこうと思います。
効率を考える点で一番いいのは、オブジェクト指向機能の実装を見てみることです。クラスの実装やインスタンス、継承、委譲の実装を見ることにより、これらが どの程度遅くなるか(または気にすることがないか)が類推できるようになります。
- A 君: なんか、マニアック過ぎません?
- G 君: こういうことも必要なんじゃ。決して効率オタクではないぞ。
- A 君: でも、プログラミングするのに、その言語の実装を思い浮かべるなんて、面倒では。
- G 君: 実装を思い浮かべるのを習性にしてしまえばいいんじゃ。そのためにも言語は1個は作らんと。
レンタルビデオショップの例
レンタルビデオショップをもう一度振り返ります。
「オブジェクト指向機能はそれほど遅くない」という言葉を信じて、 例えば、ビデオ1本1本をクラスにしてみます。これはビデオ「ターミネータ」の12番のビデオをクラスにするというイメージです。この設計は、ビデオの種別ごとにクラスにするという前述した設計と 比較して、メモリフットプリントが大きくなり、その結果、実行速度も悪くなるでしょう。例えば、Java であれば、他で紹介しているフリーの計測ツールで測定してみましょう。
これは、どうしてこうなるのでしょうか。
クラス化のパターン
上記の結果はクラスの実装を考えずに、クラスにするかインスタンスにするかの設計を決定してしまったことにあります。
- A 君: 普通しないでしょ。また極端な例を持ってきて・・・
- G 君: そうではないぞ。昔、辞書を作るときに、そこに出てくる単語一つ一つをクラスにしたのがあったぞ。
- A 君: えー、世界最多のクラスでギネスでも狙っていたんですかね。
Java や C# などのクラスベースのオブジェクト指向機能では、クラスは多大なコストが掛かります。 例えば、空間的コストとして、クラスの情報を保持するためのメモリが必要です。 時間的コストとして、クラスの情報をアクセスするためにはメモリアクセスを何回か実行するコストも掛かります。 これは、「クラスの情報を動的にメモリに保持して、動的にアクセスする」 という仕様とその実装からくるものです。
- A 君: これを言っちゃお終いなのでは。誰もクラスベースのオブジェクト指向なんか使わなくなるんでは。
- A 君: なんか、オブジェクト指向批判者に聞こえますが・・・
- G 君: トレードオフなんじゃ。甘いものには罠があるということを知っておいて罠に嵌(はま)るんじゃ。
これらを考慮すれば、どの程度のものをクラスにするか、直接クラスにせずに共通化したものをクラスにして、対象のものはそれの インスタンスにするかを決めることができます。また、どのパターンのクラス化にするかという設計・実装を選びます。 これらを便宜的に以下のように名づけます。
- 独立クラス型
例. ビデオの1本1本をクラスにする。 - 共通クラス型
例. 「ビデオ」の共通クラスを作成し、ビデオの一つ一つは、そのインスタンスにする。 - 非クラス型
クラスにせずに値型1個かまたは複数の値型のオブジェクトにする。
例えば、クラスのサイズやクラス資源にアクセスする時間の空間/時間コストをまとめて ClassCost、対象のすべてのインスタンスオブジェクトの合計サイズとメソッドアクセスの平均時間からなる空間/時間コストを AllInstanceCost とします。さらに1個の対象インスタンスのみのコストを InstanceCost とします。
これらの値によって、どのパターンにするかを以下にまとめました。
- 独立クラス型
AllInstanceCost と InstanceCost がほぼ同じ大きさで、かつそれらの絶対値が比較的大きい。
例. インスタンスの個数が2個までと少なく、1個のインスタンスのサイズが4以上の大きさを持ち、やや大きい。 - 共通クラス型
AllInstanceCost と InstanceCost に比較的差が有り、かつ絶対値が比較的大きい。
例. インスタンス個数が3個以上で、1個のインスタンスのサイズは4以上の大きさを持つ。 - 非クラス型
各々の絶対値が比較的小さい。
例. インスタンスサイズが3以下である。
上記の例には、3 とか 4 とかの具体的な個数が現れていますが、多人数でプロジェクトを運営するときには、
このレベルの個数も決めておいた方がいいでしょう。そうしないと「先生役」の人はこのような質問を例えばレビューのときにされて、
うんざりになるでしょう。
閾値の決定方法
それでは、上記の例にある閾値(上記の個数をこのように呼びましょう)を決定する方法を示します。
無難な方法は「実験」してみることがお勧めです。 (なお、毎回、実験する必要はありませんので、例えば、同一フレームワークで1回で十分です。)
- A 君: えー、面倒くさいのですが。
- G 君: 毎回毎回、行う必要はなく、感覚を掴むだけでもいいんじゃ。実装を思い浮かべるのと同じじゃ。
- A 君: Java や C# だったら、同じでいいですか。
- A 君: 両者ともクラスベースで、単一継承で、クラスライブラリも似ていますから、同じでもいい?
- G 君: デリゲーションや匿名メソッドがあるから、C# の方が触りがいがあるんじゃが、面倒だからいいじゃろ。
実験は、速度やメモリフットプリントだけでなく、「読解性」「拡張性」もその観点に入れるべきです。 両者は相反することになることも多いですが、トレードオフを取ることにします
- A 君: 読解性や拡張性って、機械的に判断できないのでは。
- G 君: そういうこともある。「美人プログラミング」がいいんじゃ。
- A 君: 宣伝!
また、世の中で公開されているエレガントなプログラムや、教科書に載っているものは、 この閾値の決定の補完として使えます。例えば、ライブラリ系であれば、Java の swing system がお勧めです。また、OSS の中には多くの見るべきプログラムがあります。
今日はここまでになります。実際の効率については、筆を改めてしたいと思います。
![]()
9日目 --- リファクタリング(デザインパターン編)
今日は、設計のリファクタリングを行っていきます。
- A 君: 最初からへたくそなものを作らない方がいい!
- G 君: まぁそれはその、将来が変わるのはあるしの
- A 君: それは隠れ蓑で、実際はへたくそな作りの尻拭いなのでは?
- G 君: 単に作り直しや見直しと言うよりも、リファクタリングと言えば、カッコがつくじゃろ。
リファクタリングの種類と方向
設計のリファクタリングには色々な方法があります。その一例を以下に示してみます。
- クラス間の関係からのリファクタリング
- クラスの粒度/クラス数からのリファクタリング
- ホットスポットの観点からのリファクタリング
1はクラスの質とも言うべき関係からのリファクタリングです。2 はクラスの量からのリファクタリングです。 3 は将来拡張する部分やデータに依存して抽象化しておきたい場所に依存したリファクタリングです。
デザインパターンは 3 のホットスポットの観点からのリファクタリングに使える道具になります。
リファクタリングをするときに、クラス設計を見ていく方向として、以下のものが考えられます。
- トップダウン
クラス設計をトップから見直す方法です。大掛かりになる傾向性があります。 - ボトムアップ
クラス設計をボトムから見直す方法です。共通クラス化などに注意する必要があります。
デザインパターンを用いるリファクタリングは、ボトムアップ的ですが、
見かけ的には「一定の高さを持った山」を探すことになります。
これはツリー的に言えば「一定のリーフを持った枝」に相当するところを対象にします。
ホットスポット
リファクタリングをするためには、リファクタリングの目的を明確にして、それを元に手段を決め、 次にリファクタリングする場所を探します。
今回のリファクタリングの目的は、(1) 将来の変更に備えて抽象化することと、(2) プログラムの読解性を高める ことにします。このための手段としては、デザインパターンのいくつかを採用します。
将来拡張する場所などのような、リファクタリングで注目するべき場所を「ホットスポット」と呼びます。 逆にそれ以外の拡張せずに固定的に使うなど、リファクタリングで注目しない場所を「コールドスポット」 と呼びます。
ユーザ要求の分析をしているときに、どこをホットスポットとし、どこをコールドスポットとするかを発見する のが重要になります。
ホットスポットは多い方がいいように感じるかも知れません。 例えば、以下のように言えるように、システムをホットスポットだらけにするのがいいかも知れません。
「このシステムはどのような拡張に対してもオープンであり、他のプログラムはその拡張に対してクローズドです」
しかし、これは良くありません。この理由はホットスポットを、デザインパターンなどで抽象化することは、以下の結果になります。
「デザインパターンは、プログラムを見にくくします」
これは直感と違うと感じる方がいるかも知れません。
読解性が向上したと見えるのは、デザインパターンを採用した結果、 プログラムの実装手法や、クラス名やメソッド名などの名前そのものまでパターン化した効果によるものです。
デザインパターンや抽象化そのものは、プログラムの複雑度を上げます。 例えば、1回の関数呼び出しでよかったところを抽象化するために途中に関数呼び出しを増加させます。 他の例ではクラス数が増えるのもあります。プログラムは複雑になり、プログラムは見にくくなります。
これは一般論ですべてのパターンがプログラムを見にくくするものではありません。例えば、シングルトンはプログラムを見やすくするでしょう。
- A 君: なんか、プログラムを見にくくするって、過激な事を
- G 君: 単純に考えても、複雑になるから、単純じゃなくなるんじゃ
- A 君: 「見やすさを損なう危険性があります」ぐらいでどうでしょう
「デザインパターンはプログラムを遅くします」
効率についても読解性と同じことが言えます。効率と読解性の面からもホットスポット設計は重要になります。
つまり、これらから、ホットスポットとコールドスポットのトレードオフを見つけることになります。 これが結果として、デザインパターンの適度な採用になります。
もう一度、繰り返しますが、デザインパターンを多用することが目的でありません。 また、システムの多くの場所を拡張可能にするためではありません。
つまり、全体のバランスを考え、目的や問題に応じて、ホットスポット単位に適用するデザインパターンを 検討することになります。
多用には注意することを書いてきましたが、しかしそれをやや勧める「デザインパターンの適用パターン」があります。
- インタフェースやデリゲーション、インヘリタンス、コンポジションは比較的多用する
これらの使い分けは後述します。 - 動的な機能クラス(制御クラス)は生成系デザインパターン(factory, prototype, builder など)を使う
どの生成系パターンがいいかは後述します。 - 状態が多くなったら、状態管理系パターン(state, command, interpreter など)を使います。
どの状態系パターンがいいかは後述します。 - その他
その他の「デザインパターンの適用パターン」は別途書くようにします。
これがデザインパターンを適用するパターンになります。 インタフェースやコンポジションの多用に関しては、それほど違和感は少ないかと思います。 しかしインヘリタンスの多用には異議を申し立てる人も多いかも知れません。 ここでは、他の差分化が失敗して、かつ always に is_a 関係があるときにのみ、多用するという意味です。 多少、違和感が減ったかと思いますが、どうでしょう。
デザインパターン本では、これらは「商売柄」、やや多目に適用するように書かれていますので、「真に受けずに」適当に適用するようにしてください。
デザインパターンの適用アンチパターン
前節では、デザインパターンの適用パターンのいくつかを紹介しました。 しかし、デザインパターンを学んだ直後には、多くの種類のパターンを無闇矢鱈と採用することがあります。 そこで、適用パターンを論じるのではなく、適用してはいけないパターン、つまり、適用アンチパターンを 紹介することで、その戒めとすることにします。
- インヘリタンスを用いた階層化インタフェース
もし階層化 API を作成する目的で、これを使おうとするのであれば、 階層間(インタフェース間)の関係はデリゲーションを使うことを勧めます。
経験から、これは「例外処理」があるために階層間に is_a 関係が always でない場合が多いと思うからです。
さらに将来パフォーマンスチューニングを行うときに、インヘリタンスでは途中の階層を省略する「中抜け」テクニックを使いにくくします。一方、デリゲーションは比較的容易に中抜けをすることができます。 - レベルが異なる生成系パターン
複数のオブジェクトを持つ生成系パターン(abstract factory, builder など)が、静的オブジェクト(ファシリティ)や 動的オブジェクト(エンティティ)が混合しているなどレベルの異なるオブジェクトを同時に持つようにしないことを 勧めます。
生成系クラス(factory, creator など)によって、一つのグループにすることにより、意味がないのに意味を生じさせて、 それを元にプログラムを読むことを困難にさせるからです。
対策は別の生成系パターンにして、グルーピングしてから、まとめる方法も考えられますが、ファシリティとエンティティは一緒にしないのが無難です。 - 複雑すぎるステートパターン、単純すぎるステートパターン
状態が多くなったときに使うステートパターンですが、これは状態数と状態遷移の複雑さによって、使い分ける必要があります。
複雑すぎる状態を単純なステートパターンを使うと、プログラムは意味不明になります。ステートを階層化する、グルーピングする、グラフ化するなどで状態の複雑さを局所化します。
一方、2値で使う人もいないでしょうが、4個の状態で使うのは、プログラムをいたずらに複雑にするだけです。一定個以上のときに使います。また状態遷移がスター型のときは使わなくてもいいでしょう。メッシュ型のような複雑なときに使います。 - 入れ子になったパターン
デザインパターンを適用するホットスポットが入れ子状態になっているときに別々にパターンを適用している。
独立しているパターンであるときには複雑さ以外の問題はないが、独立ではない場合が多い。
他のアンチパターンもあります。デザインパターンの種類よりも多いでしょう。デザインパターンは定理のようなものであるのに対し、アンチパターンは反例のようなものになります。
- A 君: うわ、これは、デザインパターンにいちゃもんを付けているみたい
- G 君: いちゃもんを付けられるということはじゃ、見込みがあるということで喜ぶべきじゃ
- A 君: そうかな・・・そうなんでしょうねぇ・・・そうだよね・・・そうなんだ
デザインパターンの適用方法
次にデザインパターンの適用について考えます。今までに書いてきたことをまとめてみます。
- リファクタリングの目的を知る
- ホットスポットの候補を探る
ホットスポットによっては、他のホットスポットを一部または全部を含んでいる場合もある - コールドスポットの候補を探る
- ホットスポットとコールドスポットを設計する
- ホットスポットに適用するデザインパターンを発見する
既存のパターンから適当なものを探す - (参考)目的と合致するパターンがないときは、新規に作成する
健康のためにデザインパターンの使いすぎには注意しましょう。しかし、デザインパターンを知らなくて使わないのは賢明とは言えません。楽しいリファクタリングになるためにも、「名前の付いた」パターンを適用していくことはいいことでしょう。
- A 君: 何気にまとめようとしている
- G 君: 実際のプログラムに適用しない限り、絵に描いた煎餅じゃ
- A 君: 煎餅よりも羊羹を。適用方法よりも実際のプログラムの適用を。
![]()