淺談面向對象中的一些主要思想


淺談面向對象中的一些主要思想

何為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()方法被調用!");
    }
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM