# 類之間的關係 ###### tags: `design pattern` **<a href="https://hackmd.io/eJCtKmTbTB6GvJgqWToqJA">點此回到 Design pattern 目錄 </a>** [toc] ## UML 統一建模語言 - 類 - 上面是類別名稱 - 中間是類別屬性 - `[可見性]屬性名:類型[=默認值]` - 下面是類別方法 - "+" 代表 public,"-" 代表private,"#" 代表protected,"~" 表示friend - `[可見性]名稱(參數列表)[:返回類型]` ![](https://i.imgur.com/hvIoHFB.png) - 接口(Interface): 一種特殊的類,**它具有類的結構但不可被實例化,只可以被子類實現。它包含抽象操作,但不包含屬性**。它描述了類或組件對外可見的動作。在 UML 中,接口使用一個帶有名稱的小圓圈來進行表示。 ![](https://i.imgur.com/96SHoaE.png) - 類圖:來顯示系統中的類、接口、協作以及它們之間的靜態結構和關係的一種靜態模型。主要用於軟體系統的結構化設計,幫助簡化軟體系統的理解,是系統分析與設計階段的產物。 ## 類之間的關係 在軟體系統中,類通常不會孤立存在的,類與類之間存在各種關係。根據類與類的之間的耦合度從弱到強排列,UML中的類圖有以下幾種關係:**依賴關係、關聯關係、聚合關係、組合關係、泛化關係、實現關係**。其中泛化和實現的耦合度相等,他們耦合度是最強的。 ### 依賴關係 use a 在方法行為中使用 - 一種使用關係,他是物件之間耦合度最弱的一種關聯方式,是臨時性的關聯。 - 在程式碼中,**某個類的方法通過<font color=red>局部變數</font>、方法的參數或者對静態方法的調用来訪問另一個類(被依赖類)中的某些方法來完成一些職責** - 生活中例子: 人(class) 打電話(class),打電話是人(類)的一個方法,電話是被依賴類。 - 依赖關係使用**帶箭頭的虛線**來表示 #### 使用 use a - B作為A的成員函數參數 - B作為A的成員函數的局部變量 - A的成員函數調用B的靜態方法 #### 人使用手機打電話 ![](https://i.imgur.com/mwOmm55.png) ```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"關係 ![](https://i.imgur.com/njGgTaT.png) - 雙向的關聯可以用**兩個箭頭或者沒有箭頭的實線表示**。單向的關聯用帶一個箭頭的實現表示,箭頭從使用類指向被關聯類。也可以在關聯線的兩端標注角色名,代表兩種不同的角色。 #### 老師教學生 ```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、公司與員工、班級與學生 - 聚合關係可以用帶**空心菱形的實線**來表示,菱形指向整体 ![](https://i.imgur.com/ihWhkVr.png) #### 大學裡有很多老師 ```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 關係。 - 在组合關係中,整體對象可以控制部分對象的生命周期,一旦整體對象不存在,部分對象也將不存在,**部分對象不能脫離整體對象而存在。** - 聚合、組合只能配合語義,結合上下文才能夠判斷出來,而只給出一段代碼讓我們判斷是關聯,聚合,則是無法判斷的。 - 生活中例子:頭和嘴的關係,没有了頭,嘴也就不存在了。 - 人與四肢 - 组合關係用帶**實心菱形的實線**來表示,菱形指向整體。 ![](https://i.imgur.com/O46ryHh.png) #### 頭與嘴的關係 ```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 類的子類。 - 泛化關係用**帶空心三角箭頭的實線**來表示,箭頭從子類指向父類。 ![](https://i.imgur.com/MFzcjra.png) ```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父類方法並實現 - 生活中例子:汽車和船實現了交通工具。 - 實現關係使用**帶空心三角箭頭的虛線**來表示,箭頭從實現類指向接口。 ![](https://i.imgur.com/KrIcCY5.png) ```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)