UML多重度編

>100 Views

October 01, 26

スライド概要

profile-image

はじめまして、yukikoと申します。 IT教育支援や、DX推進が可能です。 ◆ スキル LPIC レベル2 AI / Python Splunk BI(データ可視化・分析) ◆ その他 新卒・未経験の学生向けに、エンジニア転職を応援する資料を趣味で作成しています。 もしよろしければご活用ください。

シェア

またはPlayer版

埋め込む »CMSなどでJSが使えない場合

(ダウンロード不可)

関連スライド

各ページのテキスト
1.

シ リ ーズ 第 3部 : 数字 が 語 る 設 計の 制 約 UML多重度編 「1」と「0..*」を読み解く 1 0..* 線の両端の小さな数字が、コードとデータを決めている 査読済み実証研究 × OMG公式仕様 × Python公式ドキュメント に基づく構成 うさうさ研修工房 | 監修:うさうさ先生

2.

ROADMAP この資料の歩き方 C H AP T E R 1 C H AP T E R 2 C H AP T E R 3 多重度の読み方 Pythonでの表現 多対多と関連クラス 記法一覧/どちら側の数字か 1 / 0..1 / 0..* / 1..* / n..m 双方向参照/中間クラス C H AP T E R 4 C H AP T E R 5 C H AP T E R 6 落とし穴と設計 総合演習 根拠と早見表 可変デフォルト引数/集合の選び方 受講申込システムを設計する 査読研究/公式リンク/チートシート 多重度は「線の飾り」ではありません。ここを読み違えると、実装もデータベースも丸ごと作り直しになります。 うさうさ研修工房|UML多重度編 2

3.

C HAPT ER 1 多重度の読み方 線の両端にある数字は、何を数えているのか

4.

WHAT IS M ULTIPLIC ITY 多重度 —「何個と関係できるか」を示す数字 Customer 1 0..* + name : str Order + order_id : str この2つの数字が「多重度(multiplicity)」。関連の両端に、それぞれ独立に付きます。 最小値 .. 最大値 0..* なら「最小0個・最大は無制限」の意味 数字ひとつなら最小=最大 1 は「ちょうど1個」、つまり 1..1 の省略形 * は「上限なし」 0..* は * と省略して書かれることもある うさうさ研修工房|UML多重度編 4

5.

NOT ATION T AB LE 多重度の記法一覧 記法 意味 読み方の例 Pythonでの表現 1 ちょうど1個(必須) 注文には必ず1人の顧客がいる self.x = x(必須引数) 0..1 0個または1個(任意) 会員に配偶者情報があれば持つ self.x: X | None = None 0..* 0個以上(制限なし) 顧客は注文を1件も持たなくてよい self.xs: list[X] = [] * 0..* の省略形 上と同じ意味 self.xs: list[X] = [] 1..* 1個以上(最低1個は必須) 注文には必ず1つ以上の明細がある list + 空チェック 2..5 2個以上5個以下 チームは2〜5名で構成する list + 範囲チェック ※ 多重度は OMG UML 2.5.1 仕様の MultiplicityElement として定義されています(詳細は公式仕様を参照)。 うさうさ研修工房|UML多重度編 5

6.

WHICH END COUNTS WHAT 最大のつまずき —「どちら側の数字か」 Customer 1 0..* Order ○ 正しい読み方 × ありがちな誤読 Order 側の「0..*」は、 『1人の Customer から見て、Order は0件以上』 「Order が0..*個ある」と単独で読んでしまう。 どちらから見た数なのかが抜け落ちる。 覚え方:数字は「遠い側のクラスから見た個数」。自分の隣にある数字ではなく、線の向こうにいる相手から自分を数える。 この1枚の読み違えが、そのまま「リストにすべき所を単一の属性にしてしまう」実装ミスに直結します。 うさうさ研修工房|UML多重度編 6

7.

C HAPT ER 2 Pythonでの表現 数字ごとに、書くべきコードは決まっている

8.

EXACT LY ON E 「1」— 必ず1個。省略を許さない Order 0..* 1 Customer 設計上の意味 「1」は“無くてもよい状態を作らせない”という宣言です。顧客のいな い注文は、そもそもインスタンスとして存在できない形にします。 必須にする書き方 class Order: def __init__(self, customer: Customer): # 引数にデフォルト値を付けない # = 省略できない=必須 self.customer = customer Order(customer=c) Order() # OK # TypeError # 必須漏れが実行時に即わかる ポイント:デフォルト値 None を付けた瞬間、それは多重度「1」ではなく「0..1」の実装になります。 うさうさ研修工房|UML多重度編 8

9.

OPTION AL: ZERO OR ONE 「0..1」— あるかもしれないし、ないかもしれない Member 1 0..1 Coupon 使う側の責任が増える 0..1 にすると、参照するたびに None チェックが必要になります。「無い 」という状態を全員が扱わねばならない、というコストを受け入れる判断 です。 任意にする書き方 from typing import Optional class Member: def __init__(self, coupon: Optional[Coupon] = None): self.coupon = coupon # None を許す # 毎回これが必要になる if member.coupon is not None: price -= member.coupon.amount Python 3.10以降は Optional[Coupon] を Coupon | None とも書けます(公式:typing)。 うさうさ研修工房|UML多重度編 9

10.

ZERO O R MANY 「0..*」— 0個以上。空のリストから始める Customer 1 0..* Order 空がふつうの状態 0..* では「1件もない」が正常です。登録直後の顧客は注文0件。空リスト をエラー扱いしないこと、そして None ではなく [] で初期化することが 要点です。 0..* の書き方 class Customer: def __init__(self, name: str): self.name = name self.orders: list[Order] = [] def add_order(self, order: Order): self.orders.append(order) うさうさ研修工房|UML多重度編 c = Customer("うさうさ") len(c.orders) # 0 は正常 for o in c.orders: ... # 0件なら # 単に回らない 10

11.

ONE OR MANY 「1..*」— 1個以上。空を作らせない仕掛けが要る Order 1 1..* OrderItem ここが落とし穴 0..* との違いは「最小値1」だけ。しかしコード上は、生成時の チェックに加えて“削除時のチェック”も必要になります。片方 だけだと多重度が守られません。 1..* の書き方 class Order: def __init__(self, items: list[OrderItem]): if not items: raise ValueError("明細は1件以上必要です") self.items = items def remove_item(self, item): if len(self.items) == 1: raise ValueError("最後の1件は削除できません") self.items.remove(item) うさうさ研修工房|UML多重度編 型では表せない list[OrderItem] という型は「空でないリスト」を表現できませ ん。多重度の下限は、型ではなく実行時の検証で守ります。 11

12.

BOUN DED RANGE 「2..5」— 上限と下限の両方を持つ Team 1 2..5 Player 数字は定数にする 2 や 5 をコードに直接書くと、仕様変更のたびに散らばった箇 所を探すことになります。図に書かれた多重度は、そのまま名 前付き定数にしておきます。 範囲チェックの書き方 MIN_PLAYERS, MAX_PLAYERS = 2, 5 class Team: def __init__(self, players: list[Player]): self._validate(players) self.players = players @staticmethod def _validate(players): if not MIN_PLAYERS <= len(players) <= MAX_PLAYERS: raise ValueError("メンバーは2〜5名です") うさうさ研修工房|UML多重度編 検証は1か所に集める 生成時・追加時・削除時のすべてから同じ検証メソッドを呼ぶ 形にすると、多重度の定義が1か所に留まります。 12

13.

C HAPT ER 3 多対多と関連クラス 「両側が * 」のときに何が起きるか

14.

ONE-TO-MANY, B OTH D IR ECTIONS 1対多 — 両方向から辿れるようにするか Customer 1 + orders 双方向の同期 Order 0..* + customer 双方向にする代償 class Customer: def add_order(self, order: "Order"): self.orders.append(order) order.customer = self # 相手側も更新する 「顧客→注文」と「注文→顧客」の両方を持つと、追加・削除 のたびに2か所を更新する義務が生まれます。片方を忘れた瞬間 にデータが矛盾します。 # 片側だけ更新すると、両者の言い分が食い違う 本当に両方向から辿る必要があるか、先に確かめてください。 実務では、更新用のメソッドを1つだけ公開し、そこから両側を必ず書き換える形にするのが定石です。 うさうさ研修工房|UML多重度編 14

15.

MANY-TO-MANY & AS SOCIATION CLASS 多対多 — 関係そのものが情報を持ち始める そのまま多対多にすると Student 置き場所がない 0..* 「いつ申し込んだか」「成績は何点か」は、Student のものでも Course のものでもありません。両側が * のとき、関係そのものが属性を持ち始 めます。 Course 0..* 中間クラスに分解する Student 1 Enrollment 0..* 0..* 1 Course + date + grade 多対多が「1対多」2本に分かれ、日付や成績の置き場所ができる enrollment.py class Enrollment: # 関連そのものをクラスにする def __init__(self, student: Student, course: Course, date: str): self.student = student # 1 self.course = course # 1 self.date = date # 関係が持つ情報 うさうさ研修工房|UML多重度編 15

16.

C HAPT ER 4 落とし穴と設計判断 Python特有の罠と、集合の選び方

17.

TH E MUT ABLE DEFAULT T RAP 0..* で最も多い事故 — 可変デフォルト引数 × やってはいけない class Customer: def __init__(self, orders=[]): self.orders = orders ○ 正しい書き方 class Customer: def __init__(self, orders=None): self.orders = orders or [] 何が起きるのか デフォルト値の [] は関数定義のときに一度だけ作られ、以後すべてのインスタンスで同じリストが共有されます。つまり、A社の注文を追加したつもりが、B社 の注文一覧にも現れます。多重度 0..* を実装したつもりが、全インスタンス共有の1本のリストになってしまう事故です。 a, b = Customer(), Customer() a.orders.append("注文1") print(b.orders) # ['注文1'] ← 別の顧客なのに混ざる うさうさ研修工房|UML多重度編 17

18.

CH OOSING THE C OLLECTION 「多」をどの型で持つか — list / set / dict 型 重複 順序 向いている多重度 例 list 許す 保つ 順番に意味がある 0..* 注文明細の並び set 許さない 保たない 重複してはいけない 0..* タグ、権限 dict キーは一意 挿入順を保つ キーで引きたい 0..* 商品ID → 在庫数 UMLで区別したいとき 判断の順序 順序を保つことを明示したい場合は関連端に {ordered}、重複を許さない場合は {unique} と書きます。無指定は「順不同・重複なし」が既定です。 まず「重複してよいか」、次に「順番に意味があるか」。この2つが決まれば型は 自動的に決まります。迷ったら list から始めて構いません。 うさうさ研修工房|UML多重度編 18

19.

WHEN MULTIPLICITY CH ANGES 多重度が変わると、何が壊れるか 仕様変更:「1人の会員が持てる住所は1件」→「複数登録できるようにしたい」(1 → 1..*) クラスの属性 self.address → self.addresses(単数形が複数形になる) 参照している全箇所 member.address.city のような記述がすべて書き換え対象になる データベース 列として持っていたものを、別テーブルに切り出す必要が出る 画面・帳票 1件前提のレイアウトが破綻する。「どれを代表として出すか」の仕様が新たに要る だから多重度は、図を描く段階で関係者に確認しておく価値があります。「今は1件だが将来増えるか」を最初に聞くだけで、後の手戻りが大きく減ります。 うさうさ研修工房|UML多重度編 19

20.

C HAPT ER 5 総合演習 受講申込システムの多重度を決める

21.

CAS E STU DY: DIAGRAM 総合演習 — 研修の受講申込システム Student 1 0..* + name Enrollment 0..* + applied_at Course 1 + title 1..2 Trainer + name 決めた多重度とその理由 Student → Enrollment 0..* 申込0件の受講者もいる(登録だけ済んだ状態) Enrollment → Course 1 申込は必ず1つの講座に紐づく Course → Trainer 1..2 講師は1名、または2名の共同開催まで うさうさ研修工房|UML多重度編 21

22.

CAS E STU DY: C ODE 多重度をそのまま守るコードにする enrollment.py course.py class Student: def __init__(self, name: str): self.name = name # 0..* → 空リストで開始 self.enrollments: list["Enrollment"] = [] MIN_TRAINERS, MAX_TRAINERS = 1, 2 class Enrollment: def __init__(self, student: Student, course: "Course", applied_at: str): # 1 → デフォルト値なし=必須 self.student = student self.course = course self.applied_at = applied_at student.enrollments.append(self) うさうさ研修工房|UML多重度編 class Course: def __init__(self, title: str, trainers: list["Trainer"]): self._validate(trainers) self.title = title # 1..2 → 検証を通ったリスト self.trainers = trainers @staticmethod def _validate(trainers): n = len(trainers) if not MIN_TRAINERS <= n <= MAX_TRAINERS: raise ValueError("講師は1〜2名です") 22

23.

EVIDEN CE-BASE D DES IGN 査読済み研究が示す「多重度の重み」 ① 多重度(カーディナリティ)は概念モデルの中核概念 実体間の関係を1対1・1対多・多対多として捉える枠組みが提示され、以後のデータモデリングとUMLの関連表記の土台になった。 出典:Chen, P. P. (1976). The Entity-Relationship Model. ACM Transactions on Database Systems, 1(1), 9–36 ② 「0..」を安易に使うと、深い理解を妨げうる 任意(最小0)の属性や関連を含む図は、表面的な把握には向くが、利用者が業務の意味を深く理解する場面では理解を損なうと予測され、3つの実験で支持された。 出典:Bodart, Patel, Sim & Weber (2001). Information Systems Research, 12(4), 384–405 ③ 必須と任意の選択は、図の分かりやすさに実際に影響する 必須の属性・関連で構成した図と、任意を含む図を比較する実証研究が行われ、モデルの複雑さと明快さの観点から両者の違いが分析されている。 出典:Gemino, A. & Wand, Y. (2005). Data & Knowledge Engineering, 55(3), 301–326 → 「0..1 や 0..* を使うなら、なぜ任意なのかを説明できるようにしておく」。この姿勢が、図の読み手を助けます。 うさうさ研修工房|UML多重度編 23

24.

OFFICIAL SOURC ES & REFE RENC ES 一次情報にあたるための参考リンク 公式仕様・公式ドキュメント OMG UML 2.5.1 Specification https://www.omg.org/spec/UML/ typing — 型ヒント(Optional) https://docs.python.org/ja/3/library/typing.html 組み込み型(list / set / dict) https://docs.python.org/ja/3/library/stdtypes.html よくある落とし穴(既定値の共有) https://docs.python.org/ja/3/faq/programming.html 査読済み参考論文 Chen, P. P. (1976). The Entity-Relationship Model — Toward a Unified View of Data. ACM Transactions on Database Systems, 1(1), 9–36. Bodart, F., Patel, A., Sim, M. & Weber, R. (2001). Should Optional Properties Be Used in Conceptual Modelling? Information Systems Research, 12(4), 384–405. Gemino, A. & Wand, Y. (2005). Complexity and Clarity in Conceptual Modeling: Comparison of Mandatory and Optional Properties. Data & Knowledge Engineering, 55(3), 301–326. ※ シリーズ第1部(UMLクラス図編)・第2部(オブジェクト指向Python実装編)と併せてご活用ください。 うさうさ研修工房|UML多重度編 24

25.

CH EAT SH EET 困ったときの1枚チートシート 多重度 → Python どの型で持つか 最大の罠 1 self.x = x(必須引数) list 順番に意味がある 0..1 x: X | None = None set 重複させたくない 0..* self.xs: list[X] = [] dict キーで引きたい 1..* list + 空チェック 2..5 list + 範囲チェック * 0..* と同じ def __init__(self, xs=[]): → 全インスタンスで共有される def __init__(self, xs=None): self.xs = xs or [] 下限を守るコード 決める前に聞くこと if not items: raise ValueError(...) if len(self.items) == 1: raise ValueError(...) 「0件のことはありますか?」→ 下限0か1か 読む向き 「上限はありますか?」→ * か n..m か 数字は「線の向こう側のクラスから見た個数」。 A ─1──0..*─ B なら 『A 1件につき B は0件以上』 UMLの補助記法 「将来増える可能性は?」→ 1 か 1..* か うさうさ研修工房|UML多重度編 {ordered} 順序を保つ {unique} 重複を許さない 「順番に意味はありますか?」→ list か set か 25

26.

まとめ • 多重度は「最小値..最大値」。数字ひとつなら最小=最大 • 数字は“線の向こう側から見た個数”。読む向きを間違えない • 1 は必須引数、0..1 は None 許容、0..* は空リストで始める • 1..* や n..m の下限・上限は、型では守れない。検証コードで守る • 両側が * なら、関連クラスに分解できないか考える • 0..* の実装で最も多い事故は、可変デフォルト引数の共有 次の一歩 手元の仕様書から関係を1つ選び、両端の多重度を書き込んでみてください。数字が決められない箇所 こそ、業務側にまだ確認が残っている部分です。 うさうさ研修工房 | 監修:うさうさ先生 1 A 0..* B