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>   2. Function Overloading 對於同名但不同參數的Function,稱為Function Overloading,藉由丟入不同參數來執行對應的實作細節 <b>範例:</b>   3. Operator Overloading 透過overloading類別中的符號(例如:+),來讓我們實作出結果。(例如:人物走動location相加)   4. 樣板(Template)  當有許多礦物時候,會需創建更多類別,維護性低  主要差別在類別名稱及型態,我們可以使用Template來讓類別使用通用型別,並將類別中通用型別直接取代成指定的型別  類別中的T我們統稱為 "通用類別",會被使用者所傳入的型態所取代  template對於函數的綁定是Early Binding,因此對於每個template型別的變數,它會自動去綁定該傳入型別  5. Function Template 可以幫助我們讓某個函數可以使用通用型別,跟Class Template 沒有不同差別,只有設計方法上有些差別 e.g:我們想要做批量燒製礦物   使用時機: 1. 如果你的某個成員的型態是T 2. 如果你的某個virtual function 需要型別 T 3. 實際上還是根據Case決定 # tip : Function Matching 在C++中,Function Name與Function並不是一對一,而是一對多 例如我們可以建立許多同名但不同參數的Function,讓C++自動選擇對應的Function去執行 # 概念:Duck Typing  例如: Smelt()函數,本來是只能放礦物,但木頭也是可以被燒製成木炭,那麼木頭也能被放到T中,畢竟他是可以支援Smelt()的 不管是不是真的礦物,只要能燒,就可以丟入這個template # 總結: 透過多型,盡可能不考慮"精準型態",而是使用繼承多型或泛型來解決型別的問題 其中也提到以下問題:  # Ch6 ## 組合 簡單定義:「組合」就是成員內具有其他的類別成員,我們會在建構子或者 Setter 中設定這些成員, 透過組合,我們可以真的讓某個成員是一個類別,取代大量的成員來描述一種類別 1. 物件委派 裝備是被委派到了類別裡面做處理,而非我們將EnhancementTable 中的附魔拿出來並加到裝備上 ,把丟入的裝備當作一個function,並用其中的Setter附魔。  2. 聚合 當物件委派時,才會有所謂的聚合,簡單定義就是當物件被委派到別的class時,不應該被釋放,應該要小心 Double Free 的問題,因邏輯上,釋放蛋糕的時機為整個程式碼執行完畢時,若因為Aggregation 沒處理好,提前釋放蛋糕,就會造成 Double Free 的問題。 1. 如果這個類別擁有「類別成員」的所有權,我們稱之為組合 1. 在釋放時我們應該要把組合內的成員全部釋放,因為成員內的東西是所有物 2. ex:如果你讓一台筆電變不見,他的所有零件「電池、螢幕」也應該不見 2. 對於這個成員是「委派」給這個類別的,我們稱之為聚合 1. 不應該釋放,因為東西是委派給這個類別,這個類別自己不見與委派的成員無關 2. Ex. 放入物品的附魔台,在附魔台被破壞時,物品不應該被破壞,因為物品委派給附魔台做附魔 小總結: 組合(相當於擁有) ,在 Destructor 中,我們將這個類別擁有的成員全部釋放 ex: 極限模式的Minecraft,死掉不會噴裝備 or 讓筆電變不見,連螢幕跟電池應該也會不見 委派 -> 聚合 ,在 Destructor 中,這個類別擁有的成員不應該被釋放 ex:當把裝備放入附魔台,並將附魔台拆掉,裝備不會消失,而是會噴出來 ## 介面   * 介面和繼承差別: * 我們在介面上主要思考的是「會攻擊角色的類別需要有哪些 Function」,出發點從 Function 開始 * 我們在繼承上主要思考的是「這個類別會需要哪些 Function 能夠給底下的子類做使用」,出發點從類別開始 * 在介面上,我們可以不用管底下的子類是不是父類的衍生,而是「這些所有的類別」都有「某個 Function」的功能 ex:要用繼承,需要在意仙人掌是不是一種怪物,所以我們可以選擇不用,我們可以知道的是無論是仙人掌、殭屍、還是史萊姆,都是一種會對主角造成危害的類別 ## 物件導向中的耦合 目前的架構如下:  但如果要加入骷弓,讓骷顱弓箭手有近戰攻擊的 Function其實不太合理,因為骷顱弓箭手會發射弓箭來射傷玩家,因此就需要額外加上一層繼承來解決這件事情。  * 會發現當我們新增這個骷顱弓箭手後,可能會導致「整組」架構要重寫 * 這會使我們的「繼承鏈」變長,所謂的繼承鏈即為繼承所造成的鏈式結構 * 繼承鏈能夠保證「父類是子類的衍伸類別」這個準則,但是因為這個準則,導致大量相依 簡單定義耦合:若要加入新東西而改變整體架構(新增一個類別可能導致所有的類別都要更動),被稱作高耦合度的實作 * 耦合其實並不是一個用於精確測量受影響類別數量的方式,而是一種經驗法則 * 我們期望上我們的實作是低耦合度跟程式碼的重複率做衡量,可以讓程式的可讀性與可維護性增加 * 這時候,我們可以先思考「這樣的一個東西會不會有大量變動的可能」 * 如果某些事物,我們可以確保它不會再有額外變動的可能,就能夠使用「繼承」來提升程式碼的整潔度 * Ex. 我們知道蛋糕是甜、苦、鹹的 * 我們可以讓所有不同種類的蛋糕,繼承「蛋糕」這個類別,並有與「甜、苦、鹹」相關的成員與函式 * 理論上,蛋糕不會有酸、辣的(假設),我們不會在蛋糕類別上,再額外加「酸、辣」等功能了 * 這時候,用「繼承」就不需要擔心因為耦合所以導致大量變更的可能 * 所以,我們在乎「耦合」這件事情,主要還是在於要頻繁處理繼承鏈的變更 * 如果這個繼承鏈沒有太大的變動,那其實就可以不用太在意耦合 ## 利用「組合與介面」取代繼承「類別」 * 傳統類別的繼承 * 當我們使用繼承的時候,我們主要將重複的程式碼提到了父類 • 好處:程式碼的整潔度提升 • 壞處:使耦合度提升,可能會產生巨大的繼承鏈來導致難以維護 例子如下:   若換成以下架構: * 概念有點像是這樣: • 不繼承 Mob,而是直接把 Mob 當作類別成員 • 創立 MobHealth 與 MobAttack 來當作 Mob 內部的成員    • 這時候我們可以使用介面,來讓所有的類別繼承介面。 • 由於介面是由 Pure Virtual Function 組成,所以就會強制實作。  這樣做有什麼好處? • 我們實際上是在「扁平化」繼承鏈,如果繼承鏈的高度變低,那麼就會讓耦合度下降,可能使程式碼更好維護。  這些 Class 都在同一層上,如果我們希望可以對一個特定的類別新增一個類別成員… • 那似乎也不影響其他的類別。 當我們要加入新的怪物或是新的功能,只須找到對應的怪物加入,而不用動到全部code的架構 。  ## Diamond Problem 當使用繼承時,Attackable跟Floatable的父類皆為Mob,對於一些語言(例如:Java)來說是不被允許的  因此我們使用"組合跟介面"來避免Diamond Problem ## Combining Interface  把介面定義好的好處就在於,我們可以重複使用這些介面。如果要加入新的怪物,只要使用所需的介面即可。 ex:要加入凋零怪  ## template issue  而面對「多型」能夠放進去的最小需求是基於丟進去的類別應該要實作某些 Function * 我們也許可以把多型的通用型別換成介面? * 那麼,我們就不知道這個多型能夠放進去的最小需求是什麼 * 不過我們可以知道的是應該要實現一些必要的 Function ## 依賴注入(Dependency Injection) 什麼叫做依賴注入?當我們在建構子中將類別當作參數,就是依賴注入的方法   基本上 Furnace 就可以透過呼叫傳入的物件持有的 Smelt 特性,來做不一樣的事情,讓耦合度變低 * 燒肉可能會飄香 * 燒礦石可能會比燒木頭、燒礦石還要來得久 * 取決於傳入的物件,來讓熔爐呼叫 Smelt() 時有不同的特性 ## 結論 * 我們討論到了組合的一些特性 * 聚合(Aggregation)與組合(Composition) * 取決於是不是委派給類別。 * 我們討論到了一些組合與介面用來解決繼承的問題 * 繼承鏈是一個高耦合度的實作,難以維護 * 透過組合與介面能夠扁平化繼承鏈,降低耦合。 * 組合與介面還能優化掉很多有趣的問題 * 鑽石問題 * 透過組合介面來重複利用介面組出想要的東西 * 優化掉使用泛型時難以理解最小需求的問題 * 我們也討論到了繼承、組合與介面的好與壞 * 繼承可能更適合用在不需要討論耦合的情況 * 組合與介面能夠更好的應付需求與降低耦合度 * 使用繼承時需要額外小心一些問題 * 最後,我們討論到了依賴注入 * 能夠根據傳入的物件是什麼來做出對應的行為。 * 例如熔爐丟肉進去燒會飄香、丟礦石可能會燒比較慢… * 依賴注入也能有效優化建構子的部分,可以不用傳入組合所需要的所有成員,導致難以維護建構子的參數。 ###### tags: `物件導向程式設計`
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up