在設計介面層級時,常面臨「介面繼承」與「實作重用」的衝突。以參考計數介面 IRefCount 為例,若 IReader、IWriter 皆繼承它,具體類別 Reader、Writer 就必須重複實作 add()、release()、dispose() 等方法,違反DRY 原則。若將預設實作放入基類,又會強制所有衍生類採用相同的記憶體管理策略(如必須 heap 分配),犧牲靈活性。更棘手的是菱形繼承問題:IRWiter : IReader, IWriter 會導致兩個 IRefCount 子物件,虛擬繼承雖能解決但引入資料成員佈局限制與設計複雜度。
解決方案是將實作封裝為模板類別,以「混入」方式組合,而非透過繼承硬編碼。定義通用實作模板:
template <typename Base>
class ImpReader : public Base {
constraint(is_base_derive(IReader, Base));
// 實作 IReader 介面
};
此時 ImpReader 不直接繼承 IReader,而是接受任意基底 Base,並在編譯期靜態斷言確保 Base 滿足介面需求。具體類別透過 typedef 組合:
typedef ImpReader< ImpHeapRefCount< IReader > > HeapReader;
typedef ImpReader< ImpStackRefCount< IReader > > StackReader;
typedef ImpReader< ImpPoolRefCount< IReader > > PoolReader;
實際繼承鏈:ImpReader → ImpHeapRefCount → IReader → IRefCount。每個「磚塊」只負責單一職責,客戶端自由組裝,達到介面與實作完全分離、實作細粒度重用、設計決策延遲至使用端。
將 IRefCount 實作拆分為共用部分與策略部分:
template <typename Base>
class ImpPartialRefCount : public Base {
// 實作 add、release、count 共用邏輯
};
template <typename Base>
class ImpHeapRefCount : public Base {
virtual void dispose() { delete this; }
};
template <typename Base>
class ImpStackRefCount : public Base {
virtual void dispose() { } // 堆疊物件不刪除
};
組合時:typedef ImpReader< ImpHeapRefCount< ImpPartialRefCount< IReader > > > HeapReader;。這展示了單一介面可拆分為多個部分實作類別,再由使用端按序組合的強大能力。
Combine 輔助模板當物件需同時支援多個獨立介面(如 IFileReader、IHttpReader 皆繼承 IReader),直接多重繼承會導致共用基底實作衝突。引入 Combine 將介面「並列」而非「串聯」:
template <typename B1, typename B2>
struct Combine : B1, B2 {
typedef B1 type1; typedef B2 type2;
};
// 使用:每個分支擁有獨立的 IReader 實作
typedef Reform<
Combine< ImpFileReader< ImpReaderA< IFileReader > >,
ImpHttpReader< ImpReaderB< IHttpReader > > >,
ImpRefCount
>::type ConcreteFileHttpReader;
此技術避免了中間介面 IFileHttpReader 的概念混淆,並允許不同分支使用不同的 IReader 實作。
Reform 簡化嵌套 typedef為改善深層嵌套 ImpA< ImpB< ImpC< ... > > > 的可讀性,提供 Reform 變參模板:
template <typename I, template<class> class B1,
template<class> class B2 = Empty, ... >
struct Reform {
typedef typename Merge< ... Merge< Merge< I, B1 >::type, B2 >::type ... >::type type;
};
// 使用:
typedef Reform< InterfaceN, ImpInterface0, ImpInterface1, ..., ImpInterfaceN >::type ConcreteClassN;
this->Method() 而非 BaseClass::Method(),保持虛擬函式多態性。template<typename Base> class SomeComponent : public ImpPartA< ImpPartB< Base > > {} 封裝常用組合,降低使用端複雜度。模板類別通常需放在標頭檔,暴露實作細節。透過 Pimpl (Pointer to Implementation) 將核心邏輯移至 .cpp:
// 標頭檔
class ConcreteImpReader; // 前向宣告
template <typename Base>
class ImpPartialReader : public Base {
Pimpl<ConcreteImpReader> m_reader;
public:
ImpPartialReader() : m_reader(this), Base() {}
virtual void read(Buffer& buf, size_t size) { m_reader->read(buf, size); }
};
// 實作檔
class ConcreteImpReader {
IReader* m_host;
public:
ConcreteImpReader(IReader* host) : m_host(host) {}
void read(Buffer& buf, size_t size) { /* 實際邏輯 */ }
};
ConcreteImpReader 可存取宿主物件的其他介面方法,但無法存取自身實作的方法(避免循環相依)。此法保留組合彈性同時隱藏實作細節。
此技術實現了:
代價:
正如《Imperfect C++》作者 Matthew Wilson 所言,這是在 C++ 現實世界中逼近「烏托邦」的設計技藝——在語言限制下,以模板元程式設計換取極致的彈性與重用。