91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

面向對象三大特性的意義講解

發布時間:2020-10-20 02:28:40 來源:腳本之家 閱讀:157 作者:李先靜 欄目:編程語言

面向對象的三大特性:封裝、繼承和多態。這是任何一本面向對象設計的書里都會介紹的,但鮮有講清楚的,新手看了之后除了記住幾個概念外,并沒真正了解他們的意義。前幾天在youtube上看了Bob大叔講解的SOLID原則,其中有一段提到面向對象的三大特性,收獲很多,但是我并不完全贊同他的觀點,這里談談我的想法:

封裝

『封裝』第一層含義是信息隱藏。這是教科書里都會講解的,把類或模塊的實現細節隱藏起來,對外只提供最小的接口,也就是所謂的『最小知識原則』。有個共識,正常的程序員能理解的代碼在一萬行左右。這是指在理解代碼的實現細節的情況下,正常的程序員能理解的代碼的規模。比如一個文件系統,FAT、NTFS、EXT4和YAFFS2等,它們的實現是比較復雜的,少則幾千行代碼,多則幾萬行,要理解它們的內部實現是很困難的,但是如果屏蔽它們的內部實現細節,只是要了解它們對外的接口,那就非常容易了。

關于『封裝』的這一層含義,Bob大叔提出了驚人的見解:『封裝』不是面向對象的特性,面向過程的C語言比面向對象的C++/Java在『封裝』方面做得更好!證據也是很充分:C語言把函數的分為內部函數和外部函數兩類。內部函數用static修飾,放在C文件中,外部函數放在頭文件中。你完全不知道內部函數的存在,即使知道也沒法調用。而像在C++/Java中,通過public/protected/private/friend等關鍵字,把函數或屬性分成不同的等級,這把內部的細節暴露給使用者了,使用者甚至可以繞過編譯器的限制去調用私有函數。所以在信息隱藏方面,『封裝』不但不是面向對象的特性,而且面向對象減弱了『封裝』。

『封裝』的第二層含義是把數據和行為封裝在一起。我覺得這才是面向對象中的『封裝』的意義所在,而一般的教科書里并沒提及或強調。面向過程的編程中,數據和行為是分離的,面向對象的編程則是把它們看成一個有機的整體。所以,從這一層含義來看,『封裝』確實是面向對象的『特性』。

面向對象是一種思維方式,而不是表現形式。在C語言中,可以實現面向對象的編程,事實上,幾乎所有C語言開發的大型項目,都是采用了面向對象的思想開發的。把C語言說成面向過程的語言是不公平的,是不是面向對象的編程主要是看指導思想,而不是編程語言。你用C++/Java可以寫面向過程的代碼,也可以用C語言寫面向對象的代碼。

繼承

類就是分類的標準,也就是一類事物,一類具有相同屬性和行為對象的抽象。比如動物就是一個類,它描述了所有具有動物這個屬性的事物的集合。狗也是一個類,它具有動物所有的特性,我們說狗這個類繼承了動物這個類,動物是狗的父類,狗是動物的子類。在C語言中也可以模擬繼承的效果,比如:

struct Animal {
...
};
struct Dog {
  struct Animal animal;
  ...
}
struct Cat {
  struct Animal animal;
  ...
}

因為C語言也可以實現『繼承』,所以Bob大叔認為『繼承』也不算不上是面向對象的『特性』。但是我覺得,C語言中實現『繼承』的方式,需要用面向對象的思維來思考才能理解,否則純粹從數據結構的方式來看上面的例子,理解起來就會大相徑庭:animal是Dog的一個成員,所以Animal可以看成是Dog的一部分!Is a 變成了has a。只有在面向對象的思想中,說『繼承』才有意義,所以說『繼承』是面向對象的『特性』并不牽強。

在C語言里實現多重繼承更是非常麻煩了,記得glib里實現了接口的多重繼承,但是用起來還是挺別扭的,對新手來說更是難以理解。多重繼承在某些情況下,會對編譯器造成歧義,比菱形繼承結構:A是基類,B和C是它的兩個子類,D從B和C中繼承過來,如果B和C都重載了A的一個函數,編譯器此時就沒法區分用B的還是C的了(當然這是可以解決的)。

像Bob大叔說的,Java沒有實現多重繼承,并不是多重繼承沒有用。而是為了簡化編譯器的實現,C#沒有實現多重繼承,則是因為Java沒有實現多重繼承:)

除了接口多重繼承是必不可少的,類的多重繼承在現實中也是很常見的。比如:狼和狗都是狗科動物的子類,貓和老虎都是貓科動物的子類。狗科動物和貓科動物都是動物的子類。但是貓和狗都是家畜,老虎和狼都是野生動物。貓不但要繼承貓科動物的特性,還繼承家畜的特性。類就是分類的標準,而混用不同的分類標準是多重繼承的主要來源。多重繼承可以用其他方式實現,比如traits和mixin。

不管是普通繼承,接口繼承,還是多重繼承,在面向對象的編程語言中,實現起來要更加容易和直觀,在面向過程的語言中,雖然可以實現,但是比較丑陋,而且本質是面向對象的思考方式。所以『繼承』應該稱得上是面向對象的『特性』了。介于繼承帶來的復雜性,現代面向對象的設計中,都推薦用組合來代替繼承實現重用。

多態

『多態』本來是面向對象思想中最重要的性質(當然也算不上是特有的性質),但是教科書里都只是介紹了『多態』的表現形式,而沒有介紹它用途和價值。『多態』一般表現為兩種形式:

允許不同輸入參數的同名函數存在。這個性質會帶來一定的便利,特別是對于構造函數和操作符的重載。但這種『多態』是在編譯時就確定了的,所以只能算成一種語法糖,并沒有什么特別的意義。

子類可以重載父類中函數原型完全相同的同名函數。如果只看它的表現形式,在父類中存在的函數,在不同的子類中可以被重新實現,這看起來是吃飽了撐著。但是這種『多態』卻是軟件架構的基礎,幾乎所有的設計模式和方法都依賴這種特性。

隔離變化是軟件架構設計的基本目標之一,接口正是隔離變化最重要的手段。我們經常說分離接口與實現,針對接口編程,主要是因為接口可以隔離變化。如果沒有第二種『多態』,就沒有真正意義上的接口。面向對象中的接口,不僅是指模塊對外提供的一組函數,而且特指在運行時才綁定具體實現的一組函數,在編譯時根本不知道這組函數是誰提供的。我們先把接口簡單的理解為,在基類中定義一組函數,但是基類并沒有實現它們,而在具體的子類中去實現。這不就是『多態』的第二種表現形式么。

接口怎么能夠隔離變化呢?Bob大叔舉了一個非常好的例子:

#include <stdio.h>
int main() {
  int c;
  while((c = getchar()) != EOF) {
    putchar(c);
  }
  return 0;
}

這個程序和Hello world是一個級別的,你從鍵盤輸入一個字符,它就顯示一個字符。但是它卻蘊含了『多態』最精妙的招式。比如說輸入吧,getchar是從標準輸入(STDIN)讀入一個字符,鍵盤輸入是缺省的標準輸入,但是鍵盤輸入只是眾多標準輸入(STDIN)中的一種。你可以從任何一個IO設備讀取數據:從網絡、文件、內存和串口等等,換成任何一種輸入,這個程序都不需要任何改變。

具體實現變了,調用者不需要修改代碼,而且它根本不用重新編譯,甚至不用重啟應用程序。這就是接口的威力,也是『多態』的功勞。

上面的程序是如何做到的呢?IO設備的驅動是一套接口,它定義了打開、關閉、讀和寫等操作。對實現者來說,不管數據從哪里來,要到哪里去,只要實現接口中定義的函數即可。對使用者來說,完全不同關心它具體的實現方式。

『多態』不但是隔離變化的基礎,也是代碼重用的基礎。公共函數的重用是有價值的,在面向過程的開發中也很容易做到這種重用。但現實中的重用沒那么簡單,就連一些大師也感嘆重用太難。比如,你可能需要A這個類,你把它拿過來時,發現它有依賴B這個類,B這個類有依賴C這個類,搞到最后發現,它還依賴一個看似完全不相關的類,重用的念頭只好打住。如果你覺得夸張了,你可以嘗試從一個數據庫(如sqlite)中,把它的B+樹代碼拿出來用一下。

在『多態』的幫助下,情況就會大不相同了。A這個類依賴于B這個類,我們可以把B定義成一個接口,讓使用A這個類的使用者傳入進來,也就是所謂的依賴注入。如果你想重用A這個類,你可以為它定制一個B接口的實現。比如,我最近在一個只有8K內存的硬件上,為一塊norflash寫了一個簡單的文件系統(且看作是A類),如果我直接去調用norflash的API(且看作是B類),就會讓文件系統(A類)與norflash的API(B類)緊密耦合到一起,這就會讓文件系統的重用性大打折扣。

我的做法是定義了一個塊設備的接口(即B接口):

typedef unsigned short block_num_t;
struct _block_dev_t;
typedef struct _block_dev_t block_dev_t;
typedef block_num_t (*block_dev_get_block_nr_t)(block_dev_t* dev);
typedef bool_t (*block_dev_read_block_t)(block_dev_t* dev, block_num_t block_num, void* buff);
typedef bool_t (*block_dev_write_block_t)(block_dev_t* dev, block_num_t block_num, const void* buff);
typedef void  (*block_dev_destroy_t)(block_dev_t* dev);
struct _block_dev_t {
  block_dev_get_block_nr_t  get_block_nr;
  block_dev_write_block_t  write_block;
  block_dev_read_block_t   read_block;
  block_dev_destroy_t    destroy;
};

在初始化文件系統時,把塊設備注入進來:

bool_t sfs_init(sfs_t* fs, block_dev_t* dev);

這樣,文件系統只與塊設備接口交互,不需要關心實現是norflash、nandflash、內存還是磁盤。而且帶來幾個附加好處:

可以在PC上做文件系統的單元測試。在PC上,用內存模擬一個塊設備,文件系統可以正常工作了。

可以通過裝飾模式為塊設備添加磨損均衡算法和壞塊管理算法。這些算法和文件系統都可以獨立重用。

『多態』讓真正的重用成為可能,沒有『多態』就沒有各種框架。在C語言中,多態是通過函數指針實現的,而在C++中是通過虛函數,在Java中有專門的接口,在JS這種動態語言中,每個函數是多態的。『多態』雖然不是面向對象的『特有的』屬性,但是面向對象的編程語言讓『多態』更加簡單和安全。

總結

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,謝謝大家對億速云的支持。如果你想了解更多相關內容請查看下面相關鏈接

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

西宁市| 彰化市| 泰顺县| 宁强县| 乡城县| 紫阳县| 平罗县| 漳州市| 凤凰县| 丘北县| 宜兰市| 建平县| 榆社县| 酉阳| 疏勒县| 海原县| 团风县| 衡阳市| 民县| 淳安县| 梅河口市| 视频| 镇雄县| 韶关市| 深州市| 雷山县| 海盐县| 兴业县| 布尔津县| 新昌县| 咸丰县| 楚雄市| 石林| 辛集市| 临沂市| 永顺县| 确山县| 灵山县| 七台河市| 图片| 张掖市|