# 類之間的關係
###### tags: `design pattern`
**<a href="https://hackmd.io/eJCtKmTbTB6GvJgqWToqJA">點此回到 Design pattern 目錄 </a>**
[toc]
## UML 統一建模語言
- 類
- 上面是類別名稱
- 中間是類別屬性
- `[可見性]屬性名:類型[=默認值]`
- 下面是類別方法
- "+" 代表 public,"-" 代表private,"#" 代表protected,"~" 表示friend
- `[可見性]名稱(參數列表)[:返回類型]`

- 接口(Interface): 一種特殊的類,**它具有類的結構但不可被實例化,只可以被子類實現。它包含抽象操作,但不包含屬性**。它描述了類或組件對外可見的動作。在 UML 中,接口使用一個帶有名稱的小圓圈來進行表示。

- 類圖:來顯示系統中的類、接口、協作以及它們之間的靜態結構和關係的一種靜態模型。主要用於軟體系統的結構化設計,幫助簡化軟體系統的理解,是系統分析與設計階段的產物。
## 類之間的關係
在軟體系統中,類通常不會孤立存在的,類與類之間存在各種關係。根據類與類的之間的耦合度從弱到強排列,UML中的類圖有以下幾種關係:**依賴關係、關聯關係、聚合關係、組合關係、泛化關係、實現關係**。其中泛化和實現的耦合度相等,他們耦合度是最強的。
### 依賴關係 use a 在方法行為中使用
- 一種使用關係,他是物件之間耦合度最弱的一種關聯方式,是臨時性的關聯。
- 在程式碼中,**某個類的方法通過<font color=red>局部變數</font>、方法的參數或者對静態方法的調用来訪問另一個類(被依赖類)中的某些方法來完成一些職責**
- 生活中例子: 人(class) 打電話(class),打電話是人(類)的一個方法,電話是被依賴類。
- 依赖關係使用**帶箭頭的虛線**來表示
#### 使用 use a
- B作為A的成員函數參數
- B作為A的成員函數的局部變量
- A的成員函數調用B的靜態方法
#### 人使用手機打電話

```cpp=
#include <iostream>
class MobilePhone
{
public:
void transfer()
{
std::cout << "語音傳送" << std::endl;
}
};
class Person
{
private:
std::string name;
public:
// 方法的參數
void call(MobilePhone mp)
{
mp.transfer();
}
};
int main(){
MobilePhone mp;
Person().call(mp);
return 0;
}
```
#### 人使用電腦、手機打遊戲
```python=
class Person:
def play(self, deivce):
# 通過方法的參數
deivce.run()
print("終於能打遊戲")
class Computer:
def run(self):
print("電腦已經打開,已經登入")
class Phone:
def run(self):
print("手機版英雄聯盟已登入")
if __name__ == "__main__":
computer = Computer()
phone = Phone()
person = Person()
person.play(computer)
# 電腦已經打開,已經登入
# 終於能打遊戲
person.play(phone)
# 手機版英雄聯盟已登入
# 終於能打遊戲
```
#### 駕駛開車
```cpp=
#include<iostream>
class Car{
public:
static void run(){
std::cout<<"Car is running"<<std::endl;
}
};
class Driver{
public:
// B作為A的成員函數參數
void drive1(Car car){
car.run();
}
// B作為A的成員函數的局部變量
void drive2(){
Car car ;
car.run();
}
// A的成員函數調用B的靜態方法
void drive3(){
Car::run();
}
};
int main(){
Car car;
Driver().drive1(car);
Driver().drive2();
Driver().drive3();
return 0;
}
```
### 關聯關係
- 對象之間的一種引用關係,表示一類對象與另一類對象的關係。關聯關係是類與類之間最常用的一種關係。又**可分為一般關聯、聚合關係、組合關係**。
- 生活中例子:老師與學生、師父與徒弟、丈夫與妻子、學生與學生證、班級與學生、學生與課程、客戶與訂單、訂單與商品
- 在程式碼中通常將**一個類的對象作為另一個類的<font color=red>成員變量</font>来實現關聯關係**
> 可以為雙向、單向關聯(通常我們需要避免使用雙向關聯關係),是一種"has a"關係

- 雙向的關聯可以用**兩個箭頭或者沒有箭頭的實線表示**。單向的關聯用帶一個箭頭的實現表示,箭頭從使用類指向被關聯類。也可以在關聯線的兩端標注角色名,代表兩種不同的角色。
#### 老師教學生
```cpp=
#include <iostream>
#include <list>
class Student;
class Teacher;
class Teacher
{
private:
std::string name;
std::list<Student> stus; // 成員變量
public:
Teacher(std::string name) : name(name){};
void append_student(std::string st);
void teach();
const std::string GetName() const;
};
class Student
{
private:
std::string name;
std::list<Teacher> teas; // 成員變量
public:
Student(std::string name) : name(name){};
void append_teacher(std::string t);
void study();
const std::string GetName() const;
};
void Student::append_teacher(std::string t)
{
teas.push_back(Teacher(t));
}
void Student::study()
{
for (Teacher teacher : teas)
{
std::cout << "Student " << this->name << " study from teacher: " << teacher.GetName() << std::endl;
}
}
const std::string Student::GetName() const { return name; }
void Teacher::append_student(std::string st)
{
stus.push_back(Student(st));
}
void Teacher::teach()
{
for (Student student : stus)
{
std::cout << "Teacher: " << Teacher::name << " teaches " << student.GetName() << std::endl;
}
}
const std::string Teacher::GetName() const { return name; }
int main()
{
Student student("Amy");
student.append_teacher("Yoyo");
student.append_teacher("Tim");
student.study();
Teacher teacher("Yoyo");
teacher.append_student("Jason");
teacher.append_student("Tim");
teacher.teach();
return 0;
// Student Amystudy from teacher: Yoyo
// Student Amystudy from teacher: Tim
// Teacher: Yoyo teaches Jason
// Teacher: Yoyo teaches Tim
}
```
#### 男生與女朋友吃飯、看電影
```python=
# 一對一
class Boy:
def __init__(self, name, girlFriend=None):
# 在初始化的时候可以给一個對象的屬性設置成另一個類的對象
self.girlFriend = girlFriend # 一個男孩有一個女朋友
def chi(self):
if self.girlFriend:
print(f"帶著他的女朋友{self.girlFriend.name}去吃飯")
else:
print("單身狗, 吃什麼吃? 滾去學習")
def movie(self):
if self.girlFriend:
print(f"帶著他的女朋友{self.girlFriend.name}去看電影影")
else:
print("單身狗, 看什麼看? 滾去學習")
class Girl:
def __init__(self, name):
self.name = name
b = Boy("小明")
g = Girl("ABC")
b.chi() # 單身狗, 吃什麼吃? 滾去學習
# 給小明介绍了一個女朋友
b.girlFriend = g
b.chi() # 帶著他的女朋友ABC去吃飯
g2 = Girl("QWER")
b.girlFriend = g2 # 換了女朋友
b.chi() # 帶著他的女朋友QWER去吃飯
```
### 聚合關係 has-a 屬性
- 聚合(Aggregation)關係是關聯關係的一種,是強關聯關係,是整体和部分之間的關係,是 has-a 的關係。
- 聚合關係也是通過**成員對象(屬性)實現**的,其中成員對象是整體對象的一部分,但是**成員對象可以脱离整体對象而獨立存在。**
- 聚合、組合只能配合語義,結合上下文才能夠判斷出來,而只給出一段代碼讓我們判斷是關聯,聚合,則是無法判斷的。
- 生活中例子:學校與老師的關係,學校包含老師但如果學校停辦了,老師依然存在。
- 電腦與CPU、公司與員工、班級與學生
- 聚合關係可以用帶**空心菱形的實線**來表示,菱形指向整体

#### 大學裡有很多老師
```cpp=
#include<iostream>
#include<list>
class Teacher{
private:
std::string name;
public:
Teacher(std::string name):name(name){};
void teaching(){
std::cout<<"Teacher name is"<<name<<std::endl;
}
std::string GetName(){
return name;
}
};
class School{
private:
std::list<Teacher> teas;
public:
void GetAllTeacher(){
for(Teacher t:teas){
std::cout<<t.GetName()<<" ";
}
}
void RollIn(std::string name){
teas.push_back(Teacher(name));
}
};
int main(){
School school;
school.RollIn("Yoyo");
school.RollIn("Tim");
school.RollIn("Tom");
school.GetAllTeacher();
}
```
#### 車是一輛私家車,是司機財產的一部分
```cpp=
#include<iostream>
class Car{
public:
void run(){
std::cout<<"Car is running"<<std::endl;
}
};
class Driver{
private:
Car mycar;
public:
// 使用成員變量形式實現關聯
void drive(){
mycar.run(); // 車是我自己的車,我“擁有”這個車
}
};
int main(){
Driver driver;
driver.drive();
return 0;
}
// 車是一輛私家車,是司機財產的一部分。則相同的代碼即表示聚合關係了
```
```python=
class School:
def __init__(self, name):
self.__teach_list = []
def teas(self,teach):
self.__teach_list.append(teach)
def teaching(self):
for t in self.__teach_list:
t.work()
class Teacher:
def __init__(self, name):
self.name = name
def work(self):
print(f'{self.name}在上課')
x = School('中央學校')
t1 = Teacher('教師1')
t2 = Teacher('教師2')
t3 = Teacher('教師3')
t4 = Teacher('教師4')
x.teas(t1)
x.teas(t2)
x.teas(t3)
x.teas(t4)
x.teaching()
'''
教師1在上課
教師2在上課
教師3在上課
教師4在上課
'''
```
### 組合關係 cxmtains-a 在建構子或是類宣告時就new()
- 组合(Composition)關係也是關聯關係的一種,也表示類之間的整體與部分的關係,但它是一種更強烈的聚合關係,是 cxmtains-a 關係。
- 在组合關係中,整體對象可以控制部分對象的生命周期,一旦整體對象不存在,部分對象也將不存在,**部分對象不能脫離整體對象而存在。**
- 聚合、組合只能配合語義,結合上下文才能夠判斷出來,而只給出一段代碼讓我們判斷是關聯,聚合,則是無法判斷的。
- 生活中例子:頭和嘴的關係,没有了頭,嘴也就不存在了。
- 人與四肢
- 组合關係用帶**實心菱形的實線**來表示,菱形指向整體。

#### 頭與嘴的關係
```cpp=
#include<iostream>
#include<list>
class Mouth{
public:
void eat(){
std::cout<<"eat"<<std::endl;
}
};
class Head{
private:
Mouth mouth;
public:
Head(){
mouth = Mouth();
}
void eat(){
mouth.eat();
}
};
int main(){
Head head;
head.eat();
}
```
```java=
public class Person {
public Head head;
public Body body;
public Person(){
head = new Head();
body = new Body();
}
}
```
#### 車是司機的必須有的財產,要想成為一個司機必須要先有輛車
```cpp=
#include<iostream>
class Car{
public:
void run(){
std::cout<<"Car is running"<<std::endl;
}
};
class Driver{
private:
Car mycar;
public:
// 使用成員變量形式實現關聯
void drive(){
mycar.run(); // 車是我自己的車,我“擁有”這個車
}
};
int main(){
Driver driver;
driver.drive();
return 0;
}
//車是司機的必須有的財產,要想成為一個司機必須要先有輛車,車要是沒了,司機也不想活了。而且司機要是不干司機了,這個車就砸了,別人誰也別想用。那就表示組合關係了。
```
### 泛化關係 is-a
- 泛化是耦合度最大的關係之一。
- 常見的父類與子類之間的關係,是一種**繼承**關係,是 is-a 的關係。
- 在程式碼實現時,使用物件導向的繼承機制來實現泛化關係。
- 類可以繼承**抽像類**,類繼承父類都屬於這種關係
- 生活中例子:Student 類和 Teacher 類都是 Person 類的子類。
- 泛化關係用**帶空心三角箭頭的實線**來表示,箭頭從子類指向父類。

```cpp=
#include<iostream>
#include<list>
class Person{
protected:
std::string name;
int age;
public:
Person(std::string name, int age):name(name), age(age){};
void move(){};
void say(){};
};
class Student:public Person{
private:
std::string studentNumber ;
public:
Student(std::string studentNumber, std::string name, int age):studentNumber(studentNumber), Person(name, age){};
void study(){
std::cout<<"Student "<<name<<" id is "<<studentNumber<<" and age is "<<age<<std::endl;
}
};
class Teacher:public Person{
private:
std::string teacherNumber;
public:
Teacher(std::string teacherNumber, std::string name, int age):teacherNumber(teacherNumber), Person(name, age){};
void teach(){
std::cout<<"Teacher "<<name<<" id is "<<teacherNumber<<" and age is "<<age<<std::endl;
}
};
int main(){
Teacher teacher("105522098", "Tim", 29);
teacher.teach();
Student student("101201504","Amy",12);
student.study();
// Teacher Tim id is 105522098 and age is 29
// Student Amy id is 101201504 and age is 12
}
```
### 實現關係
- 實現關係是**接口與實現類之間的關係**。
- 類實現了接口,類中的方法實現了接口中所聲明的所有的抽象方法。
- 父類中宣告(抽象)方法但不實現,(接口類)子類中override父類方法並實現
- 生活中例子:汽車和船實現了交通工具。
- 實現關係使用**帶空心三角箭頭的虛線**來表示,箭頭從實現類指向接口。

```cpp=
#include<iostream>
class Vehicle{
public:
virtual void move() = 0;
};
class Car:public Vehicle{
public:
void move(){
std::cout<<"Car moves"<<std::endl;
}
};
class Ship:public Vehicle{
public:
void move(){
std::cout<<"Ship moves"<<std::endl;
}
};
int main(){
Car car;
car.move();
Ship ship;
ship.move();
return 0;
// Car move
// Ship moves
}
```
## SUMMARY
| 關係 | 依賴關係 | 關聯關係 | 聚合關係 | 組合關係 | 泛化關係 | 實現關係 |
| ---------- | -------------------------------------- | ---------------- | -------------------- | -------------------- | -------- | -------- |
| 描述 | 某類通過方法調用另一個依賴類 | | 聚合 | 組合 | 繼承 | 接口 |
| 生活中例子 | 人們用手機打電話的關係 | 老師教學生的關係 | 學校與老師的關係 | 頭與嘴的關係 | 學生和老師都是人的關係 | 汽車和船都是交通工具的關係 |
| UML表示 | 箭頭由使用類指向被依賴類虛線 | 無箭頭實線 | 空心菱形指向整體對象實線 | 實心菱形指向整體對象實線 | 空心三角形箭頭從子類指向父類的實線 | 空心三角形箭頭從實體類指向接口的虛線 |
| 關鍵字 | use-a | | has-a | cxmtains-a | is-a | 接口 |
| 帶箭頭的虛線 | 實線箭頭 | 實心菱形實線 | 空心菱形實線 | 空心三角形虛線 | 空心三角形實線 |
| ------------ | -------- | ------------ | ------------ | -------------- | -------------- |
| 依賴 | 關聯 | 組合 | 聚合 | 實現 | 繼承 |
- 箭頭方向從子類指向父類
- **空心三角形箭頭**表示實現或繼承:虛線表示實現、實線表示繼承
- **菱形**表示組合或聚合:空心聚合、實心組合
- **線條**表示依賴或關聯:虛線依賴、實線關聯
### 關聯、組合、聚合
- 關聯是通過
- 局部變數
- 方法行參
- 靜態方法
- 關聯關係
- 成員變量
- 聚合
- 整體與部分,可獨立存在
- 組合
- 整體與部分,不可分割
## 參考資料
- [UML類別圖說明](https://ithelp.ithome.com.tw/articles/10241208)
- [Python教程:类之间的依赖关系与组合关系](https://www.cnblogs.com/q455674496/p/10145226.html)
- [C++类与类之间的存在的几种关系以及UML类图简单说明(依赖、关联、聚合、组合、泛化(继承)、实现](https://blog.csdn.net/JMW1407/article/details/107169451)