隔壁小孩也能看懂的面向對象(概念篇)

不少初學者在聽到【面向對象】這個名詞的時候挺懵逼,別懼怕,它只是聽起來很厲害的樣子~編程

什麼是面向對象

下面我舉個栗子,你們來思考下該如何設計程序~函數

一個神祕組織的boss想玩五子棋了,任命你來開發。這個時候你就開始頭腦風暴,腦海裏覆盤本身曾經晚自習上和同桌小哥哥下過的五子棋。咱們腦海裏有這樣一個過程:佈局

    1. 開始遊戲
    1. 黑子先走
    1. 繪製畫面
    1. 判斷輸贏
    1. 輪到白子
    1. 繪製畫面
    1. 判斷輸贏
    1. 返回步驟2
    1. 輸出最後結果

而後再用函數將上面每一個步驟實現,這個遊戲就設計好了~~~~設計

嗯嗯,看上去好像沒什麼問題,但這個時候boss說他想玩的不得了,給你加了個小弟一塊兒開發,你說我來搞黑子,你來搞白子,最後咱們再來和一下,小弟說o**k。3d

正在你和小弟背靠背埋頭開發的時候,boss來轉了一圈,大喊一句:「你倆咋在寫一樣的代碼?」cdn

這時你發現,對象

    1. 黑子和白子的行爲是如出一轍的
    1. 負責繪製畫面的可交由棋盤系統
    1. 負責斷定犯規、輸贏的可交由規則系統

這個時候你和小弟說,咱們搞三個對象:blog

「第一類玩家對象 負責接收用戶輸入,並告知第二類棋盤對象 起子佈局的變化,棋盤對象接收到了棋子的變化就要負責在屏幕上面顯示出這種變化,同時利用第三類規則對象來對棋局進行斷定。」繼承

小弟一據說要搞對象立刻重振旗鼓開始開發~~接口

好了,故事講到這裏,咱們其實就已經瞭解兩種設計思想:

最開始你分析出解決問題所須要的步驟,而後用函數把這些步驟一步一步實現,使用的時候一個一個依次調用的思想,就是【面向過程】

後來你把構成問題事務分解成各個對象,創建對象的目的不是爲了完成一個步驟,而是爲了描述某個事物在整個解決問題的步驟中的行爲,咱們用個高大上的名詞包裝下,稱之爲【面向對象】

2、面向對象的三大特徵

雖然咱們完成了boss的五子棋設計,但更多的時候,咱們涉及的對象卻遠遠不止三個。這個時候咱們就要對對象、對象之間的關係進行設計~

面向對象有三大特徵:封裝,繼承,多態

聽起來好高大上有木有,懼怕你就輸了~~~

封裝

封裝就是把客觀事物封裝成抽象的類,而且類能夠把本身的數據和方法只讓可信類或者對象操做,對不可信的進行信息隱藏。

概念聽起來比抽象自己還抽象,仍是再舉個栗子吧。

在咱們平常聊天的時候,常常會說「這類人如何如何」、「那類怎樣怎樣」,若是你說過,那恭喜你,你已經知道什麼是抽象了!

咱們將不一樣的客觀事物的共同點提取出來,它們的共同點能夠是一些特徵,也能夠是都能完成的事情。

想一想你的boss和小弟boy的共同點:

    1. 他們都是人類
    1. 他們都會下五子棋

那麼咱們能夠抽象出一個 player 類,player類的屬性是person,具備的方法是canPlay。若是有一天你對着神祕組織的人員名單找player,保證你名單翻來覆去找幾遍都找不到,由於沒有人叫player啊!player只是一個類,而boss和boy纔是實現了這個類的實例對象。

繼承

簡單來講,咱們如今已經有個player類了,player類的屬性是person,方法是canPlay。後來你仔細一想,發現事情並不簡單,別的組織會下五子棋的人竟然也在這個類裏面!這可不行,要知道你所在的神祕組織除了person屬性、canPlay方法之外,還有beauty屬性、canPlayGood方法。因而你打算搞個特別點的類,叫greatPlayer類。這個時候難道要從新寫以前的player類的方法嗎?nooooooo,咱們所作的一切努力都是爲了堵住那一句:「咋在寫一樣的代碼?」

繼承,就是使用現有類的全部功能,並在無需從新編寫原來的類的狀況下對這些功能進行拓展。

而繼承有兩種實現方式:「繼承」(Inheritance)和「組合」(Composition)。

首先咱們要明白,組合和繼承都是提升代碼可重用性的手段。區別是在設計對象模型時,若是去劃分類和類之間的關係。說白了就是如何對類和類進行拓展和複用。

繼承:就是 is a的關係,好比說student繼承person,則說明student is a person。

組合:設計類的時候把要組合的類的對象加入到該類中做爲本身的成員變量。

好比說,若是發現兩個類具備不少相同的方法,很類似須要抽象:

好比上圖A,B兩個類中method1,method2和method3三個方法都相同,

繼承的抽象方式是:

組合的抽象方式是:

下面咱們來比較下組合和繼承的優缺點:

繼承

優勢:

  • 子類能夠重寫父類的方法來方便地實現對父類的擴展。

缺點:

  • 父類的內部細節對子類是可見的。這太不符合「你辦事我放心」的想法了。

  • 若是對父類方法作了修改,子類的方法必須作出相應的修改。因此子類和父類是一種高耦合。

組合

優勢:

  • 被包含的對象的內部細節對當前對象不可見,「你辦事我放心」~

  • 當前對象與被包含的對象是一個低耦合關係,修改包含對象的類代碼不須要修改當前對象類的代碼~

缺點:

  • 容易產生過多對象

  • 爲了能組合多個對象,必須對【接口】進行定義

因此,組合比繼承更具備靈活性和穩定性,在設計的時候優先使用組合~

多態

多態的定義是同一個操做,做用在不一樣的對象上,能夠產生不一樣的解釋和不一樣的執行結果。

對於強類型語言,一般採用抽象類或者接口,進行更高一層的抽象,從而直接使用更高層的抽象。就比如強類型語言中裏,有兩個方法:int和int相加,float和float相加,既然都要相加,那就再抽象一下。實際上就是爲了弱化具體的類型。

而對於像js這種弱類型語言來講,多態就是與生俱來的。

再來看多態的兩種實現方式:覆蓋重載

覆蓋:子類從新定義父類的方法。咱們的js原型鏈繼承那套,不就是覆蓋了嗎~

重載:容許存在多個同名函數,而這些函數的參數表不一樣(或許參數個數不一樣,或許參數類型不一樣,或許二者都不一樣)

重載的話,像js確實沒有,畢竟是弱類型語言。順便一提,重載的概念並不屬於「面向對象編程」,重載的實現是:編譯器根據函數不一樣的參數表,對同名函數的名稱作修飾,而後這些同名函數就成了不一樣的函數(對編譯器)

有兩個同名函數:function func(p:integer):integer;和function func(p:string):integer;。那麼編譯器作過修飾後的函數名稱多是這樣的:int_func、str_func

對於這兩個函數的調用,在編譯器的時候就已經肯定了,是靜態的,其地址在編譯器就綁定了。因此重載和多態無關。

而js自己是解釋型語言而非編譯型語言,因此確定和重載不要緊。

接口

剛剛咱們說了,爲了能組合多個對象,必須對【接口】進行定義。

那麼接口究竟是什麼呢?

接口提供了一種用來講明一個對象應該具備哪些方法的手段。它能夠代表這些方法的含義,可是卻不包含具體的實現。我規定了你要作什麼,可是你怎麼作我可無論~

如今咱們知道接口是什麼了,可是爲毛要用接口呢?

有了接口,咱們就能夠按對象提供的特性對它們進行分組。再再再舉個栗子,如今有對象A、對象B以及接口I,即使對象A和對象B的差別巨大,但只要它們都實現了接口I,那麼在A.I(B)方法中,就能夠互換使用A和B,好比B.I(A)。那不是很爽嗎!我能夠互換使用A、B的方法了!

另外,還可使用接口開發不一樣的類的共同性。若是把本來要求以一個特定的類爲參數的函數改成要求以一個特定的接口爲參數的函數,那麼全部實現了該接口的對象均可以做爲參數傳遞給它,這樣作的話,明明彼此不相關的對象,也能夠被相同地對待使用該方法了!

結語

面向對象是一門很是實用的設計思想,本文旨在讓隔壁小孩也能看懂,若是內容有誤歡迎在評論區吐槽~

相關文章
相關標籤/搜索