如何理解面向過程、面向物件、面向切面?它們有什麼區別?
我們總會聽見如下詞語:
面向過程程式設計(opp)
面向物件程式設計(oop)
面向切面程式設計(aop)
上面幾個詞語,都是程式設計正規化。
程式設計正規化是指:程式設計時所秉承的思想和風格,不同的程式語言對各大程式設計正規化會有不同程度的支援。
不同的程式設計正規化各自會有自己的優點,它們適用在各種不同的情況下:面向過程效能很高,面向物件比較易於管理和維護,面向切面使軟體變得更靈活。
在理解程式設計正規化之前,我們一定要知道:
新的程式設計正規化,並不一定完全各方面都優於舊的程式設計正規化,它們只是在某一特定領域或特殊場景下有著獨到的優勢。
程式設計正規化只有適合不適合專案特性,沒有絕對的好壞。
OPP和OOP
OPP是較貼近計算機執行的程式設計正規化。
它主要關注“怎麼做”,即完成任務的具體細節。
OOP則是比較貼近人類思維的一種程式設計正規化,它主要關注的是“誰來做”,也就是完成任務的物件,而非細節。
面向過程會將一個任務拆分成各個小的步驟,然後用一個個函式來實現每一個小步驟,再在主函式中依次呼叫這些函式,以此來完成任務。
面向物件則是從一個任務中抽取出各個角色(物件),這些角色分別提供了一些能力,然後,這些擁有能力的物件互相配合,完成任務,這樣的好處是:各個物件可以通過不同的組合,完成不同的任務,這樣可以提升程式碼重用,對程式碼做好分類和分級
舉例對比
比如完成吃飯這個任務。
面向過程的寫法,需要封裝一個eat()函式:
- 如果是狗吃飯,則eat(狗,飯)
- 如果是人吃肉,則eat(人,肉)
如上,eat是人和狗共用的吃飯能力。
那麼,如果狗吃飯要嚼10下,人吃肉要嚼20下。
這時候,我們就需要在eat函式中,判斷傳入的第一個引數是人還是狗,然後再執行嚼的次數。
那如果之後要處理貓吃魚、老虎吃人、貓走路、老虎走路…
eat函式中就會存在大量的if…else的判斷,這段程式碼,無疑是很噁心的,另外,因為存在走路的功能,還需要定義一個噁心的walk函式,這不易於維護、升級、擴充套件。
但如果是面向物件,如何來解決這個問題呢?
這裡涉及到“抽象”這個思維。
我們可以發現,狗、貓、人、老虎,它們都有吃的能力,所以,我們需要抽象出它們的祖先:會吃東西的動物 。
然後,再抽象出狗、貓、人、老虎,繼承會出東西的動物,以此來決定它們都具備吃東西的能力。
接著,解決上面的問題。
狗、貓、人、老虎各自有自己的eat方法,也各自有自己的walk方法。
當我們想要進行狗吃肉,那就“狗->eat(肉)”,這樣,我們從面向過程維護eat的焦點,轉移到了面向物件維護角色的焦點上來。我們只需要維護好不同的角色(類)就好了,並且狗的eat不會影響到貓的eat,貓的eat也不會影響到人的eat。
所以,oop思想非常貼近軟體工程高內聚的思想:自己管好自己的東西,自己做好自己的事情。
上面,主要是從思想上講述了opp和oop之間的差異,如果深入oop,不同的語言也會提供不同程度的支援,比如繼承、多型、封裝這些,比較深入的內容,不是本文討論的主題。
大多數支援面向物件的語言,同時也支援面向過程,不論是java、php,還是javascript,它們都還無法完全面向物件,因為面向過程是必然的,面向過程代表著必要的程式流程,調動物件進行組合或物件內部能力的實現,都一定會存在“過程”,它最終還是需要通過拆分步驟來指導最具體的執行細節。
在此,我們也能得到一些感悟,許多事情並非完全非黑即白,非oop就必然是opp,特別是思想層面的東西,它們呈現出互相結合的形態,從opp到oop,這是一個思想進步的過程,也是人們遇到“管理問題”、“可持續發展問題”後對“管理”和“發展”提出的討論和求證。
AOP
面向切面程式設計,AOP也是一種程式設計正規化,是對oop的延續。
即:基於oop延伸出來的程式設計思想,它進一步降低專案合作、維護、擴充套件、多人協作的成本,提高程式的內聚性,降低程式的耦合度。
那麼,AOP如何體現?
這裡,可以聯想一下laravel的中介軟體、javaweb的攔截器、vue的Decorator…它們都是AOP思想的實踐。
也可以聯想一下,裝飾器模式、代理模式,它們也是基於AOP思想的設計模式。
這裡以laravel的中介軟體(middleware)作為詳細例子:
middleware所執行的地方,就是一個切面,這個切面上承路由,下接controller。
想象一下,laravel從接收到請求一路執行下來,到中介軟體這裡,就好像是被切開了一個平整面,這就是面向切面。
而AOP思想,指導我們通過找到平整切面的形式,插入新的程式碼,使新插入的程式碼對切面上下原有流程的傷害降到最低。
舉更具體的例子:
我們拿laravel中介軟體做什麼?
許可權、日誌、請求過濾、請求頻率限制、csrf過濾……
我們知道,中介軟體對於controller的業務邏輯,不會有任何傷害。
如果沒有這個切面,我們想要記錄請求日誌,可能需要在每個controller的具體方法中寫日誌記錄的程式碼,或者呼叫日誌記錄的函式、方法。
這會使一段記錄日誌的程式碼,或呼叫記錄日誌的呼叫語句出現在許多controller中,這與controller原本要關注的邏輯無關,使controller職責不單一,提高維護成本。
當然,我們可能會寫一個父類,讓許多controller來繼承這個父類,然後統一在父類的__construct方法中記錄日誌,以此來解決耦合問題。
但實際上,這不父類的construct方法,不正是一個切面嗎?它在原有流程中截取了一個切面,在切面中植入程式碼,以達到承上啟下的作用,並且不對上下文產生傷害。
從這個例子中,我們也能得出另外一個思考:AOP指導我們尋找切面,但找到合適的切面,也尤為重要。就像上文,父類建構函式的切面和中介軟體的切面比起來,顯然中介軟體這個切面更利於維護,你可以靈活選擇中介軟體,但你無法靈活選擇父類,因為決定你的controller繼承什麼父類的,不是切面中的程式碼,而是controller本身處理什麼邏輯。
另外,TP常會提到的鉤子和行為,也是AOP思想的一種實踐。
aop的思想,是對oop的補充。
而oop又是對opp的升級。