---
# System prepended metadata

title: OOP 總結
tags: [大二上, 物件導向程式設計]

---

OOP 總結
===
#    ch1

封裝:
Public Private
優點:
1 不須知道實作細節，會用就好
2 防止某些東西被更改,以及怎樣是正確的修改

繼承:
優點:
防止相同屬性及方法重複出現

多型:
多個class共用一個function但在每個class中有不同實作方式，例如:取得電腦價格

組合:
方法去選擇class，像是 run() 去選擇 class dog , class cat swim() 去選擇 class fish

依賴注入:
把整個物件丟入，而非把數值或字串丟入

#    ch2

#include ... 指導語句

建構子:用於初始化輸入的值(成員)
e.g:Laptop(std::string operatorSystem,double screensize);

解構子:用來在物件需要釋放時釋放內部資源

為何區分.hpp 跟 .cpp
編輯效率:若要修改 只需編譯修改的文件 而非整個文件
模組化(封裝):別的檔案可以include .hpp 不須知道實作細節
可讀性與維護

RAII:須確保所有屬性的值都是合法的，透過不須額外判斷值是否合法而導致程式碼混亂

std::string 字串
.length 字串長度
+ 拼接字串

std::vector 可動態調整的陣列
.push_back 放入陣列尾端
.size 陣列大小
安全 且 容易操作

std::vector<動態記憶體陣列的型態>
用大括號存放元素

std::shared_ptr:用物件來處理指標，仰賴生命週期而不用手動釋放或卡在memory leak

#    ch3 封裝(把類別變成黑箱模組)
public private(只能在類別內存取)
class 中 若未指定存取修飾字，預設為private

從物件取得資料(Getter):回傳數值給caller
e.g:getpoint()
從物件修改資料,存取成員,執行事件(Setter):檢查Caller傳入的數值會不會造成異常，設定數值至成員
e.g:addpoint()

struct預設屬性為public class則為private
struct預設屬性為public繼承 class則為private繼承
struct 不能用在template class 可以

只有資料，不會對成員做複雜處理用struct，其他用class
#    Ch5 Part1 多型
1. Object Casting
原理:
為了處理掉落各種礦物的code,先將他視為一個廣泛的礦石，把子類當成父類 用virtual + override 呼叫廣泛的礦石底下的子類getore()來取得礦物，透過指標替換，更方便替換成我們想要的東西
問題:
所有RedStoneOreStone額外的函式與成員都會因為Casting而導致子類衍生成員與變數消失
僅存父類OreStone的成員與函式

2. 深入探討 Virtual (C++)
若沒有在function前加入virtual (Early Binding): 在Compilation Time 時進行綁定
反之 (Late Binding): 在Execution Time時進行綁定
若沒特別加上virtual時，所有function綁定都視為Early Binding
有加上virtual可能代表著，這個function有可能有子類的實作，需要在執行時去找他

3. Virtual Destructor
若Destructor不是virtual的話，這種行為會被C++稱作undefined behavior也有可能有memory leak
為甚麼?    因為父類的解構子被Early Binding，導致父類先被解構子解構了，子類的卻還沒解構，導致子類資源沒有完全釋放
若使用Virtual Destructor，由於Late Binding，所以能夠順利呼叫子類的解構子，也能夠依照解構鍊的關係，可以正常向上釋放父類資源

總結:當對於一個有繼承的物件，應該讓父類的解構子是Virtual的，來確保資源能夠被正常釋放
#    Ch5 Part2
1. 多型種類
    1. inclusion
        兩種東西(例如:礦物)同時需要做同樣一件事(例如:燒礦)，會需使用到兩個Queue，且會出現太多類似重複的code，透過inclusion，可以大幅減少因為 **同樣函數原型、不同類別、不同實作細節**所早成的特判情況
        <b>範例:</b>
        ![ex1](https://hackmd.io/_uploads/SJflLOBI6.png)
    
        ![ex2](https://hackmd.io/_uploads/BkzeLuHLp.png)
    2. Function Overloading
        對於同名但不同參數的Function，稱為Function Overloading，藉由丟入不同參數來執行對應的實作細節
        <b>範例:</b>
        ![ex3](https://hackmd.io/_uploads/HklzqOHUp.png)
        ![ex4](https://hackmd.io/_uploads/r1lMcdS8a.png)
    3. Operator Overloading
        透過overloading類別中的符號(例如:+)，來讓我們實作出結果。(例如:人物走動location相加)
        ![ex5](https://hackmd.io/_uploads/SkTwsurUp.png)
        
        ![ex6](https://hackmd.io/_uploads/HkAvjdBUa.png)
    4. 樣板(Template)
        ![ex7](https://hackmd.io/_uploads/B1I7nOBUp.png)
        當有許多礦物時候，會需創建更多類別，維護性低
        ![ex8](https://hackmd.io/_uploads/Hk6F2_rIT.png)
        主要差別在類別名稱及型態，我們可以使用Template來讓類別使用通用型別，並將類別中通用型別直接取代成指定的型別
        ![a](https://hackmd.io/_uploads/HkOWaOr86.png)
        類別中的T我們統稱為 "通用類別"，會被使用者所傳入的型態所取代
        ![b](https://hackmd.io/_uploads/H1aU6urIp.png)
        template對於函數的綁定是Early Binding，因此對於每個template型別的變數，它會自動去綁定該傳入型別
        ![1](https://hackmd.io/_uploads/S178PqU8T.png)
    5. Function Template
        可以幫助我們讓某個函數可以使用通用型別，跟Class Template 沒有不同差別，只有設計方法上有些差別
        e.g:我們想要做批量燒製礦物
        ![2](https://hackmd.io/_uploads/B1y1_588p.png)
        ![3](https://hackmd.io/_uploads/SkRlOcIIT.png)
        使用時機:
        1. 如果你的某個成員的型態是T
        2. 如果你的某個virtual function 需要型別 T
        3. 實際上還是根據Case決定

# tip : Function Matching
在C++中，Function Name與Function並不是一對一，而是一對多
例如我們可以建立許多同名但不同參數的Function，讓C++自動選擇對應的Function去執行
# 概念:Duck Typing
![4](https://hackmd.io/_uploads/HyqytcILT.png)
例如:
Smelt()函數，本來是只能放礦物，但木頭也是可以被燒製成木炭，那麼木頭也能被放到T中，畢竟他是可以支援Smelt()的
不管是不是真的礦物，只要能燒，就可以丟入這個template

#    總結:
透過多型，盡可能不考慮"精準型態"，而是使用繼承多型或泛型來解決型別的問題
其中也提到以下問題:
![5](https://hackmd.io/_uploads/ByVRY9LLT.png)


#    Ch6 
##    組合 
簡單定義:「組合」就是成員內具有其他的類別成員，我們會在建構子或者 Setter 中設定這些成員，
透過組合，我們可以真的讓某個成員是一個類別，取代大量的成員來描述一種類別
1. 物件委派
    裝備是被委派到了類別裡面做處理，而非我們將EnhancementTable 中的附魔拿出來並加到裝備上
    ，把丟入的裝備當作一個function，並用其中的Setter附魔。
    ![螢幕擷取畫面 2023-12-23 115614](https://hackmd.io/_uploads/SyKv9AmDa.png)
2. 聚合 
    當物件委派時，才會有所謂的聚合，簡單定義就是當物件被委派到別的class時，不應該被釋放，應該要小心 Double Free 的問題，因邏輯上，釋放蛋糕的時機為整個程式碼執行完畢時，若因為Aggregation 沒處理好，提前釋放蛋糕，就會造成 Double Free 的問題。
    1. 如果這個類別擁有「類別成員」的所有權，我們稱之為組合
        1. 在釋放時我們應該要把組合內的成員全部釋放，因為成員內的東西是所有物
        2. ex:如果你讓一台筆電變不見，他的所有零件「電池、螢幕」也應該不見
    2. 對於這個成員是「委派」給這個類別的，我們稱之為聚合
        1. 不應該釋放，因為東西是委派給這個類別，這個類別自己不見與委派的成員無關
        2. Ex. 放入物品的附魔台，在附魔台被破壞時，物品不應該被破壞，因為物品委派給附魔台做附魔
小總結: 組合(相當於擁有) ，在 Destructor 中，我們將這個類別擁有的成員全部釋放
        ex: 極限模式的Minecraft,死掉不會噴裝備 or 讓筆電變不見，連螢幕跟電池應該也會不見
       委派 -> 聚合 ，在 Destructor 中，這個類別擁有的成員不應該被釋放
        ex:當把裝備放入附魔台，並將附魔台拆掉，裝備不會消失，而是會噴出來
##    介面
![1](https://hackmd.io/_uploads/BJ8Ryy4DT.png)
![2](https://hackmd.io/_uploads/r1I0JJ4Dp.png)
* 介面和繼承差別:
    * 我們在介面上主要思考的是「會攻擊角色的類別需要有哪些 Function」，出發點從 Function 開始
    * 我們在繼承上主要思考的是「這個類別會需要哪些 Function 能夠給底下的子類做使用」，出發點從類別開始
    * 在介面上，我們可以不用管底下的子類是不是父類的衍生，而是「這些所有的類別」都有「某個 Function」的功能
ex:要用繼承，需要在意仙人掌是不是一種怪物，所以我們可以選擇不用，我們可以知道的是無論是仙人掌、殭屍、還是史萊姆，都是一種會對主角造成危害的類別
##  物件導向中的耦合
目前的架構如下:
![3](https://hackmd.io/_uploads/H1aHQ1Nwp.png)
但如果要加入骷弓，讓骷顱弓箭手有近戰攻擊的 Function其實不太合理，因為骷顱弓箭手會發射弓箭來射傷玩家，因此就需要額外加上一層繼承來解決這件事情。
![1](https://hackmd.io/_uploads/Sk0iQyNva.png)
* 會發現當我們新增這個骷顱弓箭手後，可能會導致「整組」架構要重寫
* 這會使我們的「繼承鏈」變長，所謂的繼承鏈即為繼承所造成的鏈式結構
* 繼承鏈能夠保證「父類是子類的衍伸類別」這個準則，但是因為這個準則，導致大量相依
簡單定義耦合:若要加入新東西而改變整體架構(新增一個類別可能導致所有的類別都要更動)，被稱作高耦合度的實作
* 耦合其實並不是一個用於精確測量受影響類別數量的方式，而是一種經驗法則
* 我們期望上我們的實作是低耦合度跟程式碼的重複率做衡量，可以讓程式的可讀性與可維護性增加
* 這時候，我們可以先思考「這樣的一個東西會不會有大量變動的可能」
    * 如果某些事物，我們可以確保它不會再有額外變動的可能，就能夠使用「繼承」來提升程式碼的整潔度
    * Ex. 我們知道蛋糕是甜、苦、鹹的
        * 我們可以讓所有不同種類的蛋糕，繼承「蛋糕」這個類別，並有與「甜、苦、鹹」相關的成員與函式
        * 理論上，蛋糕不會有酸、辣的（假設），我們不會在蛋糕類別上，再額外加「酸、辣」等功能了
        * 這時候，用「繼承」就不需要擔心因為耦合所以導致大量變更的可能
* 所以，我們在乎「耦合」這件事情，主要還是在於要頻繁處理繼承鏈的變更
    * 如果這個繼承鏈沒有太大的變動，那其實就可以不用太在意耦合

##    利用「組合與介面」取代繼承「類別」
* 傳統類別的繼承
    * 當我們使用繼承的時候，我們主要將重複的程式碼提到了父類
        • 好處：程式碼的整潔度提升
        • 壞處：使耦合度提升，可能會產生巨大的繼承鏈來導致難以維護
    例子如下:
    ![1](https://hackmd.io/_uploads/rJtfF1VwT.png)

    ![1](https://hackmd.io/_uploads/rJ0oOyNvp.png)
    若換成以下架構:
    * 概念有點像是這樣：
        • 不繼承 Mob，而是直接把 Mob 當作類別成員
        • 創立 MobHealth 與 MobAttack 來當作 Mob 內部的成員
    ![1](https://hackmd.io/_uploads/HJN4F14D6.png)
    ![1](https://hackmd.io/_uploads/Sk9rtkNvT.png)
![2](https://hackmd.io/_uploads/rJtRq1VPa.png)
• 這時候我們可以使用介面，來讓所有的類別繼承介面。
• 由於介面是由 Pure Virtual Function 組成，所以就會強制實作。![1](https://hackmd.io/_uploads/rky7i1NwT.png)
![1](https://hackmd.io/_uploads/Hy0HskEPT.png)
這樣做有什麼好處？
    • 我們實際上是在「扁平化」繼承鏈，如果繼承鏈的高度變低，那麼就會讓耦合度下降，可能使程式碼更好維護。
    
![1](https://hackmd.io/_uploads/HkHUnJ4DT.png)

這些 Class 都在同一層上，如果我們希望可以對一個特定的類別新增一個類別成員…
• 那似乎也不影響其他的類別。
當我們要加入新的怪物或是新的功能，只須找到對應的怪物加入，而不用動到全部code的架構 。
![1](https://hackmd.io/_uploads/BywZPIVPa.png)
##    Diamond Problem
當使用繼承時，Attackable跟Floatable的父類皆為Mob，對於一些語言（例如：Java）來說是不被允許的
![1](https://hackmd.io/_uploads/Bk5uPLNwT.png)
因此我們使用"組合跟介面"來避免Diamond Problem![1](https://hackmd.io/_uploads/HJKZdI4vT.png)
##    Combining Interface
![1](https://hackmd.io/_uploads/B1jv_L4vT.png)
把介面定義好的好處就在於，我們可以重複使用這些介面。如果要加入新的怪物，只要使用所需的介面即可。
ex:要加入凋零怪
![1](https://hackmd.io/_uploads/By33OUEvT.png)
##    template issue
![1](https://hackmd.io/_uploads/r1w6nUNwa.png)
而面對「多型」能夠放進去的最小需求是基於丟進去的類別應該要實作某些 Function
* 我們也許可以把多型的通用型別換成介面？
    * 那麼，我們就不知道這個多型能夠放進去的最小需求是什麼
    * 不過我們可以知道的是應該要實現一些必要的 Function

##    依賴注入（Dependency Injection）
什麼叫做依賴注入？當我們在建構子中將類別當作參數，就是依賴注入的方法
![1](https://hackmd.io/_uploads/BJ5QpI4Da.png)
![1](https://hackmd.io/_uploads/rk5LpUEPT.png)
基本上 Furnace 就可以透過呼叫傳入的物件持有的 Smelt 特性，來做不一樣的事情，讓耦合度變低
* 燒肉可能會飄香
* 燒礦石可能會比燒木頭、燒礦石還要來得久
* 取決於傳入的物件，來讓熔爐呼叫 Smelt() 時有不同的特性
##    結論
* 我們討論到了組合的一些特性
    * 聚合（Aggregation）與組合（Composition）
    * 取決於是不是委派給類別。
* 我們討論到了一些組合與介面用來解決繼承的問題
    * 繼承鏈是一個高耦合度的實作，難以維護
    * 透過組合與介面能夠扁平化繼承鏈，降低耦合。
* 組合與介面還能優化掉很多有趣的問題
    * 鑽石問題
    * 透過組合介面來重複利用介面組出想要的東西
    * 優化掉使用泛型時難以理解最小需求的問題
* 我們也討論到了繼承、組合與介面的好與壞
    * 繼承可能更適合用在不需要討論耦合的情況
    * 組合與介面能夠更好的應付需求與降低耦合度
    * 使用繼承時需要額外小心一些問題
* 最後，我們討論到了依賴注入
    * 能夠根據傳入的物件是什麼來做出對應的行為。
    * 例如熔爐丟肉進去燒會飄香、丟礦石可能會燒比較慢…
    * 依賴注入也能有效優化建構子的部分，可以不用傳入組合所需要的所有成員，導致難以維護建構子的參數。

###### tags: `物件導向程式設計`