組合與繼承
Composition(復合)
??????? 一個類包含另一個類(has-a),可以通過Adapter改造一個類,即一個對象為另一個對象的子對象(成員)。
?????? ?在構造時由內而外,先調用內部類的構造函數,在構造自己;在析構時,先析構自己再析構內部類。
Delegation(委托)
?????? ?一個類里面有一個指針指向另一個類,當前類委托另一個類實現各種操作。可以多個對象里面的指針指向同一個委托對象。兩個類的生命周期不一致。
? ? ? ? Handle/Body模式:將類的接口和類的實現相互分開,對實現類的修改不會影響對外的使用。其相比繼承而言有更大的靈活度。
Inheritance(繼承)
??? class class_name2:?access_specifiers?class_name1
??? {
??? }
??????? class1完全包含class2的內容,calss1為基類,class2為派生類。其構造與析構順序和復合關系一樣,不同的是對于編譯器來說class2對象本身就是一個class1對象。class1的引用和指針完全可以綁定到class2對象上。即
class2 cl2;
class1 &cl1=cl2;
class1 *p=&cl2;
??????? 完全是合法的。但是cl1還是一個class1的引用,p還是一個class1的指針,所以需要一個class2類型的指針和引用的地方,不能用p和cl1來代替。
虛函數與多態
??????? 基類的析構函數必須為virtual(虛函數)。
??????? non-virtual函數:不能被子類重新定義(override);
??????? virtual函數:基類已經默認定義了,但是希望子類重新定義;
??????? pure virtual函數:沒有默認定義,子類必須重新定義;對于含有純虛函數的類成為抽象類,不能產生實例,只能被派生類繼承,且如果派生類沒有重新定義的話,派生類也為一個抽象類。
繼承+復合關系下的構造與析構
?????? 構造時先構造Component,然后構造基類Base,最后才構造Derived。析構時先調用Derived的析構函數,然后析構Base最后進行Component的析構。
經測驗Base的構造函數會先于Component的構造函數調用;析構時Component的析構函數先于Base的析構函數調用。
委托+繼承(composite)
???
??????? 為了讓composite中的容器能夠放入primitive和composite兩種不同的類,那么可以讓這兩個類有同一個基類(繼承)。然后基類的指針(委托)可以指向任何一個派生類。
??????? 基類不知道會派生出哪些派生類,而有需要知道派生出的類的名稱。在寫出派生類之后,派生類創建一個自己這種靜態對象(LSAT),然后把自己的靜態對象添加到基類的addPrototype里面的指針中。然后需要創建某一個派生類對象時通過相應的靜態對象中的clone函數復制一個當前對象,然后為復制出的對象的變量賦值完成一個對象的創建。
??????? 參考資料sourcemaking.com/design_patterns/prototype;該網站列出了一些參考示例,使用prototype的目的,與存在的問題。
作業過程中面臨的問題與解決方案
???????? 在本次作業中有兩套不同的解決方案,一種是按照普通的繼承與復合關系完成的;另一種是通過prototype設計模式進行作業(未完成)。
??????? 在普通的繼承關系中存在以下問題:
??????? 1. 作業中要求通過一個傳統數組保存兩種類型的對象,但是對于數組的描述中明確表示了數組是相同類型的數據按照一定的數序排列。
???????? 解決方案是,由于兩中類都是同一個類Shape的派生類,而Shape形指針又完全可以指向Shpae及其派生類的對象,所以可以創建一個Shape形指針數組,以達到題目要求。
??????? 2. 由于數組中是保存的Shape形指針,當指針解引用時也被編譯器當作了一個Shape形的對象。在輸出流操作符<<重載時,為了使std::cout滿足操作習慣不能采用成員函數形式重載。而在普通函數重載時就要為Circle與Rectangle各自進行一個重載,參數如下:
std::ostream& operator <<(std::ostream &os, Rectangle const &re);
std::ostream& operator <<(std::ostream &os, Circle const &ce);
可以看到區別它們之間不同的為形參列表,但是我的對象是由Shape指針保存的,不能有效區分,導致不能調用相應的流操作符。
??????? 解決方案為:可以采用以下兩種方案,將expression轉換為type_id對象的指針或引用
? dynamic_cast<type-id>(expression)
? static_cast < type-id > ( expression )
在進行由派生類道基類的轉化時,它們倆沒什么區別。但是當由基類向派生類轉化時,dynamic_cast具有類型檢查,比如expression確實指向一個派生類對象時,沒問題返回一個派生類的指針,當expression沒有指向type-id類時返回一個空指針,可用于判斷。而static_cast由于沒有類型檢查所以下行轉換時時不安全的。
??????? 3. 對于**ptr指針作為函數形參時const的使用如下:
? ? ? ? ? const MyStructure *? ? ? *ppMyStruct;
??????? // ptr --> ptr --> const MyStructure
??????? MyStructure *const *ppMyStruct;
??????? // ptr --> const ptr --> MyStructure
??????? MyStructure *? ? ? *const ppMyStruct;
?????? ?// const ptr --> ptr --> MyStructure
??????? 如上,如果ptr所指的指針與指針所指的值還有ptr本身都不會在函數中改變的話,可以采用 const *const *const ptr;這種方式。沒有找到對這種用法的相關建議是否應該少用之類的,但是這種用法確實對于理解有一定的困難。
在使用prototype設計模式進行作業
? ? ? ? 1. 這種設計模式強調我們在運行時去創建一個對象時,不需要完全的重新初始化對象,并且不需要知道對象屬于哪一個派生類也能夠創建出相應的對象。但是如果兩個派生類所需的構造函數形參列表不同,就不能通過*shape=class_name()來改變一個對象里面的值,和上面一樣也要通過類型的強制轉換“dynamic_cast(expression)”。而像示例代碼這樣添加一個draw()純虛函數,用以改變對象的值,但是純虛函數在復寫的時候又要求形參列表要和基類一樣,而circle和rectangle所需的參數數量肯定是不一樣的,最后只有采用笨辦法dynamic_cast改變值。但是網上說用了dynamic_cast就代表類設計上就有問題,一個完好的類的設計是不需要用到dynamic_cast的~~~~
? ? ? ? 2. 這種設計模式在規模較小的程序中,代碼復雜度提升太大,為了實現這個模式而添加的代碼都差不多和普通的一個派生類的所有代碼行數相當。反而不如普通的直接繼承更方便,也許是我反應慢,照著(sourcemaking.com)里面的示例代碼編寫了一個以這種模式為基礎的作業,感覺有時候自己都看不懂了~~~~
總結:本章最難的我認為就數設計模式這部分了,特別是prototype這種,看示例代碼好像很簡單,但是一到自己寫這種設計模式的代碼的時候,就完全不知道該寫什么了呢。反而不明不白的寫了好多完全沒有用的代碼。