#はじめに
この記事は、UMLの「汎化関係」についての説明です。
... なんとなく、お客様の成果物をみていて急に書きたくなったので、書いてます。
UMLの記号で描くと、これです。
図1
でも、これって、皆さん、「継承」って呼んでますよね。多分...。
開発現場を見ていると、これって結構、誤解されていると感じるので、その辺について、
書きたいと思います。
#そもそも汎化関係とは
そもそもなのですが、上述の記号の名前は、「継承」ではなく「汎化関係」です。
UML仕様書では、継承ではありません。
汎化関係の意味は、要するに分類です。
上述の図で言えば、「Class2はClass1の一種である」と読み下します。
ちなみに、汎化関係は、汎化関係であって、抽象化が正しいとか、具象化(派生)が正しいとか、そういったことはありません。ただ、概念の関係性です。
例えば、次の図を読む下すと「携帯電話は電話の一種である」です。
図2
ただ、概念の分類をしているだけで、何がうれしいのか? って思いますよね。
#汎化関係のうれしさ
まあ、うれしさがなかったら、そもそもモデリングなんてしませんよね。
なので、うれしさを確認します。
##派生開発ができる
まず、分かりやすそうなのは、派生開発ができる。
汎化関係を派生開発時に利用すると、便利ですね。この場合、「派生クラス」とか言われたりしますね。
「電話」クラスに分類される「携帯電話」クラスを開発するならば、「電話」クラスの属性や操作を引き継いで、差分だけ開発すればよくなる。
##派生製品がたくさんできてしまってコードメンテの横展開に追われている製品群のコードを整理するときに便利
実務上、すでに、顧客向けにたくさんの派生を作ってしまっていて、管理しきれなくなってしまったときに、
それらの製品群を分類しなおして、派生構造を整理して、整理すると良いですね。
普通だと、きれいにやるのが難しいです。
なので、整理したクラス図内に、整理前のコードから処理コードを引き抜いてきて再配置するのが現実的です。
元のコードの構造を活かすのは無理があることが多い気がします。
整理した後は、同じ不具合修正をあちこちに入れないといけないとか、そんなことがなくなって、うれしいですね。
##モノゴトの本質を抽出できる
自動ドアに赤外線センサで人を検知しようとしていると、こんな風に書いてしまいがち。
図3
だけれど、これでは、あとあと、センサの廉価対応とか、赤外線では難しいものを検知する機能改善とかでの影響範囲が広くなってしまう。
// 赤外線センサ
class InfraredSensor {
}
// 自動ドア
class AutoDoor {
// もし赤外線センサではないものにしようとしたら、「自動ドア」クラスのコードも変更が入る
InfraredSensor OutsideDetectionSensor = new InfraredSensor(); // 外側人感センサ
InfraredSensor InsideDetectionSensor = new InfraredSensor(); // 内側人感センサ
}
なので、自動ドアを語る文脈の抽象度の言葉との汎化関係を結ぶと、こんな感じになり、実は「人感センサ」のことなんだな、と気づきます。
図4
// 人感センサ
abstract class HumanDetectorSensor{
}
// 赤外線センサ
class InfraredSensor : HumanDetectorSensor {
}
// 自動ドア
class AutoDoor {
public AutoDoor(HumanDetectorSensor outsideSensor, HumanDetectorSensor insideSensor){
OutsideDdetectionSensor = outsideSensor; // 外側人感センサ
InsideDdetectionSensor = insideSensor; // 内側人感センサ
}
// もし赤外線センサではないものにしようとしたら、「自動ドア」クラスのコードも変更が入る
HumanDetectorSensor OutsideDdetectionSensor = null; // 外側人感センサ
HumanDetectorSensor InsideDdetectionSensor = null; // 内側人感センサ
}
// なんかいい加減だけれど、自動ドアを生成するところ
class Main{
AutoDoor autoDoor = new AutoDoor(new InfraredSensor(), new InfraredSensor());
}
さらにもう一段気づいて、つぎのような感じになるとベターですね。
図5
##で、汎化関係は、より抽象的なクラスの特徴(属性や操作など)を受け継ぐ
当たり前なんですけど、「Class2はClass1の一種である」の関係である以上、Class1側の持つ性質をClass2が持ちます。
そうでないと、Class2はClass1の一種である、とは言えませんものね。
うれしさも他にもあるとは思うのですが、ひとまず、流れのままに書いているので、流れます。
まあまあ、やや軽い感じですが、汎化関係の概要、ざっくりと、こんな感じです。
#汎化関係は、文脈上の分類であること
これ、ちょー重要です。
上のほうに、汎化関係がくれるうれしさについて書きましたが、
概念の分類が、開発対象のドメイン(=関心対象分野/領域)のコンテキスト(=文脈)上での分類でなかったら、
何にもうれしくなくなります。
例えば、電話を開発しているのならば、図2のように、電話そのもののドメインと電話を利用するコンテキストにおいての分類でないとなりません。
これが、電話を利用してもらうために開発している人たちが、
次のような分類をしてしまったら、何にもうれしいことはありません。
図6
いや、まあ、確かに間違ってはいないんだけれど、文脈に合ってない。
「モバイル機器」って抽象度のことに価値があって、それの派生開発として、様々なバリエーションを開発してゆくビジネスなら、これでいいですが、たぶん違いますよね。
「モバイル機器」って抽象的な概念そのものに、独立した、かつ横展開できる価値があるとは、あまり想像できません。
だいたい、概念モデル描いたところで、モデルベース開発を断念する人は、ここで失敗しているケースもあります。
または断念しなかったけれど、概念モデル描いて、オブジェクト指向のモデル描いてプログラミングしたのに、
オブジェクト指向開発なんてオワコンだね、って言っている人も、ここで間違っていたりします。
#注意1) 実は関連も受け継がれる
上の図5って、「自動ドア」クラスと「人感センサ」クラスの間に関連がありますよね。
実は、図5の「赤外線式人感センサ」クラスって、この関連自体も受け継いでいます。
なので、「自動ドア」クラスと「赤外線式人感センサ」クラスの間にも関連を引いてしまうと、重複になってしまいます。
図7
実際、プログラミングでも、この関連を実装してしまったら、重複しますね。
#注意2) 派生しているクラス同士に変化できない
図8
たとえば、ウーバーイーツみたいなシステムを考えます。
「会員」には、「利用者」と「配達員」がいるとします。
分類のモデリングとして良さそうに見えます。
ここに落とし穴があります。
汎化関係では「利用者」と「配達員」は、それぞれ「会員」から派生した「それぞれ異なる型」です。
たとえば「利用者」クラスのインスタンスである田中さんと、「配達員」クラスのインスタンスである山本さんがいると、
いくら同じ「会員」クラスの特徴を受け継いでいるとはいえ、田中さんが「配達員」クラスのインスタンスになることはできません。
図9
開発のスコープが、利用者と配達員に同じ人がなることはない、という範囲であれば、汎化関係で良いですが、
開発のスコープが、利用者は配達員にもなれる、という範囲だとすると、結構問題があります。
同一人物が、「利用者」としても「配達員」としても、氏名と連絡先を登録しなければなりません。
「会員」というデータがあるわけではないので、同一人物の情報を結び付けようとすると、新たに会員IDを振って関連づけるなどしてゆかなければなりません。
連絡先の変更も同一人物の「利用者」「配達員」の両方同時に変更する必要がありますね。検索して直さないといけなかったりすると、変更漏れとか出たら大変ですね。
図10
図10のよう汎化関係を使わずに、「利用者」や「配達員」を役割として捉えると、一度会員登録すれば、利用者としての追加登録も配達員としての追加登録もできるし、連絡先が変わったときも1か所直せば済みますね。
#継承との違い
そうそう、この説明を忘れていました。
汎化関係は、関係性のことです。
継承とは、汎化関係により、性質が引き継がれることです。
プログラミング言語の「継承」は、この性質を引き継ぐことができる「言語上の仕組み」を示しています。
言葉を使い分けてもらうと、うれしいです。
#注意事項
この記事のクラス図は、汎化関係の説明に着目させるために、一部の多重度や関連端名を省略しています。
※2023/03/26 コードを追記していたので、タイトルを【コードでわかる..】シリーズに変更しました。