原貼:https://coolshell.cn/articles/8961.htmlhtml
前些天發了一篇《如此理解面向對象編程》的文章,而後引發了你們的熱議。而後我在微博上說了一句——「那23個經典的設計模式和OO半毛錢關係沒有,只不過人家用OO來實現罷了……OO的設計模式思想和Unix的設計思想基本沒什麼差異」,結果引來了一點點爭議。因此,我寫下這篇文章把個人觀點說明一下。我但願這樣可讓你們更容易地理解什麼是設計模式。我順便幫OO和 Unix/Linux搞搞基。前端
在正式說明GoF的那23個經典的設計模式其實和OO關係不大並和Unix的設計思想很類似的這個觀點以前,讓我先來講說什麼是模式?設計模式的英文是Design Pattern,模式是Pattern的漢譯。所謂Pattern就是一種規則,或是一種模型,或是一種習慣。Pattern這個東西處處都是,並不僅有技術圏子裏纔有。好比:面試
這就是Pattern,只要你細心觀察,你會發現這世間有不少不少的Pattern。算法
《設計模式》這本書中,GoF這四我的總結了23個經典的面向對象的設計模式,某中有5個建立模式,7個結構模式,11個行爲模式。不少人都會以爲這是面向對象的設計模式,不少人也以爲非面向對象不能用這些模式。我以爲這是一種教條主義。就像《那些流行的編程方法》中的「設計模式驅動型編程」同樣,就像《如此理解面向對象》同樣的那麼的滑稽。shell
好了,回到個人論點——「GoF的這23個設計模式和OO關係不大,而且和Unix的設計思想基本一致,只不過GoF用OO實現了它們」,就像我上面說過的那些生活中的Pattern同樣,只要你仔細思考,你會發現這23個設計模式在咱們的生活和社會中也能有他們的身影。並且也同樣能夠用OO的方式實現之。數據庫
讓咱們來看看這23個經典的設計模式中的幾個經常使用的模式:編程
Factory 模式,這個模式多是是我的都知道的模式。這個模式在現實社會中就像各類工廠同樣,工廠跨界的很少,基本上都是在生產同一類的產品,有的生產汽車,有的生產電視,有的生產衣服,有的生產衛生紙……基本上來講,一個生產線上只有作同一類的東西。這和Factory模式很類似。編程中,像內存池,線程池,鏈接池等池化技術都是這個模式,固然,Factory給你的一個對象,而不僅僅只是資源,factory建立出來的對象都有一樣的接口能夠被多態調用。這其實和Unix把全部的硬件都factory成文件同樣,並提供了read/write等文件操做來讓你操做任意設備的I/O。後端
Abstract Factory:抽象工廠這個模式是建立一組有同一主題的不一樣的類。這個模式在現實社會當中也有不少例子,好比:設計模式
這就是抽象工廠的業務模型(或是:Business Pattern),你以爲是否是不必定非要用OO來實現這樣的模式?(咱們思考一下,咱們會不會被先入爲主了,以爲不會OO都不知道怎麼實現了),不用OO,用相同格式但內容不一樣的配置文件是否是也能實現?在Unix下,抽象工廠這個模式在Unix下就像是/etc/rcX.d下的那些東西,1表明命令行單用戶,2,表明命令行多用戶,3表明命令行多用戶完整模式啓動,5表明圖形界面啓動,0表明關機,6表明重啓,你要切換的話,init <X>就好了。數組
Prototype模式,原型模式,複製一個類的實現。這個模式在現實中的例子也有不少:傳真,複印,都是這個模式。Unix進程和Github項目的Fork就是一種。進程fork明顯不是OO的模型(參看:關於Fork的一道面試題)。用非OO的方法一樣能夠實現這個模式。
Singleton模式,單例模式。生活中,公司只有一個CEO,法律限制你只能有一個老婆,你只能有一個身份證號,一個TCP端口只能被一個進程使用,等等。軟件開發方面,並不必定只有OO才能作到,你能夠用一個全局變量,一箇中心服務器,甚至可使用行政手段來約束開發中不會出現多個實例。Unix下實現單例進程的一個最經常使用的實踐是在進程啓動的時候用「(S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)」模式打開一個「鎖文件」。
Adapter模式,適配器模式。能夠兼容歐洲美國中國的插頭或插座,萬能讀卡器,能夠播放各類格式多媒體文件的插放器,能夠解析FTP/HTTP/HTTPS/等網絡協議的瀏覽器,能夠兼容各大銀行的銀聯接口、支付寶、Paypal、VISA等銀行接口,能夠適配各類後端的解釋器的Nginx或Apache,等等。用非OO的編程方式就是從新包裝成一個標準接口。這個模式很像Unix下的/dev下的那些文件,操做系統把系統設備適配成文件,因而你就可使用read/write來進行讀寫了。
Bridge模式,橋接模式。這個模式用的更多,好比一個燈具能夠接各類燈泡或燈管,一個電鑽能夠換上不一樣的鑽頭來適應不一樣的材料,一輛汽車能夠隨時更換不一樣的輪胎來適應不一樣的路面,你的桌面能夠隨時更換一個圖片來適應你的心情,你的單反相機能夠更換不一樣的鏡頭來拍不一樣的照片…… 橋接模式說白了就是組件化,模塊化,能夠自由拼裝。在OO中,其主要是經過讓業務類組合一個標準接口來完成,這在非OO的程序設計中用得實在是太多了,主要是經過回調函數或是標準接口來實現。這個也是Unix設計哲學中的主要思想。在Unix中,文件的權限使用的就是Bridge模式,標準接口是用戶,用戶組和其它,rwx三個模式,而後用 chmod/chown改一改,這文件就有不一樣的屬主和屬性了。
Decorator模式,裝飾模式。這個模式在生活中太多了,你給你的手機或電腦貼個什麼,掛個什麼,吃東西的時候加點什麼佐料,多點肉仍是多個蛋,一個Unix/Linux命令的各類參數是對這個命令的修飾,等等。我以爲這個模式在Unix中最常常的體現就是經過管道把命令鏈接起來來完成一個功能,好比:ps -elf 是列進程的,用管道 grep hchen就能夠達到過濾的目的,grep的邏輯沒有侵入ps中,grep 修飾了 ps,可是其組合起來完成了一個特定的功能。可見,這和OO沒有什麼關係。
Facade模式,這個模式咱們每一個人從會編程的時候就在無心識地用這個模式了。這個模式就是把一大堆類拼裝起來,並統一往外提供提口。在現實生活中這樣的例子太多了,好比:旅行社把機票,酒店,景點,導遊,司機,進店打了一個包叫旅行;IBM把主機,存儲,OS,J2EE,DB,網絡,流程打了個包叫企業級解決方案。Unix中最典型的一個例子就是用Shell腳本組合各類命令來創造一個新的功能,這是的Shell中的各類命令經過標準I/O這個接口進行組合交互。
Proxy模式,代理模式。咱們租個房,買個機票,打個官司,都少不了代理,人大表明代理了老百姓去行使政治權力。咱們去飯館裏吃飯也是一種代理模式,由於咱們只管吃就行了,洗菜作飯洗碗的工做都被Proxy幫你幹了,因而你就省事多了。操做系統就是硬件的代理,CDN就是網站的代理,……使用代理你可讓事情變理更簡單,也能夠在代理層加入一些權限檢查,這樣可讓業務模塊更關注業務,而把一些非業務的事情剝離出來交給代理以完成解耦。可見這個模式和OO沒啥關係。Unix下這個模式最佳體現就是Shell,它代理了系統調用並提供UI。還有不少命令會幫你把/proc目錄下的那些文件內容整理和顯示出來。
Chain of Responsibility模式,劫匪來搶銀行,保安搞不定,就交給110,110搞不定就交給武警。有什麼事件發生時的響應的Escalation Path,辦公中的逐級審批。這個模式用一個函數指針數組或是棧結構就能夠實現了。這個思想很像編程中的異常處理機制,一層一層地往上傳遞異常直到異常被捕捉。在Unix下,一個最簡單的例子就是用 && 或 || 來把命令拼起來,如:cmd1 && cmd2 或 cmd3 || cmd4 , 若是cmd1失敗了,cmd2就不會執行,若是cmd3失敗了,cmd4纔會執行。如: cd lib && rm -rf .o 或 ping -c1 coolshell.cn && ssh haoel@coolshell.cn
Command模式,這恐怕是軟件裏最多的模式了,好比:編譯器裏的Undo/Redo,宏錄製。還有數據庫的事務處理,線程池,設置嚮導,包括程序並行執行的指令集等等。這個模式主要是把一個對象的行爲封裝成一個一個的有相同接口的command,而後交給一個統一的命令執行器執行或管理這些命令。這個模式和咱們的Unix/Linux機器啓動時在/etc/init.d下的那些S和K開頭的腳本很像,把各類daemon的啓動和退出行爲封裝成一個腳本其支持reload/start/stop/status這樣的命令,而後把他們按必定的規範作符號連接到/etc/init.d目錄下,這樣操做系統就會接管這些daemon的啓動和退出。
Observer模式,觀察者模式,這個模式也叫pub-sub模式,很像咱們用手機訂閱手機報,微博的follow的信息流也是這樣的一個模式。MVC中的C會sub V中的事件,用非OO的方式其實也是一個回調函數的事。在不少異步系統中,你須要知道最終的調用有沒有成功,好比說調用支付寶的支付接口,你須要向支付寶註冊一個回調的接口,以便支付寶回調你。Linux下的一些系統調用如epoll/aio/inotify/signal都是這種思路。
Strategy 模式,策略模式,這個模式和Bridge模式很像,只不過Bridge是結構模式,其主要是用於對象的構造;而Strategy是行爲模式,主要是用於對象的行爲。策略模式很像瀏覽器裏的各類插件,只要你裝了某個插件,你就有某個功能。你能夠安裝多個插件來讓你的瀏覽器有更多的功能(書本上的這個模式是你只能選用一個算法,固然,咱們不用那麼教條)。就像《你可能不知道的Shell》中的那個設置設置$EDITOR變量後能夠按ctrl+x e啓動編譯器,或是用set -o vi或set -o emacs 來讓本身的shell像vi或 emacs 同樣,或是像find -exec或xargs同樣的拼裝命令。
Bridge 和 Strategy是OO設計模式裏的「Favor Composition Over Inheritance」 的典範,其實現了接口與實現分離的。Unix中的Shell就是一種,你可隨意地更換不一樣的Shell。還有Emacs中的LISP驅動C,C實現了引擎,交給LISP實現邏輯。把程序分爲前端和後端,經過socket專用應用協議進行通信,前端實現策略,後端實現機制。再看看makefile把編譯器和源代碼的解耦,命令行輸出這個接口能夠把一個複雜的功能解耦並抽像成各類各樣小而美的小功能命令,等等這樣的例子,你會發現,還有大量的編程框架都會多少採用這樣的思想,可讓你的軟件像更換汽車零件同樣方便。我在用Unix的設計思想來應對變動的需求中說過燈具廠,燈泡廠,和開關廠的例子。
由於寫做倉促,上面的那些東西,可能會你讓你以爲有些牽強,那麼抱歉了,你能夠幫我看看在生活中和 Unix裏有沒有更帥的例子。
不過,咱們會發現上面OO搞出來的那麼多模式在Unix下看來好像沒有那麼複雜,並且Unix下看起來並無那麼多模式,並且Unix中的設計模式無非就是這麼幾個關鍵詞:單一,簡潔,模塊,拼裝。咱們再來看看OO設計的兩大準則:1)鍾情於組合而不是繼承,2)依賴於接口而不是實現。還有S.O.L.I.D原則也同樣(若是你仔細觀察,你會發現SOLID原則在Unix下也是完美地體現)。你看,Unix和OO設計模式是否是完美的統一嗎?
我有種強烈的感受——Unix對這些所謂的OO的設計模式實現得更好。由於Unix就一條設計模式!再次推薦《The Art of Unix Programming》
我上面提到了《The Art of Unix Programming》,因此我有必要再談談這本書中我中毒最深的一章《模塊性:保持清晰和簡潔》中所談到的膠合層。
膠合層這一節中說了,咱們開發軟件通常要麼Top-Down,要麼Bottom-Up,這兩種方法都有好有很差。頂層通常是應用邏輯層,底層通常是原語層(我理解爲技術沉澱層,或是技術基礎層)。自頂向下的開發,你可能會由於開發到底層後發現底層可沉澱的東西愈來愈不爽(由於被可能被不少業務邏輯所侵入),若是自底向上的開發,你可能越到上層你愈加現不少你下面乾的基礎上工做有不少用不上(好比干多了)。因此,最好的方式是同時進行,一會頂層,一會底層,來來回回的開發——說白了就是在開發中不斷的重構,邊開發邊理解邊沉澱。
不管怎麼樣,你會發現須要一層膠合層來膠合業務邏輯層和底層原語層(軟件開發中的業務層和技術層的膠合),Unix的設計哲學認爲,這層膠合層應該儘可能地薄,膠合層越多,咱們就只能在其中苦苦掙扎。
其實,膠合層原則就是分離原則上更爲上層地體現,策略(業務邏輯)和機制(基礎技術或原語)的清楚的分離。你能夠看到,OO和Unix都是在作這樣的分離。可是須要注意到的時,OO用抽象接口來作這個分離——不少OO的模式中,抽象層太多了,致使膠合層太過於複雜了,也就是說,OO鼓勵了——「厚重地膠合和複雜層次」,反而增長了程序的複雜度(這種狀況在惡化中)。而Unix採用的是薄的膠合層,薄地至關的優雅。(經過這段話的描述,我相信你會明白了《如此理解面向對象編程》中的個例子——爲何用OO來實現會比用非OO來實現更爲地噁心——那就是由於OO膠合層太複雜了)
OO的最大的問題就——接口複雜度過高,膠合層太多!(注:Unix編程藝術這本書裏說了軟件有三個複雜度:代碼量、接口、實現,這三個東西構成了咱們的軟件複雜度)