接口繼承中一個常見問題的思考 以前在設計DirectUI界面庫(該界面庫現已開源, 可到 這裡 下載)架構時,遇到一個接口繼承相關的問題,當時沒有太好的解決方案,卻一直個耿耿於懷, 現在重新思考整理下。 我們的DirectUI控件層次大概如下: 其中, 類名以 I 開頭的都是接口: IObject表示框架的基本接口, 要求實現類似COM裡IUnknown的功能, IControl表示控件的基本接口, 所有控件都從該接口繼承, IControlContainer表示容器類控件的基本接口, IButton表示Button類的基本接口, IPanel表示某種容器控件接口。 當然上面的框架是簡化的情況,實際情況比上面的復雜的多, 但該圖已經可以幫我們說明這裡的情況。 在真正實現Panel和Button時,我們會發現大量的代碼是重復和可以共用的,因此在實際實現時, 我們的框架可能會變成這樣: 也就是說我們會出現接口和實現交叉繼承的情況,實際上我自己在實現時就是用這種方法的, 我想大部分人都會用這種方法(實際上WPF也是用這種方法的)。 這種方法的缺點是顯而易見的, 接口中包含了實現,基本上讓接口失去了它應有的作用, 這在組件式編程中是致命的,比如本來在C++中我可以封裝成DLL,然後以類似COM的方式暴露接口給外部, 現在用這種方式卻沒法做到了(只能用導出類的方式)。 那麼我們怎樣才能既基於接口編程, 又能在實現時實現代碼重用呢? 這個東西實際上是個語法糖, 即如何既符合C++語法又能實現我們這個需求。 於是,我們想到了如下的實現方式: 我們的這種實現方式基於C++模板, 總的來說就是把我們要實現的接口通過模板參數傳到繼承類體系的最底層, 該方式的代碼大概如下: class IObject { }; class IControl: public IObject { }; class IButton: public IControl { }; template<typename TBase> class CObjectImpl: public TBase { }; template<typename TBase> class CControlImpl: public TBase { }; template<typename T, typename TBase> class CButtonImpl: public TBase { }; class CButton: public CButtonImpl<CButton, IButton> { }; 該方式基本上完全滿足我們上面的需求,既實現了代碼重用,又是基於接口編程,但是你有沒有發現它有一個致命的缺點, 這個缺點就是C++模板導致的代碼膨脹, 我們在 C++模板會使代碼膨脹嗎 對模板導致的代碼膨脹有相關分析。也就是說我們上面的設計會導致每種控件繼承類都有一份重復的代碼, 即CControlImpl<IButton>和CControlImpl<IPanel>因為是不同的類實例, 因此它們會生成2分代碼。你可能會覺得這個不算什麼, 但是想想控件的繼承類可能有好幾十甚至上百,最終的可執行文件會被撐大不少。 那麼有沒有其他的方法來實現呢? 既能基於接口編程, 又能實現代碼重用,還沒有代碼膨脹的問題。 於是,我們想到了下面這種實現方式: 這種方式是最原始的方式, 實際上就是把接口體系單獨獨立出來, 把實現體系也單獨獨立出來, 然後在最終類(Button和Panel)裡繼承組合起來。 當然這種方式也有缺點, 就是我們要多做些工作,因為我們要在最終類(Button)裡實現接口(IButton), 在實現時我們要把所有接口需要實現的方法轉發給實現類(CButtonImpl)。 最後,總結下上面三種方法: 第一種實現和接口混合繼承的方法最簡單,也最容易理解, 缺點是沒法完全基於接口編程; 第二種基於模板的方法比較難理解,實現上也比較簡單, 缺點是代碼膨脹; 第三種多重繼承的方法也比較容易理解, 缺點是我們要多做一些工作。