在軟體開發的世界中,良好的溝通是成功的關鍵。想像一下,如果建築師沒有藍圖,要如何與工程團隊溝通建築設計?同樣地,軟體開發也需要一種標準化的「藍圖」來表達系統架構和設計想法。這就是 UML(Unified Modeling Language,統一塑模語言) 的價值所在。
UML 是一種標準化的視覺化建模語言,用於:
💡 UML 不是程式語言,而是一種圖形化的表達方式,讓複雜的系統設計變得容易理解。
UML 2.x 定義了 14 種不同類型的圖表,主要分為兩大類:
描述系統的靜態結構:
描述系統的動態行為:
類別圖是 UML 中最常用、最重要的圖表之一。它展示系統中的類別、屬性、方法,以及類別之間的關係。
┌─────────────────────┐
│ ClassName │ ← 類別名稱
├─────────────────────┤
│ - attribute1: Type │ ← 屬性(- 表示 private)
│ + attribute2: Type │ ← 屬性(+ 表示 public)
│ # attribute3: Type │ ← 屬性(# 表示 protected)
├─────────────────────┤
│ + method1(): void │ ← 方法
│ - method2(): Type │
│ # method3(): Type │
└─────────────────────┘
符號 | 意義 | Java 對應 |
---|---|---|
+ |
Public | public |
- |
Private | private |
# |
Protected | protected |
~ |
Package | (預設,無修飾符) |
{abstract}
<<interface>>
標籤類別圖的精髓在於表達類別之間的關係,以下是六種主要關係:
符號:───▷
(實線空心三角箭頭)
意義:子類別繼承父類別,"is-a" 關係
範例:
┌─────────┐
│ 動物 │
└─────────┘
△
│
┌────┴────┐
│ │
┌───────┐ ┌───────┐
│ 貓 │ │ 狗 │
└───────┘ └───────┘
貓 是一種 動物,狗 是一種 動物。
符號:┄┄▷
(虛線空心三角箭頭)
意義:類別實作介面
範例:
┌─────────────┐
│<<interface>>│
│ 可飛行 │
└─────────────┘
△
┊
┌────┴────┐
┊ ┊
┌───────┐ ┌───────┐
│ 鳥 │ │ 飛機 │
└───────┘ └───────┘
符號:───
(實線)
意義:類別之間有某種關聯,"has-a" 或 "uses" 關係
範例:
┌─────┐ ┌─────┐
│教師 │──────>│學生 │
└─────┘ 1 * └─────┘
一位教師可以教導多位學生。
多重性標記:
1
: 正好一個0..1
: 零或一個*
或 0..*
: 零或多個1..*
: 一或多個n..m
: n 到 m 個符號:───◇
(實線空心菱形)
意義:整體包含部分的關係,但部分可以獨立存在(弱擁有關係)
範例:
┌─────┐ ┌─────┐
│部門 │◇──────│員工 │
└─────┘ └─────┘
部門包含員工,但員工離開部門後仍然存在。
符號:───◆
(實線實心菱形)
意義:整體包含部分的關係,部分不能獨立於整體存在(強擁有關係)
範例:
┌─────┐ ┌─────┐
│房子 │◆──────│房間 │
└─────┘ └─────┘
房子包含房間,房子不存在了,房間也就不存在了。
符號:┄┄>
(虛線箭頭)
意義:一個類別使用另一個類別,通常是暫時性的關係
範例:
┌─────┐ ┌──────────┐
│訂單 │ ┄┄┄┄┄> │折扣計算器│
└─────┘ └──────────┘
訂單在計算價格時會使用折扣計算器。
設計一個 RPG 遊戲的角色系統:
┌─────────────────────────┐
│ <<abstract>> │
│ Character │
├─────────────────────────┤
│ - name: String │
│ - level: int │
│ - hp: int │
├─────────────────────────┤
│ + Character(name) │
│ + attack(): void │ ← 抽象方法
│ + defend(): void │
│ + levelUp(): void │
└─────────────────────────┘
△
│ (繼承)
┌──────┴──────┐
│ │
┌───────────┐ ┌───────────┐
│ Warrior │ │ Mage │
├───────────┤ ├───────────┤
│-sword: │ │-staff: │
│ String │ │ String │
├───────────┤ ├───────────┤
│+Warrior() │ │+Mage() │
│+attack() │ │+attack() │
│+slash() │ │+cast() │
└───────────┘ └───────────┘
// 抽象父類別
public abstract class Character {
private String name;
private int level;
private int hp;
public Character(String name) {
this.name = name;
this.level = 1;
this.hp = 100;
}
// 抽象方法,子類別必須實作
public abstract void attack();
// 具體方法
public void defend() {
System.out.println(name + " 防禦中...");
}
public void levelUp() {
level++;
hp += 50;
System.out.println(name + " 升級到 Lv." + level + "!生命值: " + hp);
}
// Getters
public String getName() {
return name;
}
public int getLevel() {
return level;
}
public int getHp() {
return hp;
}
}
// 戰士子類別
public class Warrior extends Character {
private String sword;
public Warrior(String name) {
super(name);
this.sword = "鐵劍";
}
@Override
public void attack() {
System.out.println(getName() + " 戰士揮劍斬擊!");
}
// 戰士專屬技能
public void slash() {
System.out.println(getName() + " 使用 " + sword + " 進行強力斬擊!造成 150% 傷害!");
}
// 升級時可以強化武器
public void upgradeSword() {
if (getLevel() >= 5) {
this.sword = "鋼劍";
System.out.println("武器升級為:" + sword);
} else if (getLevel() >= 10) {
this.sword = "神劍";
System.out.println("武器升級為:" + sword);
}
}
}
// 法師子類別
public class Mage extends Character {
private String staff;
private int mana;
public Mage(String name) {
super(name);
this.staff = "木杖";
this.mana = 100;
}
@Override
public void attack() {
System.out.println(getName() + " 法師施放魔法攻擊!");
useMana(10);
}
// 法師專屬技能
public void cast() {
if (mana >= 30) {
System.out.println(getName() + " 使用 " + staff + " 施放火球術!造成 200% 傷害!");
useMana(30);
} else {
System.out.println("魔力不足!");
}
}
private void useMana(int amount) {
mana -= amount;
System.out.println("剩餘魔力: " + mana);
}
public void restoreMana() {
mana = 100;
System.out.println("魔力已恢復!");
}
}
public class RPGGameExample {
public static void main(String[] args) {
// 創建戰士角色
Warrior warrior = new Warrior("亞瑟");
System.out.println("=== 戰士 " + warrior.getName() + " ===");
warrior.attack(); // 一般攻擊
warrior.slash(); // 專屬技能
warrior.defend(); // 防禦
warrior.levelUp(); // 升級
System.out.println();
// 創建法師角色
Mage mage = new Mage("梅林");
System.out.println("=== 法師 " + mage.getName() + " ===");
mage.attack(); // 一般攻擊
mage.cast(); // 專屬技能
mage.attack(); // 再次攻擊
mage.attack(); // 魔力不足
mage.restoreMana(); // 恢復魔力
mage.levelUp(); // 升級
}
}
=== 戰士 亞瑟 ===
亞瑟 戰士揮劍斬擊!
亞瑟 使用 鐵劍 進行強力斬擊!造成 150% 傷害!
亞瑟 防禦中...
亞瑟 升級到 Lv.2!生命值: 150
=== 法師 梅林 ===
梅林 法師施放魔法攻擊!
剩餘魔力: 90
梅林 使用 木杖 施放火球術!造成 200% 傷害!
剩餘魔力: 60
梅林 法師施放魔法攻擊!
剩餘魔力: 50
梅林 法師施放魔法攻擊!
剩餘魔力: 40
魔力已恢復!
梅林 升級到 Lv.2!生命值: 150
每個類別應該只有一個職責。
❌ 不好的設計:
public class User {
private String name;
public void saveToDatabase() { } // 資料庫操作
public void sendEmail() { } // 郵件發送
public void generateReport() { } // 報告生成
}
✅ 好的設計:
public class User {
private String name;
// 只負責使用者資料
}
public class UserRepository {
public void save(User user) { } // 負責資料庫操作
}
public class EmailService {
public void send(User user) { } // 負責郵件發送
}
繼承建立了強耦合關係,組合更加靈活。
❌ 過度使用繼承:
Animal
│
┌───┴───┐
│ │
Bird Fish
│ │
┌─┴─┐ │
│ │ │
Duck Swan Shark
✅ 使用組合:
┌─────────┐ ┌──────────────┐
│ Animal │◇────>│ MoveBehavior │
└─────────┘ └──────────────┘
△
┌────┴────┐
┌──────┐ ┌──────┐
│ Fly │ │ Swim │
└──────┘ └──────┘
不要強迫類別實作它們不需要的方法。
❌ 過大的介面:
public interface Worker {
void work();
void eat();
void sleep();
void fly(); // 不是所有 worker 都會飛
}
✅ 分離介面:
public interface Workable {
void work();
}
public interface Eatable {
void eat();
}
public interface Flyable {
void fly();
}
UML 的定義與用途
UML 圖表分類
類別圖的完整知識