歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

C++ 類層次結構的設計方法學

關於 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

設計要義:

  1. 使用 BBWindow 不是 IValBox 概念的基本部分。過分依賴 BBWindow,使得 BBWindow 難於替換

  2. 可變數據是實現的部分,當它侵入接口(抽象類)時,會影響接口的靈活性

  3. 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 時,大多數時候都這樣做,這是大多數人用 GUI 框架的方法
    問題不在絕對的 bad design 或 good design,而在於目標是什麼?Application Development vs. Library Development, Cross-platform vs. Dedicated-platform

    下面的改進設計比 Old Hierarchy 靈活而光鮮,但也帶來的額外的負擔(如理解和維護),Pros and Cons 自行抉擇

分離實現和接口^

UML: Separate Implementation and Interface

接口線:接口繼承形成的層次
實現線/擴展線:實現繼承形成的層次

設計要義:

  1. 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 之實現可以進一步擴展

  2. 替代技術: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

無需糾結上圖具體含義,這裡的設計推論更有意思:一個系統展現給用戶的應該是一個抽象類的層次結構,其實現所用的是一個傳統的層次結構
有點教條吧?試問,傳統的層級結構從何而來?我們要做一個橫跨多種 GUI 框架之上的框架麼?

重復基類^

用 virtual 基類消除 replicated base class 重復基類,見 [EFFECT CPP] Item 40

這種用法的目的有二:

  1. 粘合 (glue) 兩個不相干的類。這是我們應該忘掉的 virtual 多繼承,連 Bjarne 也說它是 crude, effective, and important, but not very interesting.
  2. 為了下面的共享實現,這是有邏輯意義的

共享實現^

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. 使用抽象構造塊類的方法,它動態綁定到具體構造塊類的方法

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
Copyright © Linux教程網 All Rights Reserved