淺談物件導向中的一些主要思想

糖拌蕃茄發表於2020-12-15

淺談物件導向中的一些主要思想

何為OOP

OOP是一種思想,即為物件導向程式設計,將資料和行為進行封裝並看作物件進行操作,這一點很多資料書籍都提過,OOP的核心是一種思想,是解決實際問題時需要的一種思考方式,在這裡,我想以一個例子切入,來談一談的對與OOP的理解。

人作為現實生活中的一個實體,我們可以很直觀的看到,人都會有姓名,年齡,體重,身高等等的一些公共屬性,除此之外,人還會說話,會吃飯,會睡覺等等一系列的行為,於是,我們進行總結,人是一種具有姓名、年齡、體重……且會說話、睡覺……的物類,而這個總結,幾乎適用於所有的人,於是,人類的概念被概括出來,而我們每一個人,即是人類這個概念中的具體實體,即為一個物件。然後,每個物件(實際生活中的你我他)在一個環境裡生活,交流,工作。

抽象:

以上這個例子,我們會現,我們首先是對一個實體,抽取這個實體群中所共有的屬性、動作,換言之,就是抽取了公共的部分,進行一個整合,然後進行一個類別的定義,這是一個很平常的思維方式,經過以上的一個過程,就得到了一個有描述的定義。對於程式設計中,OOP中需要對我們程式中的一些主體進行特徵的抽取、然後定義,這就是OOP的第一步,抽象,即將實際存在或需求中的事物進行泛化,提取公共部分,進行型別的定義。

物件:

生活中的你我他,是作為人類這個定義中的具體,即一個抽象型別的具體,我們將一個定義抽象出來之後,可以根據這個定義,任意的產生一個具體的例項,這就是程式設計中的Class與具體的new Object,物件是根據抽象出的型別的例項化,我們定義了人類的特徵和行為(即編寫了一個Class),便可以根據這個Class,產出一個具體的個體來(new 出一個物件),就像我們每個人生活在地球這個環境中交流,工作一樣,程式中的也是每個不同型別的具體物件,進行交流(通訊)、工作,在OOP設計的程式中,程式就是一個個不同物件的集合。

物件導向要明確的:

  • 一切皆是物件:在程式中,任何事務都是物件,可以把物件看作一個奇特的變數,它可以儲存,可以通訊,可以從自身來進行各自操作,你總是可以從要解決的問題身上抽象出概念性的元件,然後在程式中將其表示為一個物件。
  • 程式是物件的集合,它通過傳送訊息來告知彼此需要做什麼:程式就像是個自然環境,一個人,一頭豬,一顆樹,一個斧頭,都是這個環境中的具體物件,物件之間相互的通訊,操作來完成一件事,這便是程式中的一個流程,要請求呼叫一個物件的方法,你需要向該物件傳送訊息。
  • 每個物件都有自己的儲存空間,可容納其他物件:人會有手機,一個人是一個物件,一個手機也是一個物件,而手機可以是人物件中的一部分,或者說,通過封裝現有物件,可製作出新型物件。所以,儘管物件的概念非常簡單,但在程式中卻可達到任意高的複雜程度。
  • 每個物件都擁有其型別:按照通用的說法,任何一個物件,都是某個“類(Class)”的例項,每個物件都必須有其依賴的抽象。
  • 同一類所有物件都能接收相同的訊息:這實際是別有含義的一種說法,大家不久便能理解。由於型別為“圓”(Circle)的一個物件也屬於型別為“形狀”(Shape)的一個物件,所以一個圓完全能接收傳送給"形狀”的訊息。這意味著可讓程式程式碼統一指揮“形狀”,令其自動控制所有符合“形狀”描述的物件,其中自然包括“圓”。這一特性稱為物件的“可替換性”,是OOP最重要的概念之一。

針對OOP的一些設計原則

這部分開始之前,這裡推薦先熟悉一下,耦合與內聚:高內聚與低耦合

       開閉原則

定義軟體實體應當對擴充套件開放,對修改關閉

開閉原則的含義是:當應用的需求改變時,在不修改軟體實體的原始碼或者二進位制程式碼的前提下,可以擴充套件模組的功能,使其滿足新的需求。這裡的軟體實體可以是專案中劃分出的模組,類與介面,方法。開閉原則是物件導向程式設計的終極目標,它使軟體實體擁有一定的適應性和靈活性的同時具備穩定性和延續性。實現了該原則,對於測試時,我們只需要測試新增的功能即可,原有程式碼仍能正常執行,同時提高了程式碼的複用性,粒度越小,可複用性就越大。

下面通過一個具體的例子來闡述一下開閉原則

 

windows的桌面主題,我們應該比較熟悉,Windows 的主題是桌面背景圖片、視窗顏色和聲音等元素的組合。使用者可以根據自己的喜愛更換自己的桌面主題,也可以從網上下載新的主題。這些主題有共同的特點,可以為其定義一個抽象類(Abstract Subject),而每個具體的主題(Specific Subject)是其子類。使用者窗體可以根據需要選擇或者增加新的主題,而不需要修改原始碼,所以它是滿足開閉原則的經典例子。

里氏替換原則

定義:繼承必須確保超類所擁有的性質在子類中仍然成立

里氏替換原則主要闡述了有關繼承的一些原則,也就是什麼時候應該使用繼承,什麼時候不應該使用繼承,以及其中蘊含的原理。里氏替換原是繼承複用的基礎,它反映了基類與子類之間的關係,是對開閉原則的補充,是對實現抽象化的具體步驟的規範。通俗來講就是:子類可以擴充套件父類的功能,但不能改變父類原有的功能。也就是說:子類繼承父類時,除新增新的方法完成新增功能外,儘量不要重寫父類的方法。

具體到實現其實就是:

    • 子類可以實現父類的抽象方法,但不能覆蓋父類的非抽象方法
    • 子類中可以增加自己特有的方法
    • 當子類的方法過載父類的方法時,方法的前置條件(即方法的輸入引數)要比父類的方法更寬鬆
    • 當子類的方法實現父類的方法時(重寫/過載或實現抽象方法),方法的後置條件(即方法的的輸出/返回值)要比父類的方法更嚴格或相等

依賴倒置原則

定義:高層模組不應該依賴低層模組,兩者都應該依賴其抽象;抽象不應該依賴細節,細節應該依賴抽象

上面的話太過於官方,換成民間的話來說就是——要面向介面程式設計,不要面向實現程式設計。

由於在軟體設計中,細節具有多變性,而抽象層則相對穩定,因此以抽象為基礎搭建起來的架構要比以細節為基礎搭建起來的架構要穩定得多。這裡的抽象指的是介面或者抽象類,而細節是指具體的實現類。使用介面或者抽象類的目的是制定好規範和契約,而不去涉及任何具體的操作,把展現細節的任務交給它們的實現類去完成。依賴倒置原則是實現開閉原則的重要途徑之一,它降低了客戶與實現模組之間的耦合。

具體到實現就是:

    • 每個類儘量提供介面或抽象類,或者兩者都具備
    • 變數的宣告型別儘量是介面或者是抽象類
    • 任何類都不應該從具體類派生
    • 使用繼承時儘量遵循里氏替換原則

舉一個簡單的例子

以下是一個墨西哥風味披薩店的類,它擁有獲取披薩的方法,客戶可以根據這個類的方法來獲取披薩吃。

public class MXGPizzaStore{
      void getPizza(){
       System.out.println("得到一個墨西哥風味的披薩");
  }
}
public class Customer{
   void eat(MXGPizzaStore pizza){
        pizza.getPizza();
 }
}

 

此時如果客戶想吃別的口味的披薩,例如紐約風味的,我們必須要修改Customer的eat方法的入參,這就帶來了程式碼修改的麻煩,同時也違背了開閉原則,相反,如果我們一開始定義一個披薩店的介面,所有的披薩店都是實現這一介面,客戶只使用介面,這樣想要別的口味披薩時也不需要更該程式碼。

public interface PizzaStore{
      void getPizza();
}

public class MXGPizza implements PizzaStore{
     void getPizza(){
    System.out.println("墨西哥風味");
 }
}

public class NyPizza implements PizzaStore{
     void getPizza(){
    System.out.println("紐約風味");
 }
}

public class Customer{
    void eat(PizzaStore  pizza){
     pizza.getPizza();
}
}

單一職責原則

 定義:單一職責原則規定一個類應該有且僅有一個引起它變化的原因,否則類應該被拆分

物件不應該承擔太多職責,如果一個物件承擔了太多的職責,至少存在以下兩個缺點:

1.一個職責的變化可能會削弱或者抑制這個類實現其他職責的能力;

2.當客戶端需要該物件的某一個職責時,不得不將其他不需要的職責全都包含進來,從而造成冗餘程式碼或程式碼的浪費。

單一職責原則是最簡單但又最難運用的原則,需要設計人員發現類的不同職責並將其分離,再封裝到不同的類或模組中。單一職責同樣也適用於方法。一個方法應該儘可能做好一件事情。如果一個方法處理的事情太多,其顆粒度會變得很粗,不利於重用。

具體例子

大學學生工作主要包括學生生活輔導和學生學業指導兩個方面的工作,其中生活輔導主要包括班委建設、出勤統計、心理輔導、費用催繳、班級管理等工作,學業指導主要包括專業引導、學習輔導、科研指導、學習總結等工作。如果將這些工作交給一位老師負責顯然不合理,正確的做 法是生活輔導由輔導員負責,學業指導由學業導師負責。

 

介面隔離原則

 定義:客戶端不應該被迫依賴於它不使用的方法,一個類對另一個類的依賴應該建立在最小的介面上

該原則要求程式設計師儘量將臃腫龐大的介面拆分成更小的和更具體的介面,讓介面中只包含客戶感興趣的方法;要為各個類建立它們需要的專用介面,而不要試圖去建立一個很龐大的介面供所有依賴它的類去呼叫,這一點上,它和單一職責原則很像,都是為了提高類的內聚性、降低它們之間的耦合性,但兩者有些許不同,單一職責注重的是職責,而介面隔離注重的是對介面依賴的隔離,單一職責主要約束的是類,它針對程式中的細節,介面隔離原則主要約束介面,主要針對抽象和程式整體框架的構建。

具體實現準則:

    • 介面儘量小,但是要有限度。一個介面只服務於一個子模組或業務邏輯。
    • 為依賴介面的類定製服務。只提供呼叫者需要的方法,遮蔽不需要的方法。
    • 瞭解環境,拒絕盲從。每個專案或產品都有選定的環境因素,環境不同,介面拆分的標準就不同深入瞭解業務邏輯。
    • 提高內聚,減少對外互動。使介面用最少的方法去完成最多的事情。

以下為一個具體的例項

學生成績管理程式一般包含插入成績、刪除成績、修改成績、計算總分、計算均分、列印成績資訊、査詢成績資訊等功能,如果將這些功能全部放到一個介面中顯然不太合理,正確的做法是將它們分別放在輸入模組、統計模組和列印模組等 3 個模組中。

 

 

具體實現樣例:

public class ISPtest {
    public static void main(String[] args) {
        InputModule input = StuScoreList.getInputModule();
        CountModule count = StuScoreList.getCountModule();
        PrintModule print = StuScoreList.getPrintModule();
        input.insert();
        count.countTotalScore();
        print.printStuInfo();
        //print.delete();
    }
}
//輸入模組介面
interface InputModule {
    void insert();
    void delete();
    void modify();
}
//統計模組介面
interface CountModule {
    void countTotalScore();
    void countAverage();
}
//列印模組介面
interface PrintModule {
    void printStuInfo();
    void queryStuInfo();
}
//實現類
class StuScoreList implements InputModule, CountModule, PrintModule {
    private StuScoreList() {
    }
    public static InputModule getInputModule() {
        return (InputModule) new StuScoreList();
    }
    public static CountModule getCountModule() {
        return (CountModule) new StuScoreList();
    }
    public static PrintModule getPrintModule() {
        return (PrintModule) new StuScoreList();
    }
    public void insert() {
        System.out.println("輸入模組的insert()方法被呼叫!");
    }
    public void delete() {
        System.out.println("輸入模組的delete()方法被呼叫!");
    }
    public void modify() {
        System.out.println("輸入模組的modify()方法被呼叫!");
    }
    public void countTotalScore() {
        System.out.println("統計模組的countTotalScore()方法被呼叫!");
    }
    public void countAverage() {
        System.out.println("統計模組的countAverage()方法被呼叫!");
    }
    public void printStuInfo() {
        System.out.println("列印模組的printStuInfo()方法被呼叫!");
    }
    public void queryStuInfo() {
        System.out.println("列印模組的queryStuInfo()方法被呼叫!");
    }
}

 

相關文章