作者:Breaker <breaker.zy_AT_gmail>
關於 C++ 類階層的設計方法學,note-to-self + keynote + cross-reference 式筆記
本文精鍊於 [CPP LANG] 12.4, 15.2 的 BBWindow 樣本,只涉及 design
Syntax 參考 [CPP LANG] Ch12, 15; [CPP PRIMER] Ch17, 18
Play with bits 參考 [CPP OBJMODEL] 5.2
keyword: class hierarchy, multiple inheritance, abstract class, virtual base class, abstract factory, clone
目錄
- 樣本情境
- 分離實現和介面
- 替換實現
- 重複基類
- 共用實現
- 抽象工廠
- Clone 模式
樣本情境^
IValBox: 取得使用者輸入整數的 GUI 元素之抽象類別,它不綁定具體 GUI 元素,如 slider 滑塊, dial 撥盤
IValSlider: 滑塊式 IValBox 實現,類似的還有 IValDial 撥盤式實現,代表 IValBox 類層次中具體的 GUI 元素。這些類可以進一步擴充,如從 IValSlider 派生出 PopupIValSlider
BBWindow: 第三方提供的 GUI 元素實現,類似 MFC 的 CWnd 等。IValBox 的類層次依靠 BBWindow 的類層次實現 GUI 特性(畫圖之類)。BBWindow 只是形式名,它可以替換,其意義就像將 MFC 換成 Qt 一樣,這使得 IValBox 的類層次能夠減小對特定 GUI 元素實現的依賴
UML: Old Hierarchy
設計要義:
使用 BBWindow 不是 IValBox 概念的基本部分。過分依賴 BBWindow,使得 BBWindow 難於替換
可變資料是實現的部分,當它侵入介面(抽象類別)時,會影響介面的靈活性
Two-type interfaces: public interface vs. protected interface
interface: 有譯介面,有譯介面;有時代表函數,有時代表函數之聚集處(類、名字空間),憑上下文判斷。按 [EFFECT CPP] Item 18 的說法:每一種介面都是客戶與你的代碼互動的手段
public interface: 給使用者使用
protected interface: 給衍生類別使用
Strict Guide: Data members are better kept private so that writers of derived classes cannot mess with them.
推論: A protected interface should contain only functions, types, and constants.
更多 private data member 的討論見 [EFFECT CPP] Item 22
Over strict?
Old Hierarchy 是不是 bad design?
實際上我用 MFC 時,大多數時候都這樣做,這是大多數人用 G使用者介面架構的方法
問題不在絕對的 bad design 或 good design,而在於目標是什嗎?Application Development vs. Library Development, Cross-platform vs. Dedicated-platform
下面的改進設計比 Old Hierarchy 靈活而光鮮,但也帶來的額外的負擔(如理解和維護),Pros and Cons 自行抉擇
分離實現和介面^
UML: Separate Implementation and Interface
介面線:介面繼承形成的層次
實現線/擴充線:實現繼承形成的層次
設計要義:
public 繼承 vs. protected/private 繼承
另一種表述是:介面繼承 vs. 實現繼承,見 [EFFECT CPP] Item 40。注意 Item 34 和這個設計無關,雖然標題相同,但那個是 for member function 的,這裡是 for class 的
public 繼承塑模 "is-a" 的關係(見 [EFFECT CPP] Item 32),而 protected/private 塑模 "implemented-in-terms-of" 的關係(見 [EFFECT CPP] Item 39)
protected 和 private 的區別:private 之實現止於直接衍生類別,而 protected 之實現可以進一步擴充
替代技術:public 繼承 + Composition 複合
當用於實現域時,複合塑模 "implemented-in-terms-of" 的關係(和 protected/private 繼承相同),見 [EFFECT CPP] Item 38
採用 protected/private 繼承還是複合的判斷,見 [EFFECT CPP] Item 39,如繼承會造成 EBO (Empty Base Optimization) 空白基類最佳化
替換實現^
這是分離實現和介面後得到的益處
UML: Substitute Implementation
圖中的 BBSlider 表示 BBIValSlider 可藉由以存在的更特定的 GUI 元素實現,而不是更一般的 BBWindow,就像是從 MFC 的 CWnd 改為了 CSliderCtrl
使用同樣的方法進行擴充就能得到 Big One:
UML: Substitute Implementation, Complicated
無需糾結具體含義,這裡的設計推論更有意思:一個系統展現給使用者的應該是一個抽象類別的階層,其實現所用的是一個傳統的階層
有點教條吧?試問,傳統的層級結構從何而來?我們要做一個橫跨多種 G使用者介面架構之上的架構嗎?
重複基類^
用 virtual 基類消除 replicated base class 重複基類,見 [EFFECT CPP] Item 40
這種用法的目的有二:
- 粘合 (glue) 兩個不相干的類。這是我們應該忘掉的 virtual 多繼承,連 Bjarne 也說它是 crude, effective, and important, but not very interesting.
- 為了下面的共用實現,這是有邏輯意義的
共用實現^
UML: Share Implementation
這裡 Diamond-shaped Inheritance 鑽石形繼承的意義:讓實現 PopupIValSlider 的類(即 BBPopupIValSlider)共用實現 IValSlider 的類(即 BBIValSlider)中的實現,以減少編碼
於是另一個有意思的推論:組成應用之介面的抽象類別的所有派生都應該是 virtual 的,[EFFECT CPP] Item 40 也說 public 繼承應該總是 virtual
但是現實不能如此,最簡短的反駁是效率因素,見 [CPP LANG] 15.2.5 和 [EFFECT CPP] Item 40
可藉由無 data member class 進行重複基類方式的最佳化
於是,饒了一圈又回來了
抽象工廠^
建構函式不可能是 virtual 的,道理很簡單:不知道對象的確切類型,又如何構造它(建構函式的實質是對象內布局的 bits 初始化)
抽象工廠和 Clone 模式被戲稱為 virtual constructor 虛擬建構函式,因為它們用 virtual 函數迂迴完成建構函式的任務:根據某些線索建立對象
- 對於真的建構函式,線索是建構函式之參數
- 對於抽象工廠,線索是程式初始化時的預先設定。它的形態(典型的)可以是類的 UUID 標識 (e.g. COM's IClassFactory)
- 對於 Clone 模式,線索是當前對象之確切類型
絕對的抽象建立(沒有線索)是不可能的,即沒有文法支援(virtual 建構函式),也沒有邏輯意義:當你想要鉛筆時,可以說我要鉛筆,也可以說我要鉛筆盒中的東西(帶線索的抽象),但不能只說我要東西(不帶線索的抽象)
Why abstract class?
UML: Abstract Factory
減小對象建立時,對特定實作類別(建構函式)的依賴,如當建立 IValDial 時,必須使用 BBIValDial 或 LSIValDial 的建構函式
當抽象類別階層較複雜時,並且有從一個實現系統變為另一個實現系統(如從 BBWindow 變為 LSWindow)的預期時,需要一種一次性裝入實現系統中各種建立對象的方法,這時抽象工廠就會發揮作用:
- 建立具體工廠類對象
- 將具體工廠類對象裝入抽象工廠類對象(引用、指標)
- 用抽象工廠類對象建立抽象構造塊類
- 使用抽象構造塊類的方法,它動態綁定到具體構造塊類的方法
1、2 是程式初始化階段執行的設定,3、4 是程式例行階段的行為
於是,抽象工廠是和抽象類別層次伴生的
Clone 模式^
UML: Clone Pattern
Why clone?
手上有一個對象,只知道它的抽象類別型(確切類型已丟失),要複製這種對象的大量副本,並且副本要和其確切類型一致
可以定義一個 Clonable 抽象基類,以規約 clone 函數,但不是必須的
Clone 模式可從函數 override 的傳回值類型的 covariance 協變中受益。VC 2005+ 支援協變
參考書籍^
- [CPP LANG] "The C++ Programming Language, Special Ed", Bjarne Stroustrup
- [EFFECT CPP] "Effective C++, 3Ed", Scott Meyers
- [CPP PRIMER] "C++ Primer, 3Ed", Stanley Lippman, Josee Lajoie
- [CPP OBJMODEL] "Inside The C++ Object Model", Stanley Lippman