本節(jié)內(nèi)容:
面向?qū)ο缶幊探榻B
為什么要用面向?qū)ο筮M(jìn)行開發(fā)?
面向?qū)ο蟮奶匦裕悍庋b、繼承、多態(tài)
類、方法、
引子
你現(xiàn)在是一家游戲公司的開發(fā)人員,現(xiàn)在需要你開發(fā)一款叫做<人狗大戰(zhàn)>的游戲,你就思考呀,人狗作戰(zhàn),那至少需要2個(gè)角色,一個(gè)是人, 一個(gè)是狗,且人和狗都有不同的技能,比如人拿棍打狗, 狗可以咬人,怎么描述這種不同的角色和他們的功能呢?
你搜羅了自己掌握的所有技能,寫出了下面的代碼來描述這兩個(gè)角色
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | def person(name,age,sex,job):
data = {
'name' :name,
'age' :age,
'sex' :sex,
'job' :job
}
return data
def dog(name,dog_type):
data = {
'name' :name,
'type' :dog_type
}
return data
|
上面兩個(gè)方法相當(dāng)于造了兩個(gè)模子,游戲開始,你得成一個(gè)人和狗的實(shí)際對象吧,怎么生成呢?
1 2 3 4 5 | d1 = dog( "李闖" , "京巴" )
p1 = person( "孫海濤" , 36 , "F" , "運(yùn)維" )
p2 = person( "林海峰" , 27 , "F" , "Teacher" )
|
兩個(gè)角色對象生成了,狗和人還有不同的功能呀,狗會咬人,人會打狗,對不對? 怎么實(shí)現(xiàn)呢,。。想到了, 可以每個(gè)功能再寫一個(gè)函數(shù),想執(zhí)行哪個(gè)功能,直接 調(diào)用 就可以了,對不?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | def bark(d):
print ( "dog %s:wang.wang..wang..." % d[ 'name' ])
def walk(p):
print ( "person %s is walking..." % p[ 'name' ])
d1 = dog( "李闖" , "京巴" )
p1 = person( "孫海濤" , 36 , "F" , "運(yùn)維" )
p2 = person( "林海峰" , 27 , "F" , "Teacher" )
walk(p1)
bark(d1)
|
上面的功能實(shí)現(xiàn)的簡直是完美!
但是仔細(xì)玩耍一會,你就不小心干了下面這件事
1 2 | p1 = person( "孫海濤" , 36 , "F" , "運(yùn)維" )
bark(p1) #把人的對象傳給了狗的方法
|
事實(shí) 上,這并沒出錯(cuò)。很顯然,人是不能調(diào)用狗的功能的,如何在代碼級別實(shí)現(xiàn)這個(gè)限制呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | def person(name,age,sex,job):
def walk(p):
print ( "person %s is walking..." % p[ 'name' ])
data = {
'name' :name,
'age' :age,
'sex' :sex,
'job' :job,
'walk' :walk
}
return data
def dog(name,dog_type):
def bark(d):
print ( "dog %s:wang.wang..wang..." % d[ 'name' ])
data = {
'name' :name,
'type' :dog_type,
'bark' :bark
}
return data
d1 = dog( "李闖" , "京巴" )
p1 = person( "孫海濤" , 36 , "F" , "運(yùn)維" )
p2 = person( "林海峰" , 27 , "F" , "Teacher" )
p1 = person( "孫海濤" , 36 , "F" , "運(yùn)維" )
d1[ 'bark' ](p1) #把人的對象傳給了狗的方法
|
你是如此的機(jī)智,這樣就實(shí)現(xiàn)了限制人只能用人自己的功能啦。
但,我的哥,不要高興太早,剛才你只是阻止了兩個(gè)完全 不同的角色 之前的功能混用, 但有沒有可能 ,同一個(gè)種角色,但有些屬性是不同的呢? 比如 ,大家都打過cs吧,cs里有警察和恐怖份子,但因?yàn)槎?是人, 所以你寫一個(gè)角色叫 person(), 警察和恐怖份子都 可以 互相射擊,但警察不可以殺人質(zhì),恐怖分子可以,這怎么實(shí)現(xiàn)呢? 你想了說想,說,簡單,只需要在殺人質(zhì)的功能里加個(gè)判斷,如果是警察,就不讓殺不就ok了么。 沒錯(cuò), 這雖然 解決了殺人質(zhì)的問題,但其實(shí)你會發(fā)現(xiàn),警察和恐怖分子的區(qū)別還有很多,同時(shí)又有很多共性,如果 在每個(gè)區(qū)別處都 單獨(dú)做判斷,那得累死。
你想了想說, 那就直接寫2個(gè)角色吧, 反正 這么多區(qū)別, 我的哥, 不能寫兩個(gè)角色呀,因?yàn)樗麄冞€有很多共性 , 寫兩個(gè)不同的角色,就代表 相同的功能 也要重寫了,是不是我的哥? 。。。
好了, 話題就給你點(diǎn)到這, 再多說你的智商 也理解不了了!
面向過程 VS 面向?qū)ο?nbsp;
編程范式
編程是 程序 員 用特定的語法+數(shù)據(jù)結(jié)構(gòu)+算法組成的代碼來告訴計(jì)算機(jī)如何執(zhí)行任務(wù)的過程 , 一個(gè)程序是程序員為了得到一個(gè)任務(wù)結(jié)果而編寫的一組指令的集合,正所謂條條大路通羅馬,實(shí)現(xiàn)一個(gè)任務(wù)的方式有很多種不同的方式, 對這些不同的編程方式的特點(diǎn)進(jìn)行歸納總結(jié)得出來的編程方式類別,即為編程范式。 不同的編程范式本質(zhì)上代表對各種類型的任務(wù)采取的不同的解決問題的思路, 大多數(shù)語言只支持一種編程范式,當(dāng)然也有些語言可以同時(shí)支持多種編程范式。 兩種最重要的編程范式分別是面向過程編程和面向?qū)ο缶幊獭?/p>
面向過程編程(Procedural Programming) Procedural programming uses a list of instructions to tell the computer what to do step-by-step. 面向過程編程依賴 - 你猜到了- procedures,一個(gè)procedure包含一組要被進(jìn)行計(jì)算的步驟, 面向過程又被稱為top-down languages, 就是程序從上到下一步步執(zhí)行,一步步從上到下,從頭到尾的解決問題 。基本設(shè)計(jì)思路就是程序一開始是要著手解決一個(gè)大的問題,然后把一個(gè)大問題分解成很多個(gè)小問題或子過程,這些子過程再執(zhí)行的過程再繼續(xù)分解直到小問題足夠簡單到可以在一個(gè)小步驟范圍內(nèi)解決。
舉個(gè)典型的面向過程的例子, 數(shù)據(jù)庫備份, 分三步,連接數(shù)據(jù)庫,備份數(shù)據(jù)庫,測試備份文件可用性。
代碼如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | def db_conn():
print ( "connecting db..." )
def db_backup(dbname):
print ( "導(dǎo)出數(shù)據(jù)庫..." ,dbname)
print ( "將備份文件打包,移至相應(yīng)目錄..." )
def db_backup_test():
print ( "將備份文件導(dǎo)入測試庫,看導(dǎo)入是否成功" )
def main():
db_conn()
db_backup( 'my_db' )
db_backup_test()
if __name__ = = '__main__' :
main()
|
這樣做的問題也是顯而易見的,就是如果你要對程序進(jìn)行修改,對你修改的那部分有依賴的各個(gè)部分你都也要跟著修改, 舉個(gè)例子,如果程序開頭你設(shè)置了一個(gè)變量值 為1 , 但如果其它子過程依賴這個(gè)值 為1的變量才能正常運(yùn)行,那如果你改了這個(gè)變量,那這個(gè)子過程你也要修改,假如又有一個(gè)其它子程序依賴這個(gè)子過程 , 那就會發(fā)生一連串的影響,隨著程序越來越大, 這種編程方式的維護(hù)難度會越來越高。 所以我們一般認(rèn)為, 如果你只是寫一些簡單的腳本,去做一些一次性任務(wù),用面向過程的方式是極好的,但如果你要處理的任務(wù)是復(fù)雜的,且需要不斷迭代和維護(hù) 的, 那還是用面向?qū)ο笞罘奖懔恕?
面向?qū)ο缶幊?/h4>
OOP編程是利用“類”和“對象”來創(chuàng)建各種模型來實(shí)現(xiàn)對真實(shí)世界的描述,使用面向?qū)ο缶幊痰脑蛞环矫媸且驗(yàn)樗梢允钩绦虻木S護(hù)和擴(kuò)展變得更簡單,并且可以大大提高程序開發(fā)效率 ,另外,基于面向?qū)ο蟮某绦蚩梢允顾烁尤菀桌斫饽愕拇a邏輯,從而使團(tuán)隊(duì)開發(fā)變得更從容。
面向?qū)ο蟮膸讉€(gè)核心特性如下
Class 類 一個(gè)類即是對一類擁有相同屬性的對象的抽象、藍(lán)圖、原型。在類中定義了這些對象的都具備的屬性(variables(data))、共同的方法
Object 對象 一個(gè)對象即是一個(gè)類的實(shí)例化后實(shí)例,一個(gè)類必須經(jīng)過實(shí)例化后方可在程序中調(diào)用,一個(gè)類可以實(shí)例化多個(gè)對象,每個(gè)對象亦可以有不同的屬性,就像人類是指所有人,每個(gè)人是指具體的對象,人與人之前有共性,亦有不同
Encapsulation 封裝 在類中對數(shù)據(jù)的賦值、內(nèi)部調(diào)用對外部用戶是透明的,這使類變成了一個(gè)膠囊或容器,里面包含著類的數(shù)據(jù)和方法
Inheritance 繼承 一個(gè)類可以派生出子類,在這個(gè)父類里定義的屬性、方法自動被子類繼承
Polymorphism 多態(tài) 多態(tài)是面向?qū)ο蟮闹匾匦?簡單點(diǎn)說:“一個(gè)接口,多種實(shí)現(xiàn)”,指一個(gè)基類中派生出了不同的子類,且每個(gè)子類在繼承了同樣的方法名的同時(shí)又對父類的方法做了不同的實(shí)現(xiàn),這就是同一種事物表現(xiàn)出的多種形態(tài)。 編程其實(shí)就是一個(gè)將具體世界進(jìn)行抽象化的過程,多態(tài)就是抽象化的一種體現(xiàn),把一系列具體事物的共同點(diǎn)抽象出來, 再通過這個(gè)抽象的事物, 與不同的具體事物進(jìn)行對話。 對不同類的對象發(fā)出相同的消息將會有不同的行為。比如,你的老板讓所有員工在九點(diǎn)鐘開始工作, 他只要在九點(diǎn)鐘的時(shí)候說:“開始工作”即可,而不需要對銷售人員說:“開始銷售工作”,對技術(shù)人員說:“開始技術(shù)工作”, 因?yàn)椤皢T工”是一個(gè)抽象的事物, 只要是員工就可以開始工作,他知道這一點(diǎn)就行了。至于每個(gè)員工,當(dāng)然會各司其職,做各自的工作。 多態(tài)允許將子類的對象當(dāng)作父類的對象使用,某父類型的引用指向其子類型的對象,調(diào)用的方法是該子類型的方法。這里引用和調(diào)用方法的代碼編譯前就已經(jīng)決定了,而引用所指向的對象可以在運(yùn)行期間動態(tài)綁定
面向?qū)ο缶幊?Object-Oriented Programming )介紹
對于編程語言的初學(xué)者來講,OOP不是一個(gè)很容易理解的編程方式,大家雖然都按老師講的都知道OOP的三大特性是繼承、封裝、多態(tài),并且大家也都知道了如何定義類、方法等面向?qū)ο蟮某S谜Z法,但是一到真正寫程序的時(shí)候,還是很多人喜歡用函數(shù)式編程來寫代碼,特別是初學(xué)者,很容易陷入一個(gè)窘境就是“我知道面向?qū)ο?,我也會寫類,但我依然沒發(fā)現(xiàn)在使用了面向?qū)ο蠛?,對我們的程序開發(fā)效率或其它方面帶來什么好處,因?yàn)槲沂褂煤瘮?shù)編程就可以減少重復(fù)代碼并做到程序可擴(kuò)展了,為啥子還用面向?qū)ο??”?對于此,我個(gè)人覺得原因應(yīng)該還是因?yàn)槟銢]有充分了解到面向?qū)ο竽軒淼暮锰帲裉煳揖蛯懸黄P(guān)于面向?qū)ο蟮娜腴T文章,希望能幫大家更好的理解和使用面向?qū)ο缶幊獭?
無論用什么形式來編程,我們都要明確記住以下原則:
- 寫重復(fù)代碼是非常不好的低級行為
- 你寫的代碼需要經(jīng)常變更
開發(fā)正規(guī)的程序跟那種寫個(gè)運(yùn)行一次就扔了的小腳本一個(gè)很大不同就是,你的代碼總是需要不斷的更改,不是修改bug就是添加新功能等,所以為了日后方便程序的修改及擴(kuò)展,你寫的代碼一定要遵循易讀、易改的原則(專業(yè)數(shù)據(jù)叫可讀性好、易擴(kuò)展)。
如果你把一段同樣的代碼復(fù)制、粘貼到了程序的多個(gè)地方以實(shí)現(xiàn)在程序的各個(gè)地方調(diào)用 這個(gè)功能,那日后你再對這個(gè)功能進(jìn)行修改時(shí),就需要把程序里多個(gè)地方都改一遍,這種寫程序的方式是有問題的,因?yàn)槿绻悴恍⌒穆┑袅艘粋€(gè)地方?jīng)]改,那可能會導(dǎo)致整個(gè)程序的運(yùn)行都 出問題。 因此我們知道 在開發(fā)中一定要努力避免寫重復(fù)的代碼,否則就相當(dāng)于給自己再挖坑。
還好,函數(shù)的出現(xiàn)就能幫我們輕松的解決重復(fù)代碼的問題,對于需要重復(fù)調(diào)用的功能,只需要把它寫成一個(gè)函數(shù),然后在程序的各個(gè)地方直接調(diào)用這個(gè)函數(shù)名就好了,并且當(dāng)需要修改這個(gè)功能時(shí),只需改函數(shù)代碼,然后整個(gè)程序就都更新了。
其實(shí)OOP編程的主要作用也是使你的代碼修改和擴(kuò)展變的更容易,那么小白要問了,既然函數(shù)都能實(shí)現(xiàn)這個(gè)需求了,還要OOP干毛線用呢? 呵呵,說這話就像,古時(shí)候,人們打仗殺人都用刀,后來出來了槍,它的主要功能跟刀一樣,也是殺人,然后小白就問,既然刀能殺人了,那還要槍干毛線,哈哈,顯而易見,因?yàn)闃屇芨酶旄菀椎臍⑷?。函?shù)編程與OOP的主要區(qū)別就是OOP可以使程序更加容易擴(kuò)展和易更改。
小白說,我讀書少,你別騙我,口說無憑,證明一下,好吧,那我們就下面的例子證明給小白看。
相信大家都打過CS游戲吧,我們就自己開發(fā)一個(gè)簡單版的CS來玩一玩。
![](http://image103.360doc.com/DownloadImg/2017/02/0323/90624511_1.png)
暫不考慮開發(fā)場地等復(fù)雜的東西,我們先從人物角色下手, 角色很簡單,就倆個(gè),恐怖份子、警察,他們除了角色不同,其它基本都 一樣,每個(gè)人都有生命值、武器等。 咱們先用非OOP的方式寫出游戲的基本角色
1 2 3 4 5 6 7 8 9 10 11 | #role 1
name = 'Alex'
role = 'terrorist'
weapon = 'AK47'
life_value = 100
#rolw 2
name2 = 'Jack'
role2 = 'police'
weapon2 = 'B22'
life_value2 = 100
|
上面定義了一個(gè)恐怖份子Alex和一個(gè)警察Jack,但只2個(gè)人不好玩呀,一干就死了,沒意思,那我們再分別一個(gè)恐怖分子和警察吧,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | #role 1
name = 'Alex'
role = 'terrorist'
weapon = 'AK47'
life_value = 100
money = 10000
#rolw 2
name2 = 'Jack'
role2 = 'police'
weapon2 = 'B22'
life_value2 = 100
money2 = 10000
#role 3
name3 = 'Rain'
role3 = 'terrorist'
weapon3 = 'C33'
life_value3 = 100
money3 = 10000
#rolw 4
name4 = 'Eric'
role4 = 'police'
weapon4 = 'B51'
life_value4 = 100
money4 = 10000
|
4個(gè)角色雖然創(chuàng)建好了,但是有個(gè)問題就是,每創(chuàng)建一個(gè)角色,我都要單獨(dú)命名,name1,name2,name3,name4…,后面的調(diào)用的時(shí)候這個(gè)變量名你還都得記著,要是再讓多加幾個(gè)角色,估計(jì)調(diào)用時(shí)就很容易弄混啦,所以我們想一想,能否所有的角色的變量名都是一樣的,但調(diào)用的時(shí)候又能區(qū)分開分別是誰?
當(dāng)然可以,我們只需要把上面的變量改成字典的格式就可以啦。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | roles = {
1 :{ 'name' : 'Alex' ,
'role' : 'terrorist' ,
'weapon' : 'AK47' ,
'life_value' : 100 ,
'money' : 15000 ,
},
2 :{ 'name' : 'Jack' ,
'role' : 'police' ,
'weapon' : 'B22' ,
'life_value' : 100 ,
'money' : 15000 ,
},
3 :{ 'name' : 'Rain' ,
'role' : 'terrorist' ,
'weapon' : 'C33' ,
'life_value' : 100 ,
'money' : 15000 ,
},
4 :{ 'name' : 'Eirc' ,
'role' : 'police' ,
'weapon' : 'B51' ,
'life_value' : 100 ,
'money' : 15000 ,
},
}
print (roles[ 1 ]) #Alex
print (roles[ 2 ]) #Jack
|
很好,這個(gè)以后調(diào)用這些角色時(shí)只需要roles[1],roles[2]就可以啦,角色的基本屬性設(shè)計(jì)完了后,我們接下來為每個(gè)角色開發(fā)以下幾個(gè)功能
- 被打中后就會掉血的功能
- 開槍功能
- 換子彈
- 買槍
- 跑、走、跳、下蹲等動作
- 保護(hù)人質(zhì)(僅適用于警察)
- 不能殺同伴
- 。。。
我們可以把每個(gè)功能寫成一個(gè)函數(shù),類似如下:
1 2 3 4 5 6 7 8 9 10 11 | def shot(by_who):
#開了槍后要減子彈數(shù)
pass
def got_shot(who):
#中槍后要減血
who[‘life_value’] - = 10
pass
def buy_gun(who,gun_name):
#檢查錢夠不夠,買了槍后要扣錢
pass
...
|
so far so good, 繼續(xù)按照這個(gè)思路設(shè)計(jì),再完善一下代碼,游戲的簡單版就出來了,但是在往下走之前,我們來看看上面的這種代碼寫法有沒有問題,至少從上面的代碼設(shè)計(jì)中,我看到以下幾點(diǎn)缺陷:
- 每個(gè)角色定義的屬性名稱是一樣的,但這種命名規(guī)則是我們自己約定的,從程序上來講,并沒有進(jìn)行屬性合法性檢測,也就是說role 1定義的代表武器的屬性是weapon, role 2 ,3,4也是一樣的,不過如果我在新增一個(gè)角色時(shí)不小心把weapon 寫成了wepon , 這個(gè)程序本身是檢測 不到的
- terrorist 和police這2個(gè)角色有些功能是不同的,比如police是不能殺人質(zhì)的,但是terrorist可能,隨著這個(gè)游戲開發(fā)的更復(fù)雜,我們會發(fā)現(xiàn)這2個(gè)角色后續(xù)有更多的不同之處, 但現(xiàn)在的這種寫法,我們是沒辦法 把這2個(gè)角色適用的功能區(qū)分開來的,也就是說,每個(gè)角色都可以直接調(diào)用任意功能,沒有任何限制。
- 我們在上面定義了got_shot()后要減血,也就是說減血這個(gè)動作是應(yīng)該通過被擊中這個(gè)事件來引起的,我們調(diào)用get_shot(),got_shot()這個(gè)函數(shù)再調(diào)用每個(gè)角色里的life_value變量來減血。 但其實(shí)我不通過got_shot(),直接調(diào)用角色roles[role_id][‘life_value’] 減血也可以呀,但是如果這樣調(diào)用的話,那可以就是簡單粗暴啦,因?yàn)闇p血之前其它還應(yīng)該判斷此角色是否穿了防彈衣等,如果穿了的話,傷害值肯定要減少,got_shot()函數(shù)里就做了這樣的檢測,你這里直接繞過的話,程序就亂了。 因此這里應(yīng)該設(shè)計(jì) 成除了通過got_shot(),其它的方式是沒有辦法給角色減血的,不過在上面的程序設(shè)計(jì)里,是沒有辦法實(shí)現(xiàn)的。
- 現(xiàn)在需要給所有角色添加一個(gè)可以穿防彈衣的功能,那很顯然你得在每個(gè)角色里放一個(gè)屬性來存儲此角色是否穿 了防彈衣,那就要更改每個(gè)角色的代碼,給添加一個(gè)新屬性,這樣太low了,不符合代碼可復(fù)用的原則
上面這4點(diǎn)問題如果不解決,以后肯定會引出更大的坑,有同學(xué)說了,解決也不復(fù)雜呀,直接在每個(gè)功能調(diào)用時(shí)做一下角色判斷啥就好了,沒錯(cuò),你要非得這么霸王硬上弓的搞也肯定是可以實(shí)現(xiàn)的,那你自己就開發(fā)相應(yīng)的代碼來對上面提到的問題進(jìn)行處理好啦。 但這些問題其實(shí)能過OOP就可以很簡單的解決。
之前的代碼改成用OOP中的“類”來實(shí)現(xiàn)的話如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | class Role( object ):
def __init__( self ,name,role,weapon,life_value = 100 ,money = 15000 ):
self .name = name
self .role = role
self .weapon = weapon
self .life_value = life_value
self .money = money
def shot( self ):
print ( "shooting..." )
def got_shot( self ):
print ( "ah...,I got shot..." )
def buy_gun( self ,gun_name):
print ( "just bought %s" % gun_name)
r1 = Role( 'Alex' , 'police' ,'AK47’) #生成一個(gè)角色
r2 = Role( 'Jack' , 'terrorist' ,'B22’) #生成一個(gè)角色
|
先不考慮語法細(xì)節(jié),相比靠函數(shù)拼湊出來的寫法,上面用面向?qū)ο笾械念悂韺懽钪苯拥母倪M(jìn)有以下2點(diǎn):
- 代碼量少了近一半
- 角色和它所具有的功能可以一目了然看出來
在真正開始分解上面代碼含義之之前,我們現(xiàn)來了解一些類的基本定義
類的語法
1 2 3 4 5 6 7 8 9 | class Dog( object ):
print ( "hello,I am a dog!" )
d = Dog() #實(shí)例化這個(gè)類,
#此時(shí)的d就是類Dog的實(shí)例化對象
#實(shí)例化,其實(shí)就是以Dog類為模版,在內(nèi)存里開辟一塊空間,存上數(shù)據(jù),賦值成一個(gè)變量名
|
上面的代碼其實(shí)有問題,想給狗起名字傳不進(jìn)去。
1 2 3 4 5 6 7 8 9 10 11 12 13 | class Dog( object ):
def __init__( self ,name,dog_type):
self .name = name
self . type = dog_type
def sayhi( self ):
print ( "hello,I am a dog, my name is " , self .name)
d = Dog( 'LiChuang' , "京巴" )
d.sayhi()
|
為什么有__init__? 為什么有self? 此時(shí)的你一臉蒙逼,相信不畫個(gè)圖,你的智商是理解不了的!
畫圖之前, 你先注釋掉這兩句
1 2 3 4 | # d = Dog('LiChuang', "京巴")
# d.sayhi()
print (Dog)
|
沒實(shí)例直接打印Dog輸出如下
這代表什么?代表 即使不實(shí)例化,這個(gè)Dog類本身也是已經(jīng)存在內(nèi)存里的對不對, yes, cool,那實(shí)例化時(shí),會產(chǎn)生什么化學(xué)反應(yīng)呢?
![](http://image103.360doc.com/DownloadImg/2017/02/0323/90624511_2.png)
根據(jù)上圖我們得知,其實(shí)self,就是實(shí)例本身!你實(shí)例化時(shí)python會自動把這個(gè)實(shí)例本身通過self參數(shù)傳進(jìn)去。
你說好吧,假裝懂了, 但下面這段代碼你又不明白了, 為何sayhi(self),要寫個(gè)self呢?
1 2 3 4 5 | class Dog( object ):
...
def sayhi( self ):
print ( "hello,I am a dog, my name is " , self .name)
|
這個(gè)原因,我課上在講。。?! ?/p>
好了,明白 了類的基本定義,接下來我們一起分解一下上面的代碼分別 是什么意思
1 2 3 4 5 6 7 | class Role( object ): #定義一個(gè)類, class是定義類的語法,Role是類名,(object)是新式類的寫法,必須這樣寫,以后再講為什么
def __init__( self ,name,role,weapon,life_value = 100 ,money = 15000 ): #初始化函數(shù),在生成一個(gè)角色時(shí)要初始化的一些屬性就填寫在這里
self .name = name #__init__中的第一個(gè)參數(shù)self,和這里的self都 是什么意思? 看下面解釋
self .role = role
self .weapon = weapon
self .life_value = life_value
self .money = money
|
上面的這個(gè)__init__()叫做初始化方法(或構(gòu)造方法), 在類被調(diào)用時(shí),這個(gè)方法(雖然它是函數(shù)形式,但在類中就不叫函數(shù)了,叫方法)會自動執(zhí)行,進(jìn)行一些初始化的動作,所以我們這里寫的__init__(self,name,role,weapon,life_value=100,money=15000)就是要在創(chuàng)建一個(gè)角色時(shí)給它設(shè)置這些屬性,那么這第一個(gè)參數(shù)self是干毛用的呢?
初始化一個(gè)角色,就需要調(diào)用這個(gè)類一次:
1 2 | r1 = Role( 'Alex' , 'police' ,'AK47’) #生成一個(gè)角色 , 會自動把參數(shù)傳給Role下面的__init__(...)方法
r2 = Role( 'Jack' , 'terrorist' ,'B22’) #生成一個(gè)角色
|
我們看到,上面的創(chuàng)建角色時(shí),我們并沒有給__init__傳值,程序也沒未報(bào)錯(cuò),是因?yàn)椋愒谡{(diào)用它自己的__init__(…)時(shí)自己幫你給self參數(shù)賦值了,
1 2 | r1 = Role( 'Alex' , 'police' , 'AK47’) #此時(shí)self 相當(dāng)于 r1 , Role(r1,' Alex ',' police ',' AK47’)
r2 = Role( 'Jack' , 'terrorist' , 'B22’)#此時(shí)self 相當(dāng)于 r2, Role(r2,' Jack ',' terrorist ',' B22’)
|
為什么這樣子?你拉著我說你有些猶豫,怎么會這樣子?
你執(zhí)行r1 = Role('Alex','police','AK47’)時(shí),python的解釋器其實(shí)干了兩件事:
- 在內(nèi)存中開辟一塊空間指向r1這個(gè)變量名
- 調(diào)用Role這個(gè)類并執(zhí)行其中的__init__(…)方法,相當(dāng)于Role.__init__(r1,'Alex','police',’AK47’),這么做是為什么呢? 是為了把'Alex','police',’AK47’這3個(gè)值跟剛開辟的r1關(guān)聯(lián)起來,是為了把'Alex','police',’AK47’這3個(gè)值跟剛開辟的r1關(guān)聯(lián)起來,是為了把'Alex','police',’AK47’這3個(gè)值跟剛開辟的r1關(guān)聯(lián)起來,重要的事情說3次, 因?yàn)殛P(guān)聯(lián)起來后,你就可以直接r1.name, r1.weapon 這樣來調(diào)用啦。所以,為實(shí)現(xiàn)這種關(guān)聯(lián),在調(diào)用__init__方法時(shí),就必須把r1這個(gè)變量也傳進(jìn)去,否則__init__不知道要把那3個(gè)參數(shù)跟誰關(guān)聯(lián)呀。
- 明白了么哥?所以這個(gè)__init__(…)方法里的,self.name = name , self.role = role 等等的意思就是要把這幾個(gè)值 存到r1的內(nèi)存空間里。
如果還不明白的話,哥,去測試一下智商吧, 應(yīng)該不會超過70,哈哈。
為了暴露自己的智商,此時(shí)你假裝懂了,但又問, __init__(…)我懂了,但后面的那幾個(gè)函數(shù),噢 不對,后面那幾個(gè)方法 為什么也還需要self參數(shù)么? 不是在初始化角色的時(shí)候 ,就已經(jīng)把角色的屬性跟r1綁定好了么?
good question, 先來看一下上面類中的一個(gè)buy_gun的方法:
1 2 | def buy_gun( self ,gun_name):
print (“ % s has just bought % s” % ( self .name,gun_name) )
|
上面這個(gè)方法通過類調(diào)用的話要寫成如下:
1 2 | r1 = Role( 'Alex' , 'police' , 'AK47' )
r1.buy_gun( "B21”) #python 會自動幫你轉(zhuǎn)成 Role.buy_gun(r1,”B21" )
|
執(zhí)行結(jié)果
#Alex has just bought B21
依然沒給self傳值 ,但Python還是會自動的幫你把r1 賦值給self這個(gè)參數(shù), 為什么呢? 因?yàn)椋阍赽uy_gun(..)方法中可能要訪問r1的一些其它屬性呀, 比如這里就訪問 了r1的名字,怎么訪問呢?你得告訴這個(gè)方法呀,于是就把r1傳給了這個(gè)self參數(shù),然后在buy_gun里調(diào)用 self.name 就相當(dāng)于調(diào)用r1.name 啦,如果還想知道r1的生命值 有多少,直接寫成self.life_value就可以了。 說白了就是在調(diào)用類中的一個(gè)方法時(shí),你得告訴人家你是誰。
好啦, 總結(jié)一下2點(diǎn):
- 上面的這個(gè)r1 = Role('Alex','police','AK47’)動作,叫做類的“實(shí)例化”, 就是把一個(gè)虛擬的抽象的類,通過這個(gè)動作,變成了一個(gè)具體的對象了, 這個(gè)對象就叫做實(shí)例
- 剛才定義的這個(gè)類體現(xiàn)了面向?qū)ο蟮牡谝粋€(gè)基本特性,封裝,其實(shí)就是使用構(gòu)造方法將內(nèi)容封裝到某個(gè)具體對象中,然后通過對象直接或者self間接獲取被封裝的內(nèi)容
面向?qū)ο蟮奶匦裕?/strong>
封裝
封裝最好理解了。封裝是面向?qū)ο蟮奶卣髦唬菍ο蠛皖惛拍畹闹饕匦浴?/p>
封裝,也就是把客觀事物封裝成抽象的類,并且類可以把自己的數(shù)據(jù)和方法只讓可信的類或者對象操作,對不可信的進(jìn)行信息隱藏。
繼承
面向?qū)ο缶幊?(OOP) 語言的一個(gè)主要功能就是“繼承”。繼承是指這樣一種能力:它可以使用現(xiàn)有類的所有功能,并在無需重新編寫原來的類的情況下對這些功能進(jìn)行擴(kuò)展。
通過繼承創(chuàng)建的新類稱為“子類”或“派生類”。
被繼承的類稱為“基類”、“父類”或“超類”。
繼承的過程,就是從一般到特殊的過程。
要實(shí)現(xiàn)繼承,可以通過“繼承”(Inheritance)和“組合”(Composition)來實(shí)現(xiàn)。
在某些 OOP 語言中,一個(gè)子類可以繼承多個(gè)基類。但是一般情況下,一個(gè)子類只能有一個(gè)基類,要實(shí)現(xiàn)多重繼承,可以通過多級繼承來實(shí)現(xiàn)。
繼承概念的實(shí)現(xiàn)方式主要有2類:實(shí)現(xiàn)繼承、接口繼承。
實(shí)現(xiàn)繼承是指使用基類的屬性和方法而無需額外編碼的能力;
接口繼承是指僅使用屬性和方法的名稱、但是子類必須提供實(shí)現(xiàn)的能力(子類重構(gòu)爹類方法);
在考慮使用繼承時(shí),有一點(diǎn)需要注意,那就是兩個(gè)類之間的關(guān)系應(yīng)該是“屬于”關(guān)系。例如,Employee 是一個(gè)人,Manager 也是一個(gè)人,因此這兩個(gè)類都可以繼承 Person 類。但是 Leg 類卻不能繼承 Person 類,因?yàn)橥炔⒉皇且粋€(gè)人。
抽象類僅定義將由子類創(chuàng)建的一般屬性和方法。
OO開發(fā)范式大致為:劃分對象→抽象類→將類組織成為層次化結(jié)構(gòu)(繼承和合成) →用類與實(shí)例進(jìn)行設(shè)計(jì)和實(shí)現(xiàn)幾個(gè)階段。
繼承示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | #!_*_coding:utf-8_*_
#__author__:"Alex Li"
class SchoolMember( object ):
members = 0 #初始學(xué)校人數(shù)為0
def __init__( self ,name,age):
self .name = name
self .age = age
def tell( self ):
pass
def enroll( self ):
'''注冊'''
SchoolMember.members + = 1
print ( "\033[32;1mnew member [%s] is enrolled,now there are [%s] members.\033[0m " % ( self .name,SchoolMember.members))
def __del__( self ):
'''析構(gòu)方法'''
print ( "\033[31;1mmember [%s] is dead!\033[0m" % self .name)
class Teacher(SchoolMember):
def __init__( self ,name,age,course,salary):
super (Teacher, self ).__init__(name,age)
self .course = course
self .salary = salary
self .enroll()
def teaching( self ):
'''講課方法'''
print ( "Teacher [%s] is teaching [%s] for class [%s]" % ( self .name, self .course, 's12' ))
def tell( self ):
'''自我介紹方法'''
msg = '''Hi, my name is [%s], works for [%s] as a [%s] teacher !''' % ( self .name,'Oldboy', self .course)
print (msg)
class Student(SchoolMember):
def __init__( self , name,age,grade,sid):
super (Student, self ).__init__(name,age)
self .grade = grade
self .sid = sid
self .enroll()
def tell( self ):
'''自我介紹方法'''
msg = '''Hi, my name is [%s], I'm studying [%s] in [%s]!''' % ( self .name, self .grade,'Oldboy')
print (msg)
if __name__ = = '__main__' :
t1 = Teacher( "Alex" , 22 , 'Python' , 20000 )
t2 = Teacher( "TengLan" , 29 , 'Linux' , 3000 )
s1 = Student( "Qinghua" , 24 , "Python S12" , 1483 )
s2 = Student( "SanJiang" , 26 , "Python S12" , 1484 )
t1.teaching()
t2.teaching()
t1.tell()
|
多態(tài)
多態(tài)性(polymorphisn)是允許你將父對象設(shè)置成為和一個(gè)或更多的他的子對象相等的技術(shù),賦值之后,父對象就可以根據(jù)當(dāng)前賦值給它的子對象的特性以不同的方式運(yùn)作。簡單的說,就是一句話:允許將子類類型的指針賦值給父類類型的指針。
那么,多態(tài)的作用是什么呢?我們知道,封裝可以隱藏實(shí)現(xiàn)細(xì)節(jié),使得代碼模塊化;繼承可以擴(kuò)展已存在的代碼模塊(類);它們的目的都是為了——代碼重用。而多態(tài)則是為了實(shí)現(xiàn)另一個(gè)目的——接口重用!多態(tài)的作用,就是為了類在繼承和派生的時(shí)候,保證使用“家譜”中任一類的實(shí)例的某一屬性時(shí)的正確調(diào)用。
Pyhon不直接支持多態(tài),但可以間接實(shí)現(xiàn)
通過Python模擬的多態(tài)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | class Animal:
def __init__( self , name): # Constructor of the class
self .name = name
def talk( self ): # Abstract method, defined by convention only
raise NotImplementedError( "Subclass must implement abstract method" )
class Cat(Animal):
def talk( self ):
return 'Meow!'
class Dog(Animal):
def talk( self ):
return 'Woof! Woof!'
animals = [Cat( 'Missy' ),
Dog( 'Lassie' )]
for animal in animals:
print animal.name + ': ' + animal.talk()
|
本節(jié)作業(yè): 選課系統(tǒng)
角色:學(xué)校、學(xué)員、課程、講師 要求: 1. 創(chuàng)建北京、上海 2 所學(xué)校 2. 創(chuàng)建linux , python , go 3個(gè)課程 , linux\py 在北京開, go 在上海開 3. 課程包含,周期,價(jià)格,通過學(xué)校創(chuàng)建課程 4. 通過學(xué)校創(chuàng)建班級, 班級關(guān)聯(lián)課程、講師 5. 創(chuàng)建學(xué)員時(shí),選擇學(xué)校,關(guān)聯(lián)班級 5. 創(chuàng)建講師角色時(shí)要關(guān)聯(lián)學(xué)校, 6. 提供兩個(gè)角色接口 6.1 學(xué)員視圖, 可以注冊, 交學(xué)費(fèi), 選擇班級, 6.2 講師視圖, 講師可管理自己的班級, 上課時(shí)選擇班級, 查看班級學(xué)員列表 , 修改所管理的學(xué)員的成績 6.3 管理視圖,創(chuàng)建講師, 創(chuàng)建班級,創(chuàng)建課程
7. 上面的操作產(chǎn)生的數(shù)據(jù)都通過pickle序列化保存到文件里
|