1. 程式人生 > >【小家java】POP(面向過程程式設計)、OOP(面向物件程式設計)、AOP(面向切面程式設計)三種程式設計思想的區別和聯絡

【小家java】POP(面向過程程式設計)、OOP(面向物件程式設計)、AOP(面向切面程式設計)三種程式設計思想的區別和聯絡

相關閱讀

【小家java】java5新特性(簡述十大新特性) 重要一躍
【小家java】java6新特性(簡述十大新特性) 雞肋升級
【小家java】java7新特性(簡述八大新特性) 不溫不火
【小家java】java8新特性(簡述十大新特性) 飽受讚譽
【小家java】java9新特性(簡述十大新特性) 褒貶不一
【小家java】java10新特性(簡述十大新特性) 小步迭代
【小家java】java11新特性(簡述八大新特性) 首個重磅LTS版本


前言

現在很多語言都是面向物件(OOP)的程式設計思想,比如Java、Python、JS等為代表。然後聊起面向物件,雖然我們一直在用,但真的問起你什麼叫面向物件時,還是很難有一個具相的回答,因此本文嘗試以OOP為中心,以講解和對比的方式,聊聊這三種程式設計思想。

OOP前夕:POP

談起了OOP,我們就不得不瞭解一下POP即面向過程程式設計,它是以功能為中心來進行思考和組織的一種程式設計方式,強調的是系統的資料被加工和處理的過程,說白了就是注重功能性的實現,效果達到就好了,而OOP則注重封裝,強調整體性的概念,以物件為中心,將物件的內部組織與外部環境區分開來。之前看到過一個很貼切的解釋,博主把它們畫成一幅圖如下:
POP和OOP的對比圖
在這裡我們暫且把程式設計比喻為房子的佈置,一間房子的佈局中,需要各種功能的傢俱和潔具(類似方法),如馬桶、浴缸、天然氣灶,床、桌子等。

  • POP設計師:對於面向過程的程式設計更注重的是功能的實現(即功能方法的實現),效果符合預期就好,因此面向過程的程式設計會更傾向圖1設定結構,各種功能都已實現,房子也就可以正常居住了
  • OOP設計師:但對於面向物件的程式設計則是無法忍受的,這樣的設定使房子內的各種傢俱和潔具間擺放散亂並且相互暴露的機率大大增加,各種氣味相互參雜,顯然是很糟糕的,於是為了更優雅地設定房屋的佈局,面向物件的程式設計便採用了圖2的佈局

對於面向物件程式設計來說這樣設定好處是顯而易見的,房子中的每個房間都有各自的名稱和相應功能(在java程式設計中一般把類似這樣的房間稱為類,每個類代表著一種房間的抽象體),如衛生間是大小解和洗澡梳妝用的,臥室是休息用的,廚房則是做飯用的,每個小房間都各司其職並且無需時刻向外界暴露內部的結構,整個房間結構清晰,外界只需要知道這個房間並使用房間內提供的各項功能即可(方法呼叫),同時也更有利於後期的拓展了,畢竟哪個房間需要新增那些功能,其範圍也有了限制,也就使職責更加明確了(單一責任原則)。

POP和OOP的聯絡

OOP的出現對POP確實存在很多顛覆性的,但並不能說POP已沒有價值了,畢竟只是不同時代的產物,從方法論來講,更喜歡將面向過程與面向物件看做是事物的兩個方面–區域性與整體(你必須要注意到區域性與整體是相對的),因此在實際應用中,兩者方法都同樣重要。

瞭解完OOP和POP各自的特點,接著看java程式設計過程中OOP應用

Java中使用OOP

在java程式設計過程中,我們幾乎享盡了OOP設計思想帶來的甜頭,以至於在這個一切皆物件,眾生平等的世界裡,狂歡不已,而OOP確實也遵循自身的宗旨即將資料及對資料的操作行為放在一起,作為一個相互依存、不可分割的整體,這個整體美其名曰:物件

利用該定義對於相同型別的物件進行分類、抽象後,得出共同的特徵,從而形成了類,在java程式設計中這些類就是class

由於類(物件)基本都是現實世界存在的事物概念(如前面的不同的小房間)因此更接近人們對客觀事物的認識,同時把資料和方法(演算法)封裝在一個類(物件)中,這樣更有利於資料的安全,一般情況下屬性和演算法只單獨屬於某個類,從而使程式設計更簡單,也更易於維護。

AOP的出現

基於這套理論思想,在實際的軟體開發中,整個軟體系統事實也是由系列相互依賴的物件所組成,而這些物件也是被抽象出來的類。相信大家在實際開發中是有所體驗的(本篇檔案假定讀者已具備面向物件的開發思想包括封裝、繼承、多型的知識點)。但隨著軟體規模的增大,應用的逐漸升級,慢慢地,OOP也開始暴露出一些問題,現在不需要急於知道它們,通過案例,我們慢慢感受:
A類:

public class A {
    public void executeA(){
        //其他業務操作省略......
        recordLog();
    }

    public void recordLog(){
        //....記錄日誌並上報日誌系統
    }
}

B類:

public class B {
    public void executeB(){
        //其他業務操作省略......
        recordLog();
    }

    public void recordLog(){
        //....記錄日誌並上報日誌系統
    }
}

C類:

public class C {
    public void executeC(){
        //其他業務操作省略......
        recordLog();
    }

    public void recordLog(){
        //....記錄日誌並上報日誌系統
    }
}

假設存在A、B、C三個類,需要對它們的方法訪問進行日誌記錄,在程式碼中各種存在recordLog方法進行日誌記錄並上報,或許對現在的工程師來說幾乎不可能寫出如此糟糕的程式碼,但在OOP這樣的寫法是允許的,而且在OOP開始階段這樣的程式碼確實並大量存在著,直到工程師實在忍受不了一次修改,到處挖墳時(修改recordLog內容),才下定決心解決該問題,為了解決程式間過多冗餘程式碼的問題,工程師便開始使用下面的編碼方式

//A類
public class A {
    public void executeA(){
        //其他業務操作省略...... args 引數,一般會傳遞類名,方法名稱 或資訊(這樣的資訊一般不輕易改動)
        Report.recordLog(args ...);
    }
}

//B類
public class B {
    public void executeB(){
        //其他業務操作省略......
        Report.recordLog(args ...);
    }
}

//C類
public class C {
    public void executeC(){
        //其他業務操作省略......
        Report.recordLog(args ...);
    }
}

//record
public class Report {
    public static void recordLog(args ...){
        //....記錄日誌並上報日誌系統
    }
}

另外一種是通過繼承的方式,父類書寫一份列印日誌的方法即可。

顯然程式碼冗餘也得到了解決,這種通過繼承抽取通用程式碼的方式也稱為縱向拓展,與之對應的還有橫向拓展(這就是AOP)。

AOP面向切面程式設計

事實上有了上述兩種解決方案後,在大部分業務場景的程式碼冗餘問題也得到了實實在在的解決,原理如下圖:
在這裡插入圖片描述
但是隨著軟體開發的系統越來越複雜,工程師認識到,傳統的OOP程式經常表現出一些不自然的現象,核心業務中總摻雜著一些不相關聯的特殊業務,如日誌記錄,許可權驗證,事務控制,效能檢測,錯誤資訊檢測等等,這些特殊業務可以說和核心業務沒有根本上的關聯而且核心業務也不關心它們,比如在使用者管理模組中,該模組本身只關心與使用者相關的業務資訊處理,至於其他的業務完全可以不理會,我們看一個簡單例子協助理解這個問題

public interface IUserService {

    void saveUser();

    void deleteUser();

    void findAllUser();
}
//實現類
public class UserServiceImpl implements IUserService {

    //核心資料成員

    //日誌操作物件

    //許可權管理物件

    //事務控制物件

    @Override
    public void saveUser() {

        //許可權驗證(假設許可權驗證丟在這裡)

        //事務控制

        //日誌操作

        //進行Dao層操作
        userDao.saveUser();

    }

    @Override
    public void deleteUser() {

    }

    @Override
    public void findAllUser() {

    }
}

上述程式碼中我們注意到一些問題,許可權,日誌,事務都不是使用者管理的核心業務,也就是說使用者管理模組除了要處理自身的核心業務外,還需要處理許可權,日誌,事務等待這些雜七雜八的不相干業務的外圍操作,而且這些外圍操作同樣會在其他業務模組中出現,這樣就會造成如下問題

  • 程式碼混亂:核心業務模組可能需要兼顧處理其他不相干的業務外圍操作,這些外圍操作可能會混亂核心操作的程式碼,而且當外圍模組有重大修改時也會影響到核心模組,這顯然是不合理的。
  • 程式碼分散和冗餘:同樣的功能程式碼,在其他的模組幾乎隨處可見,導致程式碼分散並且冗餘度高。
  • 程式碼質量低擴充套件難:由於不太相關的業務程式碼混雜在一起,無法專注核心業務程式碼,當進行類似無關業務擴充套件時又會直接涉及到核心業務的程式碼,導致拓展性低。

顯然前面分析的兩種解決方案已束手無策了,那麼該如何解決呢?事實上我們知道諸如日誌,許可權,事務,效能監測等業務幾乎涉及到了所有的核心模組,如果把這些特殊的業務程式碼直接到核心業務模組的程式碼中就會造成上述的問題,而工程師更希望的是這些模組可以實現熱插拔特性而且無需把外圍的程式碼入侵到核心模組中,這樣在日後的維護和擴充套件也將會有更佳的表現,假設現在我們把日誌、許可權、事務、效能監測等外圍業務看作單獨的關注點(也可以理解為單獨的模組),每個關注點都可以在需要它們的時刻及時被運用而且無需提前整合到核心模組中,這種形式相當下圖:
AOP
從圖可以看出,每個關注點與核心業務模組分離,作為單獨的功能,橫切幾個核心業務模組,這樣的做的好處是顯而易見的,每份功能程式碼不再單獨入侵到核心業務類的程式碼中,即核心模組只需關注自己相關的業務,當需要外圍業務(日誌,許可權,效能監測、事務控制)時,這些外圍業務會通過一種特殊的技術自動應用到核心模組中,這些關注點有個特殊的名稱,叫做“橫切關注點”,上圖也很好的表現出這個概念,另外這種抽象級別的技術也叫AOP(面向切面程式設計)

至於AOP在Java中的實現,可以參見我的下一篇博文,結合具體案例進行講解