談?wù)勎覍?duì)面向?qū)ο笠约邦?lèi)與對(duì)象的理解
文章最初發(fā)表于我的個(gè)人博客非典型性程序猿
對(duì)于剛接觸JAVA或者其他面向?qū)ο缶幊陶Z(yǔ)言的朋友們來(lái)說(shuō),可能一開(kāi)始都很難理解面向?qū)ο蟮母拍钜约邦?lèi)和對(duì)象的關(guān)系。筆者曾經(jīng)帶過(guò)一個(gè)短期培訓(xùn)班教授java入門(mén)基礎(chǔ),在最后結(jié)束課程的時(shí)候,還有很多同學(xué)不太理解面向?qū)ο蟮乃季S以及類(lèi)與對(duì)象的意義。這幾天有空,就想著整理整理自己的思路,談?wù)勛约簩?duì)面向?qū)ο笠约邦?lèi)與對(duì)象的理解。
面向?qū)ο?/strong>
首先,一言不和先百度,得到如下定義:
一切事物皆對(duì)象,通過(guò)面向?qū)ο蟮姆绞?,將現(xiàn)實(shí)世界的事物抽象成對(duì)象,現(xiàn)實(shí)世界中的關(guān)系抽象成類(lèi)、繼承,幫助人們實(shí)現(xiàn)對(duì)現(xiàn)實(shí)世界的抽象與數(shù)字建模。
我們知道,編寫(xiě)程序的目的是為了解決現(xiàn)實(shí)生活中的問(wèn)題,編程的思維方式也應(yīng)該貼近現(xiàn)實(shí)生活的思維方式。面向?qū)ο蟮木幊谭绞骄褪菫榱藢?shí)現(xiàn)上述目的二出現(xiàn)的。它使得編程工作更直觀,更易理解。需要注意的是這里說(shuō)的編程不光是coding還包括了設(shè)計(jì)的過(guò)程也是面向?qū)ο蟮?/p>
為什么說(shuō)面向?qū)ο蟾N近實(shí)際生活
想象一下,當(dāng)我們向別人描述一樣事物時(shí),我們都是怎么說(shuō)的?"它有像鴨子一樣的嘴巴","它有4條退","爪子里還有蹼","它是哺乳動(dòng)物但卻是卵生"。
這種HAS A 和 IS A的表達(dá)方式往往可以簡(jiǎn)單而高效的描述一樣事物。HAS A描述事物的屬性或行為,IS A 則說(shuō)明了事物的類(lèi)屬。
當(dāng)我們把這一系列的屬性組合起來(lái)便得到的鴨嘴獸這一類(lèi),同時(shí)哺乳動(dòng)物一詞簡(jiǎn)單精煉的表面了所有哺乳動(dòng)物的特性而不用一一列出,這是繼承特性的體現(xiàn),同時(shí)卵生又是多態(tài)的體現(xiàn)。
這就是面向?qū)ο蟮乃季S特點(diǎn),抽取(抽象)有用的屬性和行為(拋棄哪些無(wú)需關(guān)系的)組織(封裝)成一個(gè)類(lèi)。這個(gè)過(guò)程中你也許會(huì)發(fā)現(xiàn)很多屬性或方法是和另一個(gè)類(lèi)相同的,那么你就可以采用繼承的方式避免重復(fù)(當(dāng)然這個(gè)過(guò)程也有可能是,當(dāng)你設(shè)計(jì)完一個(gè)個(gè)類(lèi)后,才發(fā)現(xiàn)他們有共同點(diǎn),然后再抽取出基類(lèi))。更重要的是,繼承是可以不原樣照搬的,我們可以通過(guò)重載實(shí)現(xiàn)相同行為或?qū)傩缘奶赜袑?shí)現(xiàn)方式,這種特點(diǎn)稱(chēng)之為多態(tài),例如同樣的生產(chǎn)行為,實(shí)現(xiàn)方式由胎生變?yōu)槁焉?。?qǐng)大聲念出,并牢牢記住面向?qū)ο蟮乃膫€(gè)特征:
- 抽象
- 封裝
- 繼承
- 多態(tài)
與早期結(jié)構(gòu)化編程相比
早期結(jié)構(gòu)化編程是面向過(guò)程的(功能),換句話說(shuō)程序是由功能的集合組成,而調(diào)用者是作為功能的參數(shù)傳入的。而在面向?qū)ο蟮某绦蛑校瑢?duì)象是主體,程序是由對(duì)象的集合組成。一個(gè)對(duì)象中包含一系列符合設(shè)計(jì)的功能供其他對(duì)象調(diào)用。這么說(shuō)可能還是比較抽象,
例如當(dāng)我們?cè)O(shè)計(jì)一個(gè)五子棋游戲時(shí),面向過(guò)程的設(shè)計(jì)思路就是首先分析問(wèn)題的步驟:
1、開(kāi)始游戲,2、黑子先走,3、繪制畫(huà)面,4、判斷輸贏,5、輪到白子,6、繪制畫(huà)面,7、判斷輸贏,8、返回步驟2,9、輸出最后結(jié)果。
把上面每個(gè)步驟用分別的函數(shù)來(lái)實(shí)現(xiàn),問(wèn)題就解決了。
而面向?qū)ο蟮脑O(shè)計(jì)則是從另外的思路來(lái)解決問(wèn)題。整個(gè)五子棋可以分為:
1、黑白雙方,這兩方的行為是一模一樣的,2、棋盤(pán)系統(tǒng),負(fù)責(zé)繪制畫(huà)面,3、規(guī)則系統(tǒng),負(fù)責(zé)判定諸如犯規(guī)、輸贏等。
第一類(lèi)對(duì)象(玩家對(duì)象)負(fù)責(zé)接受用戶(hù)輸入,并告知第二類(lèi)對(duì)象(棋盤(pán)對(duì)象)棋子布局的變化,棋盤(pán)對(duì)象接收到了棋子的變化就要負(fù)責(zé)在屏幕上面顯示出這種變化,同時(shí)利用第三類(lèi)對(duì)象(規(guī)則系統(tǒng))來(lái)對(duì)棋局進(jìn)行判定。
(以上例子來(lái)自國(guó)內(nèi)著名問(wèn)答社區(qū))
隨便寫(xiě)點(diǎn)代碼,大家看看就好,不要太認(rèn)真....
- /**
- 玩家類(lèi)
- **/
- public class Player {
- String name; //棋手名稱(chēng)
- boolean isFirst; //是否先手
- int color_flag; //代表顏色 0-白 1-黑
- Table table;//棋盤(pán)對(duì)象
- public Player(String name,boolean isFirst;int color_flag){
- this.name=name;
- this.isFirst=isFirst;
- this.color_flag=color_flag;
- }
- /**
- 下棋,x,y為落子坐標(biāo)
- **/
- public void play(int x,int y) throws Exception{
- if(this.table==null){
- throw new IllegalArgumentException("玩家還未注冊(cè)到棋盤(pán)!");
- }
- table.setNewPieces(x,y);
- }
- public void setTable(Table table){
- this.table=table;
- }
- }
- /**
- 棋盤(pán)類(lèi)
- **/
- public class Table{
- List<Player> playerList=new ArrayList<Player>();
- Referee referee ;
- public Table(){
- referee =new Referee(this);
- }
- /**
- 注冊(cè)玩家
- **/
- public void registPlayer(Player player) throws Exception {
- //檢測(cè)棋盤(pán)中的玩家是否已滿,先手玩家和玩家選色是否沖突。
- .......
- playerList.add(player);
- player.setTable(this);
- }
- /**
- 落子
- **/
- public void setNewPieces(int x , int y){
- //重新繪制棋盤(pán)
- ......
- //調(diào)用裁判對(duì)象,判斷結(jié)果
- if(referee.isEnd){
- End();
- }
- }
- public void End(){
- .......
- }
- }
- /**
- 裁判類(lèi)
- **/
- public class Referee(){
- Table table;
- public Referee(Table table){
- this.table=table;
- }
- public boolen isEnd(){
- //判斷輸贏
- ....
- return false;
- }
- }
然而事實(shí)上,通過(guò)上述示例代碼,我們不難發(fā)現(xiàn),即使我們使用面向?qū)ο蟮姆绞?,上面例子里面向過(guò)程中提到的幾個(gè)下棋過(guò)程我們還是都實(shí)現(xiàn)了的,只不過(guò)程被封裝到了類(lèi)的方法中。所以說(shuō)其實(shí)面向?qū)ο蠛兔嫦蜻^(guò)程并不是編程的區(qū)別(需要實(shí)現(xiàn)的業(yè)務(wù)邏輯的量不會(huì)產(chǎn)生變化),而是設(shè)計(jì)的區(qū)別!
類(lèi)與對(duì)象
類(lèi)是抽象的,而對(duì)象是具體的
如何理解上面的話呢? 例如鴨嘴獸是類(lèi)型,具體的鴨嘴獸A、鴨嘴獸B就是對(duì)象了。在JAVA中對(duì)象是通過(guò)new關(guān)鍵字聲明的。 再例如,《紅色警戒》中美國(guó)大兵是一類(lèi)兵種,點(diǎn)擊制造后從兵營(yíng)里出來(lái)的那個(gè)會(huì)開(kāi)槍的家伙就是對(duì)象了:
類(lèi)的定義就是一個(gè)模板,它描述的一類(lèi)對(duì)象的屬性與行為。類(lèi)往往是抽象的、沒(méi)有實(shí)體的。哺乳動(dòng)物是類(lèi)的概念,是抽象的,現(xiàn)實(shí)中沒(méi)有哺乳動(dòng)物這一實(shí)體,只有具體的如老虎,獅子等。編程工作中套用這一思維模式,我們將程序中的實(shí)例抽象為類(lèi),例如一個(gè)系統(tǒng)中的用戶(hù)有張三、李四我們會(huì)把他們抽象為Person類(lèi),或者稱(chēng)之為一個(gè)名為Person的數(shù)據(jù)類(lèi)型。
對(duì)象則是根據(jù)所屬類(lèi)模板創(chuàng)造出來(lái)的實(shí)實(shí)在在的事物。在程序中我將這個(gè)實(shí)實(shí)在在的事物稱(chēng)之為實(shí)例,我們?yōu)樗膶傩再x上特定的值,讓它成為張三或者李四。在內(nèi)存里來(lái)說(shuō),對(duì)象是表示的就是具體數(shù)據(jù)。
前面說(shuō)的都是概念性的東西,下面我們說(shuō)說(shuō)實(shí)際的運(yùn)用過(guò)程中的理解。
從數(shù)據(jù)類(lèi)型來(lái)說(shuō)
以java為例,數(shù)據(jù)類(lèi)型分為基本數(shù)據(jù)類(lèi)型和引用數(shù)據(jù)類(lèi)型。
基本數(shù)據(jù)類(lèi)型就是byte,short,int,long,double,char,boolean;其它的,需要用到new關(guān)鍵字來(lái)賦值的都是引用數(shù)據(jù)類(lèi)型。 類(lèi)與對(duì)象指的便是引用數(shù)據(jù)的類(lèi)型與其值(這里指的類(lèi)不光是class,還包括接口、數(shù)組、枚舉、注解)。 而引用指的是內(nèi)存地址的引用,關(guān)于這點(diǎn)在后面說(shuō)的內(nèi)存時(shí)會(huì)細(xì)說(shuō)。
看下面的代碼:
- int a =1;
- Person b=new Person();
a 和 b 都是本身無(wú)意義的變量名。需要關(guān)注的是:a的類(lèi)型是基本數(shù)據(jù)類(lèi)型int值為1,而b的類(lèi)型是Person屬于引用類(lèi)型,其引用的是new Person()這個(gè)對(duì)象。我們往往會(huì)說(shuō)對(duì)象xx,比如這里的對(duì)象b。但實(shí)際上b只是對(duì)象的引用,真正的對(duì)象是后面的new Person()!
需要注意的是String也是引用數(shù)據(jù)類(lèi)型,只不過(guò)因?yàn)槭褂寐史浅8?,所以?duì)于String,jvm支持其可 以像基本數(shù)據(jù)類(lèi)型一樣使用:String a = "abc"; 同等于 String a = new String("abc");
總之呢,簡(jiǎn)單來(lái)說(shuō)類(lèi)指的的引用數(shù)據(jù)的類(lèi)型,對(duì)象是具體賦的值。為了更深入理解,我們下面需要解釋下這個(gè)引用是如何體現(xiàn)的。
什么是引用(從內(nèi)存來(lái)說(shuō))
要深入理解什么是類(lèi),什么是對(duì)象,什么又是引用,就離不開(kāi)說(shuō)說(shuō)java的內(nèi)存使用方式。
在java中內(nèi)存被大致劃分為棧(stack)與堆(heap) (之所以是大致,是因?yàn)檫€包括其它幾部分就不在這細(xì)說(shuō))。
關(guān)于什么是棧與堆在這就不細(xì)說(shuō),有空我再整理一篇文章詳細(xì)說(shuō)明。
在這里我們只說(shuō)一點(diǎn):java中,基本數(shù)據(jù)類(lèi)型以及對(duì)象的引用都保存在棧(stack),而對(duì)象則保存在堆(heap)中,例如當(dāng)如下代碼:
- int a=1;
- Person p;
內(nèi)存中的狀態(tài)大致如下:
int a = 1 是直接在棧中開(kāi)辟空間,而對(duì)于未進(jìn)行實(shí)例化的Person p因?yàn)闆](méi)有有效的內(nèi)存地址引用它的值是null。而當(dāng)代碼進(jìn)行如下修改時(shí):
- int a =1 ;
- Person p = new Person();
內(nèi)存中的狀態(tài)大致如下:
Person p=new Person();使得p的值=0x8da23也就是對(duì)象new Person();在堆中的地址。所以,到這里后就不難理解之前說(shuō)的對(duì)象的引用了,所謂引用其實(shí)就是堆內(nèi)存地址的引用。
總結(jié)
隨著計(jì)算機(jī)技術(shù)的不斷提高,現(xiàn)在計(jì)算機(jī)不單單是用來(lái)解決運(yùn)算問(wèn)題,而是被用于解決越來(lái)越貼近現(xiàn)實(shí)生活的復(fù)雜問(wèn)題。面向?qū)ο缶褪沁@一發(fā)展進(jìn)程的產(chǎn)物,它使得編程工作更貼近人的思維方式,從而大大提升編程效率。我們必須明白的是面向?qū)ο蟛⒉皇且环N編程方式,而是一種編程思維方式,這種思維方式涵蓋了分析,設(shè)計(jì),編碼等。在面向?qū)ο缶幊讨?,程序的基本單元是?duì)象,數(shù)據(jù)封裝在對(duì)象中。類(lèi)是對(duì)象模板,是預(yù)定義好的結(jié)構(gòu),所謂的實(shí)例化一個(gè)類(lèi),所指的就是將數(shù)據(jù)填入模板。
最后,本人文筆不是很好,有待提高。寫(xiě)文章和博客的最大目的是梳理自己的思路,其二是分享自己的想法,望大家多多吐槽,愿共同提高。