#追記(2017/11/26)
この記事で紹介している内容を使って、集約やコンポジションの実装を補助するクラスを作りました。
UMLの関連を分かりやすく実装するクラス(C#)をどうぞ。
#追記(2020/03/03)
集約のコードの中に、コレクション要素に追加/削除するときのチェックについてコメントを入れました。
#集約とコンポジションの使い分け
クラス図で、「白抜きのひし形(集約)」と「黒塗りのひし形(コンポジション)」をどのように使い分けたらよいのか? 悩んだ経験はありませんか?
集約とコンポジションの違いを誤解している人がいるようですので、同じ誤解をしている人のために記事にします。
モデリングだけだと、ピンとこない人のために、サンプルコードも付けました。
#UMLをぼんやり知っているOさんの答え
白抜きのひし形は「has-a」=「所有している」という意味。
黒塗りのひし形は「part-of」=「~の一部」という意味。
だから、「人は車を所有する」は、白いひし形で表現し、「タイヤは車の一部」は、黒いひし形で表現する。
これは、「part-of」が「has-a」と呼ばれることがあるために起きている誤解なのかも知れません。
#正しい意味
まず、「ひし形」自体が「全体と部分」を表現しています。
つまり、白抜きも黒塗りも、ともに「全体と部分」=「part-of」です。
白抜きは、全体と部分としての結びつきが緩やかで、
黒塗りは、全体と部分としての結びつきが強い、
という違いです。
##白抜きのひし形(集約)の正しい意味
白抜きのひし形(集約)は、UML1.3のときは「共有集約」と呼ばれていました。
これは、複数の「全体」インスタンスが、1つの「部分」インスタンスを共有する
という意味です。
これが、全体と部分としての結びつきが緩やか、ということです。
「集約」と呼ばれるようになってからも、この意味自体は変わっていません。
例えば、「図形」には「スタイル(※線の色や図形の塗りつぶし)」があり、これは全体と部分とみることができます。
1つの「スタイル」インスタンスを、複数の「図形」インスタンスが共有しています。
ちなみに、SysMLでは「Shared Association(=共有関連)」と呼びますね。(2021/9/24 追記)
##黒塗りのひし形(コンポジション)の正しい意味
黒塗りのひし形(コンポジション)は、共有集約と違い、「部分」が複数の「全体」に共有されません。
つまり、唯一の「全体」インスタンスが、1つの「部分」インスタンスを所属させます。
また、「部分」は「全体」と生存期間を共にすると考えられています。
そのため、「全体」側を削除すると「部分」側も連鎖的に削除されます。(2021/12/22追記)これをライフサイクル制約と呼びます。
ただし、全体を削除する前に「部分」を取り外すことで、連鎖的な削除を免れることは可能です。
例えば、「車」と「タイヤ」は「全体と部分」の関係で、1つの「タイヤ」インスタンスは、1つの「車」インスタンスにしか属すことができません。
車を廃車にすると、タイヤも壊されてしまいます。
ただし、車からタイヤを外してから廃車にしてしまえば、タイヤは別の車に付け替えることもできます。
ちなみに、SysMLでは「Part Association(=部品関連)」と呼びますね。(2021/9/24 追記)
#誤解していた「所有」はどう描くのか?
いわゆる「所有」、
人(飼い主)は、犬を飼っている
社員は、社員証を持っている
は、ただの関連です。
「所有」だと ひし形のような何か特別な記号で描く、ということ自体が誤解です。
#C#サンプルコード
最後に、集約とコンポジションのサンプルコードを書いてみます。
##集約
Hoge(全体)がPiyo(部分)を複数含んでいます。
部分をprivate List<Piyo> m_piyos
で保持して、AddとRomoveだけをHoge経由で公開しています。
わざわざprivateなのは、
・List自体を外部から変更されないようにする
・Hogeが知らないところでm_piyosの中身を変えられないようにする
ためです。
車とタイヤの例で考えてみると、m_piyosを外部から = new List<Piyo>();
されたら、
タイヤのシャフトのあたりを、他の空間と交換されてしまうような感じですよね。
また、Hogeが知らないところでm_piyosの中身を変えるということは、
車が街中を走っている途中で、突然、工場からタイヤを交換することができてしまったりする感じですよね。
もし、こんな世界観でプログラミングしていたら、不具合だらけですよね。
だから、モデリングで概念上の世界観を可視化したら、その世界観をコーディングで崩さないように気を付けて(街中を走行中に工場からタイヤを交換出来ないような)コードで実装するのです。
なお、Hogeのpublicメソッド名がAdd
ではなくAddpiyo
なのは、複数の「部分」を扱うことがあるためです。
class Hoge
{
// 集約
private List<Piyo> m_piyos = new List<Piyo>();
public Hoge()
{
}
public void Addpiyo(Piyo piyo)
{
// チェックが必要ならば、この辺でする。
// 車とタイヤの例ならば、「車」クラスが「走行中」状態のときにAddされないようにガードするとか。
m_piyos.Add(piyo);
}
public bool Removepiyo(Piyo piyo)
{
// Addと同じく、チェックが必要ならば、この辺でする。
return m_piyos.Remove(piyo);
}
}
class Piyo
{
// 特になし
}
##コンポジション
基本的には集約と変わりませんが、全体側を削除したときに部分側を削除する点が異なります。
ただ、C#はガベージ コレクションを使うので、それに任せるならば、集約と同じ実装で良いと考えます。
一応、Disposeで削除できるだけ削除するサンプルを書いてみました。
Disposeの実装方法については、下記の記事を参考にさせて頂きました。
class Hoge : IDisposable
{
// コンポジション
private List<Piyo> m_piyos = new List<Piyo>();
public Hoge()
{
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
foreach(Piyo piyo in m_piyos)
{
piyo.Dispose();
}
m_piyos.Clear();
m_piyos = null;
}
}
~Hoge()
{
Dispose(false);
}
public void Addpiyo(Piyo piyo)
{
m_piyos.Add(piyo);
}
public bool Removepiyo(Piyo piyo)
{
return m_piyos.Remove(piyo);
}
}
class Piyo : IDisposable
{
// Disposeなど、削除に必要なものを実装
}
以上のサンプルコードでは、「部分の削除」を入れましたが、
あと、もう一つのコンポジションの特徴である「複数の全体に共有されないこと」の実現を追記したサンプルコードは、
別の記事コンポジションのサンプルコード(「クラス図の書きかた(集約とコンポジションの意味の違い)」の補足) に記載しました。
そちらもご覧ください。
#偉そうなクラスがコンポジションではないです
(2020/4/9 追記)
なんとなく、主導権を持っているほうや、管理している側が、コンポジションだと思って
モデリングしてしまう生徒さんを見ます。
いくら注意しても、なかなか直らないです。
そんな例を。
こういうのは、一見、問題なさそうに見えるけれど、
概念的に捉えると、結構おかしいです。
この図では、
「登録ユーザー」という概念は、「システム」という全体概念の一部を構成する概念だ、
と描かれています。
ゆえに、「システム」が消失すると「ユーザー」もその一部だから消失する、
という意味です。
ソフトウェアシステムを作っている人の頭で考えたら、おかしくなさそうだけれども、
人間が普通に持っている世界観からしたら、気が狂っていると思いませんか?
(2021/12/22追記) 補足です。
「普通に持っている世界観からしたら、おかしい」
これは、純粋にオブジェクト指向分析的に考えたらおかしいと考えられます。
このモデルを拡張したり変更したりすると、不都合が生じる恐れがあります。
たとえば、システム側が複数になったりとかしたケースです。
たとえばシステムが、あるスーパーの店舗のネットスーパーシステムだとします。
最初は、試験的に1店舗で始めたとします。この時にコンポジションにしていると、
その後、複数店舗展開したときに、登録ユーザーが複数店舗利用できなかったり、無理に対応すると、店舗閉店時に他店舗にも登録しているユーザーのインスタンスが消えたりする、不都合が生じる恐れが出てきます。
しかし、これがコンポジション(で一緒に消滅するの)ではなく、ただの集約ならば、そうはならなそうです。
こんな風に、現実世界とリンクしていると、ご利益があるのです。
だけれども、これがシステム開発に限定して、かつ、そのシステムは拡張する気がなく、システムを削除したら一緒に登録ユーザーも消滅すべきと考えているのであれば、それは否定できません。なぜなら「むしろ、それを表すために意図的にコンポジションを使っている」モデルだからです。
こちらの図では、
「ログオン先のシステム」というのは「ユーザー」という全体概念の一部を構成する概念だ、
となっています。
「ユーザー」が消えると、「システム」の実体も消えてくれる。
ユーザー登録を変えたら、「システム」が消えちゃうのかなぁ?
と思います。
普通の人が持っている世界観からしたら、変だと思いませんか?
これで、普通の人とシステム開発について話しようだなんて、、、。
、、、でも、こんなモデルでおかしいと感じない人だって、
次のモデルを見たら、なんか「やばい」って、感じるかも知れません。
「こども」は「お父さん」の一部分であって、
「お父さん」が死んだら、「こども」はその一生を共にする(つまり、一緒に死ぬ)ものだ、
というモデルですね。
絵的に言うと、↓な感じ
寄生獣 amazon.co.jp より
先ほどの「システムとユーザー」をコンポジションにしている人は、これと同じことをしているのですよ...。
こんなモデル描いてたら、
一般的な概念を持ったユーザーとお話ししていても、ユーザーに寄り添った考え方ができないですね...。
「だって、システムはこうなっているんですから!」
「だって、仕様ですから!」
って、自分の考えかたのほうが不自然だってことに
気づかなくなってしまうのかも知れませんね。
#人によって認識が違うこともあります(2021年12月22日追記)
全体と部分関係には、人によって、集約やコンポジションが適切だと捉える場合と、関連が適切だと捉える場合と、分かれるケースがあります。
よくある具体例は、組織と人の関係です。
たとえば、バンドとメンバー。バンドは全体でメンバーは部分でしょうか?
それとも、バンドとメンバーは対等な関係でしょうか?
たとえば、会社と社員の関係。会社は全体で社員は部分でしょうか?
それとも、会社と社員は対等な関係でしょうか?
これらは、人の認識によって違う、が、私の提供する答えになります。
ですので、組織と人との関係を、コンポジションや集約で描くことを全否定しないようにしましょう。
※会社側をコンポジションにしているのは、会社が消滅するときに社員が逃げないと社員も消滅してしまうという意味で、コンポジションにした例です。
#参考
『オブジェクト指向システム開発 分析・設計・プログラミングへの実践的アプローチ』
本位田 真一・山城 明宏 著 日経BP出版センター
『UMLモデリングのエッセンス 第2版 標準オブジェクトモデリング言語入門』
マーチン・ファウラー/ケンドール・スコット 著 羽生田 栄一 監訳 翔泳社
『C# によるプログラミング入門 [メモリとリソース管理] [雑記] Dispose にまつわる余談』
#その他
どのように考えながらUMLモデリングしているかを声に出しながらモデリングする
モデリングライブを行いました。UMTPのホームページに動画が載っていますので、もしよろしければどうぞ。
https://umtp-japan.org/activity-report/10409
Qiitaに載せるには、ややフランクだったり、アイディアレベルの記事はこちら
https://ameblo.jp/azukizenzai8/
他にお役立ちそうなモデリングの記事
https://www.mamezou.com/techinfo/modeling_ddd/uml_miss_01