面試經常會被問到的題目之一,面向對象的三大特徵是什麼?多態則是三大特徵之一,個人認為三大特徵中最為重要的,另外的兩大特徵是封裝和繼承。
為什麼說多態對軟件架構師非常重要,對系統軟件非常重要呢?舉個例子,當軟件面向一個客戶的時候,你會發現軟件寫得很簡單,很快就能滿足其需求。隨著時間的推移,軟件面向的不再是一個客戶。每個客戶提出的需求千差萬別,尤其當出現針對性的、個性化的需求。軟件的迭代、升級會變得相對困難,拓展功能變得困難。
然而,多態很好的幫助我們解決該問題。多態對源代碼的依賴關係具有很好的控制能力,這種能力讓軟件架構師可以構建出插件式架構,讓高層策略性組件與底層實現性組件分離,底層組件可以被編譯成插件,實現獨立於高層組建的開發和部署。
也就是說當我們很好的運用多態,有三大優勢:
1、控制源代碼的依賴關係;
2、組件獨立部署能力;
3、組件獨立開發能力;
Robert C. Martin 在《整潔架構之道》中很好的闡述了多態的強大性,今天我們來學習一下。
以下:
依賴反轉
我們可以想象一下在安全和便利的多態支持出現之前,軟件是什麼樣子的。下面有一個典型的調用樹的例子,main 函數調用了一些高層函數,這些高層函數又調用了一些中層函數,這些中層函數又繼續調用了一些底層函數。在這裡,源代碼層面的依賴不可避免地要跟隨程序的控制流。
圖:源代碼依賴與控制流的區別
如你所見,main 函數為了調用高層函數,它就必須能夠看到這個函數所在的模塊。在 C 中,我們會通過 #include 來實現,在Java 中則通過 import 來實現。而在 C#中則用的是 using 語句。總之,每個函數的調用都必須要引用被調用放所在的模塊。
顯然,這樣做就導致了我們在軟件架構上別無選擇。在這裡,系統行為決定了控制流,而控制流則決定了源代碼依賴關係。
當我們使用了多態,情況就不一樣了。
圖:依賴反轉
如你所見,模塊 HL1 調用 ML1 模塊中的 F() 函數,這裡的調用是通過源代碼級別的接口來實現的。當函數在程序實際運行時,接口這個概念是不存在的,HL1 會調用 ML1 的 F() 函數。
模塊 ML1 和接口 I 在源代碼上的依賴關係或者叫繼承關係,該關係的方向和控制流正好是相反的,這就是依賴反轉。這種反轉對軟件架構設計的影響非常大。
事實上,通過利用面向對象編程語言所提供的這種安全便利的多態實現,無論我們面對怎樣的源代碼級別的依賴關係,都可以將其反轉。
通過這種方法,軟件架構可以完全控制採用了面向對象這種編程方式的系統中所有的源代碼依賴關係,而不再受到系統控制流的限制。不管哪個模塊調用或者被調用,軟件架構師都可以隨意更改源代碼依賴關係。
接下來我們看一個案例:典型的系統分為用戶層、業務層、數據庫。通常我們的依賴關係是用戶層 Controller 調用業務的 Service,業務層調用數據庫層 DAO 或者資源庫 Repository。
圖:數據庫和用戶界面都依賴於業務邏輯
這意味著我們讓用戶界面和數據庫都成為業務邏輯的插件。也就是說,業務邏輯模塊的源代碼不需要引入用戶界面和數據庫這兩塊。
這樣一來,業務邏輯、用戶界面以及數據庫就可以編譯成三個被獨立的組件或者部署單元(例如 jar 文件、DLL 文件、Gen 文件等),這些組件或者部署單元的依賴關係與源代碼的依賴關係是一致的,業務邏輯組件也不會依賴於用戶界面和數據庫這兩個組件。
於是,業務邏輯組件就可以獨立於用戶界面和數據庫來進行部署了,我們對用戶界面或者數據庫的修改將不會對業務邏輯產生任何影響,這些組件都可以被分別 獨立部署。
簡單來說,當某個組件的源代碼需要修改時,僅僅需要重新部署該組件,不需要更改其他組件,這就是獨立部署的能力。
如果系統中的所有組件都可以獨立部署,那它們就可以由不同的團隊並行開發。這就是所謂的獨立開發能力。
總結
看似簡單,實則大部分程序員並沒將其運用到各自到系統軟件中。前段時間微服務很火、DDD也很熱,很多思想都離不開多態。系統軟件存在的意義是解決用戶問題,用戶問題則是核心部分,也就是用戶業務邏輯。如何有效的分離系統軟件的業務邏輯,控制與業務邏輯無關的代碼,從而降低軟件複雜度是一名優秀的工程師或者架構師必經之路。