08-面向对象----
一 面向?qū)ο蟮某绦蛟O(shè)計的由來
請參考:http://www.cnblogs.com/linhaifeng/articles/6428835.html
二 什么是面向?qū)ο蟮某绦蛟O(shè)計及為什么要有它
面向過程的程序設(shè)計的核心是過程,過程即解決問題的步驟,面向過程的設(shè)計就好比精心設(shè)計好一條流水線,考慮周全什么時候處理什么東西。
優(yōu)點是:極大的降低了程序的復(fù)雜度
缺點是:一套流水線或者流程就是用來解決一個問題,生產(chǎn)汽水的流水線無法生產(chǎn)汽車,即便是能,也得是大改,改一個組件,牽一發(fā)而動全身。
應(yīng)用場景:一旦完成基本很少改變的場景,著名的例子有Linux內(nèi)核,git,以及Apache HTTP Server等。
面向?qū)ο蟮某绦蛟O(shè)計的核心是對象,要理解對象為何物,必須把自己當(dāng)成上帝,上帝眼里世間存在的萬物皆為對象,不存在的也可以創(chuàng)造出來。面向?qū)ο蟮某绦蛟O(shè)計好比如來設(shè)計西游記,如來要解決的問題是把經(jīng)書傳給東土大唐,如來想了想解決這個問題需要四個人:唐僧,沙和尚,豬八戒,孫悟空,每個人都有各自的特征和技能(這就是對象的概念,特征和技能分別對應(yīng)對象的數(shù)據(jù)屬性和方法屬性),然而這并不好玩,于是如來又安排了一群妖魔鬼怪,為了防止師徒四人在取經(jīng)路上被搞死,又安排了一群神仙保駕護(hù)航,這些都是對象。然后取經(jīng)開始,師徒四人與妖魔鬼怪神仙交互著直到最后取得真經(jīng)。如來根本不會管師徒四人按照什么流程去取。
面向?qū)ο蟮某绦蛟O(shè)計的
優(yōu)點是:解決了程序的擴(kuò)展性。對某一個對象單獨修改,會立刻反映到整個體系中,如對游戲中一個人物參數(shù)的特征和技能修改都很容易。
缺點:可控性差,無法向面向過程的程序設(shè)計流水線式的可以很精準(zhǔn)的預(yù)測問題的處理流程與結(jié)果,面向?qū)ο蟮某绦蛞坏╅_始就由對象之間的交互解決問題,即便是上帝也無法預(yù)測最終結(jié)果。于是我們經(jīng)常看到一個游戲人某一參數(shù)的修改極有可能導(dǎo)致陰霸的技能出現(xiàn),一刀砍死3個人,這個游戲就失去平衡。
應(yīng)用場景:需求經(jīng)常變化的軟件,一般需求的變化都集中在用戶層,互聯(lián)網(wǎng)應(yīng)用,企業(yè)內(nèi)部軟件,游戲等都是面向?qū)ο蟮某绦蛟O(shè)計大顯身手的好地方
面向?qū)ο蟮某绦蛟O(shè)計并不是全部。對于一個軟件質(zhì)量來說,面向?qū)ο蟮某绦蛟O(shè)計只是用來解決擴(kuò)展性。
三 類和對象
提示:python的class術(shù)語與c++有一定區(qū)別,與 Modula-3更像。
python中一切皆為對象,且python3統(tǒng)一了類與類型的概念,類型就是類,所以,不管你信不信,你已經(jīng)使用了很長時間的類了
1 >>> dict #類型dict就是類dict 2 <class 'dict'> 3 >>> d=dict(name='egon') #實例化 4 >>> d.pop('name') #向d發(fā)一條消息,執(zhí)行d的方法pop 5 'egon'基于面向?qū)ο笤O(shè)計一個款游戲:英雄聯(lián)盟,每個玩家選一個英雄,每個英雄都有自己的特征和和技能,特征即數(shù)據(jù)屬性,技能即方法屬性,特征與技能的結(jié)合體就一個對象。
從一組對象中提取相似的部分就是類,類也是特征與技能的結(jié)合體,特征即數(shù)據(jù)并且是所有對象共享的數(shù)據(jù),技能即函數(shù)屬性并且是所有對象共享的函數(shù)屬性。
garen_hero.Q()稱為向garen_hero這個對象發(fā)送了一條消息,讓他去執(zhí)行Q這個函數(shù),完成一個功能,類似的有:
garen_hero.W()
garen_hero.E()
garen_hero.R()
一個英雄可以攻擊另外一個英雄,這就是對象之間的交互
garen_hero.attack(Riven)
?
?
3.1 類相關(guān)知識
3.1.1 初識類
在python中聲明函數(shù)與聲明類很相似
聲明函數(shù)
1 def functionName(args): 2 '函數(shù)文檔字符串' 3 函數(shù)體 1 ''' 2 class 類名: 3 '類的文檔字符串' 4 類體 5 ''' 6 7 #我們創(chuàng)建一個類 8 class Data: 9 pass 1 大前提: 2 1.只有在python2中才分新式類和經(jīng)典類,python3中統(tǒng)一都是新式類 3 2.新式類和經(jīng)典類聲明的最大不同在于,所有新式類必須繼承至少一個父類 4 3.所有類甭管是否顯式聲明父類,都有一個默認(rèn)繼承object父類(講繼承時會講,先記住) 5 在python2中的區(qū)分 6 經(jīng)典類: 7 class 類名: 8 pass 9 10 經(jīng)典類: 11 class 類名(父類): 12 pass 13 14 在python3中,上述兩種定義方式全都是新式類 在本節(jié)開頭介紹得出結(jié)論,類是數(shù)據(jù)與函數(shù)的結(jié)合,二者稱為類的屬性 class Garen: #定義英雄蓋倫的類,不同的玩家可以用它實例出自己英雄;camp='Demacia' #所有玩家的英雄(蓋倫)的陣營都是Demacia;def attack(self,enemy): #普通攻擊技能,enemy是敵人;enemy.life_value-=self.aggressivity #根據(jù)自己的攻擊力,攻擊敵人就減掉敵人的生命值。3.1.2 類有兩種作用:屬性引用和實例化
3.1.2.1 屬性引用(類名.屬性)
>>> Garen.camp #引用類的數(shù)據(jù)屬性,該屬性與所有對象/實例共享 'Demacia' >>> Garen.attack #引用類的函數(shù)屬性,該屬性也共享 <function Garen.attack at 0x101356510> >>> Garen.name='Garen' #增加屬性 >>> del Garen.name #刪除屬性3.1.2.2 實例化(__init__與self)
類名加括號就是實例化,會自動觸發(fā)__init__函數(shù)的運行,可以用它來為每個實例定制自己的特征
class Garen: #定義英雄蓋倫的類,不同的玩家可以用它實例出自己英雄;camp='Demacia' #所有玩家的英雄(蓋倫)的陣營都是Demacia;def __init__(self,nickname,aggressivity=58,life_value=455): #英雄的初始攻擊力58...;self.nickname=nickname #為自己的蓋倫起個別名;self.aggressivity=aggressivity #英雄都有自己的攻擊力;self.life_value=life_value #英雄都有自己的生命值;def attack(self,enemy): #普通攻擊技能,enemy是敵人;enemy.life_value-=self.aggressivity #根據(jù)自己的攻擊力,攻擊敵人就減掉敵人的生命值。實例化:類名+括號
>>> g1=Garen('草叢倫') #就是在執(zhí)行Garen.__init__(g1,'草叢倫'),然后執(zhí)行__init__內(nèi)的代碼g1.nickname=‘草叢倫’等self的作用是在實例化時自動將對象/實例本身傳給__init__的第一個參數(shù),self可以是任意名字,但是瞎幾把寫別人就看不懂了。
這種自動傳遞的機(jī)制還體現(xiàn)在g1.attack的調(diào)用上,后續(xù)會介紹。
一:我們定義的類的屬性到底存到哪里了?有兩種方式查看 dir(類名):查出的是一個名字列表 類名.__dict__:查出的是一個字典,key為屬性名,value為屬性值二:特殊的類屬性 類名.__name__# 類的名字(字符串) 類名.__doc__# 類的文檔字符串 類名.__base__# 類的第一個父類(在講繼承時會講) 類名.__bases__# 類所有父類構(gòu)成的元組(在講繼承時會講) 類名.__dict__# 類的字典屬性 類名.__module__# 類定義所在的模塊 類名.__class__# 實例對應(yīng)的類(僅新式類中)3.2 對象相關(guān)知識
對象是關(guān)于類而實際存在的一個例子,即實例
>>> g1=Garen('草叢倫') #類實例化得到g1這個實例,基于該實例我們講解實例相關(guān)知識3.2.1 對象/實例只有一種作用:屬性引用
#對象/實例本身其實只有數(shù)據(jù)屬性 >>> g1.nickname '草叢倫' >>> g1.aggressivity 58 >>> g1.life_value 455 ''' 查看實例屬性 同樣是dir和內(nèi)置__dict__兩種方式 特殊實例屬性 __class__ __dict__ .... '''對象/實例本身只有數(shù)據(jù)屬性,但是python的class機(jī)制會將類的函數(shù)綁定到對象上,稱為對象的方法,或者叫綁定方法
>>> g1.attack #對象的綁定方法 <bound method Garen.attack of <__main__.Garen object at 0x101348dd8>>>>> Garen.attack #對象的綁定方法attack本質(zhì)就是調(diào)用類的函數(shù)attack的功能,二者是一種綁定關(guān)系 <function Garen.attack at 0x101356620>對象的綁定方法的特別之處在于:obj.func()會把obj傳給func的第一個參數(shù)。
3.3 對象之間的交互
我們可以仿照garen類再創(chuàng)建一個Riven類
class Riven:camp='Noxus' #所有玩家的英雄(銳雯)的陣營都是Noxus;def __init__(self,nickname,aggressivity=54,life_value=414): #英雄的初始攻擊力54;self.nickname=nickname #為自己的銳雯起個別名;self.aggressivity=aggressivity #英雄都有自己的攻擊力;self.life_value=life_value #英雄都有自己的生命值;def attack(self,enemy): #普通攻擊技能,enemy是敵人;enemy.life_value-=self.aggressivity #根據(jù)自己的攻擊力,攻擊敵人就減掉敵人的生命值。實例出一個Riven來
>>> r1=Riven('銳雯雯')交互:銳雯雯攻擊草叢倫,反之一樣
>>> g1.life_value 455 >>> r1.attack(g1) >>> g1.life_value 4013.4 類名稱空間與對象/實例名稱空間
創(chuàng)建一個類就會創(chuàng)建一個類的名稱空間,用來存儲類中定義的所有名字,這些名字稱為類的屬性
而類有兩種屬性:數(shù)據(jù)屬性和函數(shù)屬性
其中類的數(shù)據(jù)屬性是共享給所有對象的
>>> id(r1.camp) #本質(zhì)就是在引用類的camp屬性,二者id一樣 4315241024 >>> id(Riven.camp) 4315241024而類的函數(shù)屬性是綁定到所有對象的:
>>> id(r1.attack) 4302501512 >>> id(Riven.attack) 4315244200''' r1.attack就是在執(zhí)行Riven.attack的功能,python的class機(jī)制會將Riven的函數(shù)屬性attack綁定給r1,r1相當(dāng)于拿到了一個指針,指向Riven類的attack功能除此之外r1.attack()會將r1傳給attack的第一個參數(shù) '''創(chuàng)建一個對象/實例就會創(chuàng)建一個對象/實例的名稱空間,存放對象/實例的名字,稱為對象/實例的屬性
在obj.name會先從obj自己的名稱空間里找name,找不到則去類中找,類也找不到就找父類...最后都找不到就拋出異常?
3.5 小結(jié)
瑞文
class Riven:camp='Noxus'def __init__(self,nickname,aggressivity=54,life_value=414,money=1001,armor=3):self.nickname=nicknameself.aggressivity=aggressivityself.life_value=life_valueself.money=moneyself.armor=armordef attack(self,enemy):damage_value=self.aggressivity-enemy.armorenemy.life_value-=damage_value蓋倫
class Garen:camp='Demacia'def __init__(self,nickname,aggressivity=58,life_value=455,money=100,armor=10):self.nickname=nicknameself.aggressivity=aggressivityself.life_value=life_valueself.money=moneyself.armor=armordef attack(self,enemy):damage_value=self.aggressivity-enemy.armorenemy.life_value-=damage_value定義裝備:
class BlackCleaver:def __init__(self,price=475,aggrev=9,life_value=100):self.price=priceself.aggrev=aggrevself.life_value=life_valuedef update(self,obj):obj.money-=self.price #減錢obj.aggressivity+=self.aggrev #加攻擊obj.life_value+=self.life_value #加生命值def fire(self,obj): #這是該裝備的主動技能,噴火,燒死對方obj.life_value-=1000 #假設(shè)火燒的攻擊力是1000測試交互
r1=Riven('草叢倫') g1=Garen('蓋文') b1=BlackCleaver()print(r1.aggressivity,r1.life_value,r1.money) #r1的攻擊力,生命值,護(hù)甲if r1.money > b1.price:r1.b1=b1b1.update(r1)print(r1.aggressivity,r1.life_value,r1.money) #r1的攻擊力,生命值,護(hù)甲print(g1.life_value) r1.attack(g1) #普通攻擊 print(g1.life_value) r1.b1.fire(g1) #用裝備攻擊 print(g1.life_value) #g1的生命值小于0就死了按照這種思路一點一點的設(shè)計類和對象,最終你完全可以實現(xiàn)一個對戰(zhàn)類游戲。
四 繼承與派生
4.1 什么是繼承
繼承是一種創(chuàng)建新的類的方式,在python中,新建的類可以繼承自一個或者多個父類,原始類稱為基類或超類,新建的類稱為派生類或子類。
python中類的繼承分為:單繼承和多繼承
class ParentClass1: #定義父類passclass ParentClass2: #定義父類passclass SubClass1(ParentClass1): #單繼承,基類是ParentClass1,派生類是SubClasspassclass SubClass2(ParentClass1,ParentClass2): #python支持多繼承,用逗號分隔開多個繼承的類pass查看繼承
>>> SubClass1.__bases__ (<class '__main__.ParentClass1'>,) >>> SubClass2.__bases__ (<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)提示:如果沒有指定基類,python的類會默認(rèn)繼承object類,object是所有python類的基類,它提供了一些常見方法(如__str__)的實現(xiàn)。
>>> ParentClass1.__bases__ (<class 'object'>,) >>> ParentClass2.__bases__ (<class 'object'>,)4.2 繼承與抽象(先抽象再繼承)
抽象即抽取類似或者說比較像的部分。
抽象分成兩個層次:?
1.將奧巴馬和梅西這倆對象比較像的部分抽取成類;?
2.將人,豬,狗這三個類比較像的部分抽取成父類。
抽象最主要的作用是劃分類別(可以隔離關(guān)注點,降低復(fù)雜度)
繼承:是基于抽象的結(jié)果,通過編程語言去實現(xiàn)它,肯定是先經(jīng)歷抽象這個過程,才能通過繼承的方式去表達(dá)出抽象的結(jié)構(gòu)。
抽象只是分析和設(shè)計的過程中,一個動作或者說一種技巧,通過抽象可以得到類
4.3 繼承與重用性
==========================第一部分 例如貓可以:喵喵叫、吃、喝、拉、撒狗可以:汪汪叫、吃、喝、拉、撒如果我們要分別為貓和狗創(chuàng)建一個類,那么就需要為 貓 和 狗 實現(xiàn)他們所有的功能,偽代碼如下:#貓和狗有大量相同的內(nèi)容 class 貓:def 喵喵叫(self):print '喵喵叫'def 吃(self):# do somethingdef 喝(self):# do somethingdef 拉(self):# do somethingdef 撒(self):# do somethingclass 狗:def 汪汪叫(self):print '喵喵叫'def 吃(self):# do somethingdef 喝(self):# do somethingdef 拉(self):# do somethingdef 撒(self):# do something==========================第二部分 上述代碼不難看出,吃、喝、拉、撒是貓和狗都具有的功能,而我們卻分別的貓和狗的類中編寫了兩次。如果使用 繼承 的思想,如下實現(xiàn):動物:吃、喝、拉、撒貓:喵喵叫(貓繼承動物的功能)狗:汪汪叫(狗繼承動物的功能)偽代碼如下: class 動物:def 吃(self):# do somethingdef 喝(self):# do somethingdef 拉(self):# do somethingdef 撒(self):# do something# 在類后面括號中寫入另外一個類名,表示當(dāng)前類繼承另外一個類 class 貓(動物):def 喵喵叫(self):print '喵喵叫'# 在類后面括號中寫入另外一個類名,表示當(dāng)前類繼承另外一個類 class 狗(動物):def 汪汪叫(self):print '喵喵叫'==========================第三部分 #繼承的代碼實現(xiàn) class Animal:def eat(self):print("%s 吃 " %self.name)def drink(self):print ("%s 喝 " %self.name)def shit(self):print ("%s 拉 " %self.name)def pee(self):print ("%s 撒 " %self.name)class Cat(Animal):def __init__(self, name):self.name = nameself.breed = '貓'def cry(self):print('喵喵叫')class Dog(Animal):def __init__(self, name):self.name = nameself.breed='狗'def cry(self):print('汪汪叫')# ######### 執(zhí)行 #########c1 = Cat('小白家的小黑貓') c1.eat()c2 = Cat('小黑的小白貓') c2.drink()d1 = Dog('胖子家的小瘦狗') d1.eat() view code在開發(fā)程序的過程中,如果我們定義了一個類A,然后又想新建立另外一個類B,但是類B的大部分內(nèi)容與類A的相同時
我們不可能從頭開始寫一個類B,這就用到了類的繼承的概念。
通過繼承的方式新建類B,讓B繼承A,B會‘遺傳’A的所有屬性(數(shù)據(jù)屬性和函數(shù)屬性),實現(xiàn)代碼重用
class Hero:def __init__(self,nickname,aggressivity,life_value):self.nickname=nicknameself.aggressivity=aggressivityself.life_value=life_valuedef move_forward(self):print('%s move forward' %self.nickname)def move_backward(self):print('%s move backward' %self.nickname)def move_left(self):print('%s move forward' %self.nickname)def move_right(self):print('%s move forward' %self.nickname)def attack(self,enemy):enemy.life_value-=self.aggressivity class Garen(Hero):passclass Riven(Hero):passg1=Garen('草叢倫',100,300) r1=Riven('銳雯雯',57,200)print(g1.life_value) r1.attack(g1) print(g1.life_value)''' 運行結(jié)果 300 243 ''' view code提示:用已經(jīng)有的類建立一個新的類,這樣就重用了已經(jīng)有的軟件中的一部分設(shè)置大部分,大大生了編程工作量,這就是常說的軟件重用,不僅可以重用自己的類,也可以繼承別人的,比如標(biāo)準(zhǔn)庫,來定制新的數(shù)據(jù)類型,這樣就是大大縮短了軟件開發(fā)周期,對大型軟件開發(fā)來說,意義重大.
注意:像g1.life_value之類的屬性引用,會先從實例中找life_value然后去類中找,然后再去父類中找...直到最頂級的父類。
當(dāng)然子類也可以添加自己新的屬性或者在自己這里重新定義這些屬性(不會影響到父類),需要注意的是,一旦重新定義了自己的屬性且與父類重名,那么調(diào)用新增的屬性時,就以自己為準(zhǔn)了。
class Riven(Hero):camp='Noxus'def attack(self,enemy): #在自己這里定義新的attack,不再使用父類的attack,且不會影響父類print('from riven')def fly(self): #在自己這里定義新的print('%s is flying' %self.nickname) view code在子類中,新建的重名的函數(shù)屬性,在編輯函數(shù)內(nèi)功能的時候,有可能需要重用父類中重名的那個函數(shù)功能,應(yīng)該是用調(diào)用普通函數(shù)的方式,即:類名.func(),此時就與調(diào)用普通函數(shù)無異了,因此即便是self參數(shù)也要為其傳值
class Riven(Hero):camp='Noxus'def __init__(self,nickname,aggressivity,life_value,skin):Hero.__init__(self,nickname,aggressivity,life_value) #調(diào)用父類功能self.skin=skin #新屬性def attack(self,enemy): #在自己這里定義新的attack,不再使用父類的attack,且不會影響父類Hero.attack(self,enemy) #調(diào)用功能print('from riven')def fly(self): #在自己這里定義新的print('%s is flying' %self.nickname)r1=Riven('銳雯雯',57,200,'比基尼') r1.fly() print(r1.skin)''' 運行結(jié)果 銳雯雯 is flying 比基尼''' view code4.4 組合與重用性
軟件重用的重要方式除了繼承之外還有另外一種方式,即:組合
組合指的是,在一個類中以另外一個類的對象作為數(shù)據(jù)屬性,稱為類的組合
其實早在3.5小節(jié)中我們就體會了組合的用法,比如一個英雄有一個裝備
>>> class Equip: #武器裝備類 ... def fire(self): ... print('release Fire skill') ... >>> class Riven: #英雄Riven的類,一個英雄需要有裝備,因而需要組合Equip類 ... camp='Noxus' ... def __init__(self,nickname): ... self.nickname=nickname ... self.equip=Equip() #用Equip類產(chǎn)生一個裝備,賦值給實例的equip屬性 ... >>> r1=Riven('銳雯雯') >>> r1.equip.fire() #可以使用組合的類產(chǎn)生的對象所持有的方法 release Fire skill view code組合與繼承都是有效地利用已有類的資源的重要方式。但是二者的概念和使用場景皆不同,
1.繼承的方式
通過繼承建立了派生類與基類之間的關(guān)系,它是一種'是'的關(guān)系,比如白馬是馬,人是動物。
當(dāng)類之間有很多相同的功能,提取這些共同的功能做成基類,用繼承比較好,比如教授是老師
>>> class Teacher: ... def __init__(self,name,gender): ... self.name=name ... self.gender=gender ... def teach(self): ... print('teaching') ... >>> >>> class Professor(Teacher): ... pass ... >>> p1=Professor('egon','male') >>> p1.teach() teaching view code2.組合的方式
用組合的方式建立了類與組合的類之間的關(guān)系,它是一種‘有’的關(guān)系,比如教授有生日,教授教python課程
class BirthDate:def __init__(self,year,month,day):self.year=yearself.month=monthself.day=dayclass Couse:def __init__(self,name,price,period):self.name=nameself.price=priceself.period=periodclass Teacher:def __init__(self,name,gender):self.name=nameself.gender=genderdef teach(self):print('teaching') class Professor(Teacher):def __init__(self,name,gender,birth,course):Teacher.__init__(self,name,gender)self.birth=birthself.course=coursep1=Professor('egon','male',BirthDate('1995','1','27'),Couse('python','28000','4 months'))print(p1.birth.year,p1.birth.month,p1.birth.day) print(p1.course.name,p1.course.price,p1.course.period) ''' 運行結(jié)果: 1995 1 27 python 28000 4 months ''' view code當(dāng)類之間有顯著不同,并且較小的類是較大的類所需要的組件時,用組合比較好
4.5 接口與歸一化設(shè)計
1.什么是接口
=================第一部分:Java 語言中的接口很好的展現(xiàn)了接口的含義: IAnimal.java /* * Java的Interface很好的體現(xiàn)了我們前面分析的接口的特征: * 1)是一組功能的集合,而不是一個功能 * 2)接口的功能用于交互,所有的功能都是public,即別的對象可操作 * 3)接口只定義函數(shù),但不涉及函數(shù)實現(xiàn) * 4)這些功能是相關(guān)的,都是動物相關(guān)的功能,但光合作用就不適宜放到IAnimal里面了 */package com.oo.demo; public interface IAnimal {public void eat();public void run();public void sleep();public void speak(); }=================第二部分:Pig.java:豬”的類設(shè)計,實現(xiàn)了IAnnimal接口 package com.oo.demo; public class Pig implements IAnimal{ //如下每個函數(shù)都需要詳細(xì)實現(xiàn)public void eat(){System.out.println("Pig like to eat grass");}public void run(){System.out.println("Pig run: front legs, back legs");}public void sleep(){System.out.println("Pig sleep 16 hours every day");}public void speak(){System.out.println("Pig can not speak"); } }=================第三部分:Person2.java /* *實現(xiàn)了IAnimal的“人”,有幾點說明一下: * 1)同樣都實現(xiàn)了IAnimal的接口,但“人”和“豬”的實現(xiàn)不一樣,為了避免太多代碼導(dǎo)致影響閱讀,這里的代碼簡化成一行,但輸出的內(nèi)容不一樣,實際項目中同一接口的同一功能點,不同的類實現(xiàn)完全不一樣 * 2)這里同樣是“人”這個類,但和前面介紹類時給的類“Person”完全不一樣,這是因為同樣的邏輯概念,在不同的應(yīng)用場景下,具備的屬性和功能是完全不一樣的 */package com.oo.demo; public class Person2 implements IAnimal {public void eat(){System.out.println("Person like to eat meat");}public void run(){System.out.println("Person run: left leg, right leg");}public void sleep(){System.out.println("Person sleep 8 hours every dat");}public void speak(){System.out.println("Hellow world, I am a person");} }=================第四部分:Tester03.java package com.oo.demo;public class Tester03 {public static void main(String[] args) {System.out.println("===This is a person===");IAnimal person = new Person2();person.eat();person.run();person.sleep();person.speak();System.out.println("\n===This is a pig===");IAnimal pig = new Pig();pig.eat();pig.run();pig.sleep();pig.speak();} } view code繼承有兩種用途:
一:繼承基類的方法,并且做出自己的改變或者擴(kuò)展(代碼重用)
二:聲明某個子類兼容于某基類,定義一個接口類Interface,接口類中定義了一些接口名(就是函數(shù)名)且并未實現(xiàn)接口的功能,子類繼承接口類,并且實現(xiàn)接口中的功能
class Interface:#定義接口Interface類來模仿接口的概念,python中壓根就沒有interface關(guān)鍵字來定義一個接口。def read(self): #定接口函數(shù)readpassdef write(self): #定義接口函數(shù)writepassclass Txt(Interface): #文本,具體實現(xiàn)read和writedef read(self):print('文本數(shù)據(jù)的讀取方法')def write(self):print('文本數(shù)據(jù)的讀取方法')class Sata(Interface): #磁盤,具體實現(xiàn)read和writedef read(self):print('硬盤數(shù)據(jù)的讀取方法')def write(self):print('硬盤數(shù)據(jù)的讀取方法')class Process(All_file):def read(self):print('進(jìn)程數(shù)據(jù)的讀取方法')def write(self):print('進(jìn)程數(shù)據(jù)的讀取方法') view code實踐中,繼承的第一種含義意義并不很大,甚至常常是有害的。因為它使得子類與基類出現(xiàn)強(qiáng)耦合。
繼承的第二種含義非常重要。它又叫“接口繼承”。
接口繼承實質(zhì)上是要求“做出一個良好的抽象,這個抽象規(guī)定了一個兼容接口,使得外部調(diào)用者無需關(guān)心具體細(xì)節(jié),可一視同仁的處理實現(xiàn)了特定接口的所有對象”——這在程序設(shè)計上,叫做歸一化。
歸一化使得高層的外部使用者可以不加區(qū)分的處理所有接口兼容的對象集合——就好象linux的泛文件概念一樣,所有東西都可以當(dāng)文件處理,不必關(guān)心它是內(nèi)存、磁盤、網(wǎng)絡(luò)還是屏幕(當(dāng)然,對底層設(shè)計者,當(dāng)然也可以區(qū)分出“字符設(shè)備”和“塊設(shè)備”,然后做出針對性的設(shè)計:細(xì)致到什么程度,視需求而定)。
在python中根本就沒有一個叫做interface的關(guān)鍵字,上面的代碼只是看起來像接口,其實并沒有起到接口的作用,子類完全可以不用去實現(xiàn)接口 ,如果非要去模仿接口的概念,可以借助第三方模塊:
http://pypi.python.org/pypi/zope.interface
twisted的twisted\internet\interface.py里使用zope.interface
文檔https://zopeinterface.readthedocs.io/en/latest/
2. 為何要用接口
接口提取了一群類共同的函數(shù),可以把接口當(dāng)做一個函數(shù)的集合。
然后讓子類去實現(xiàn)接口中的函數(shù)。
這么做的意義在于歸一化,什么叫歸一化,就是只要是基于同一個接口實現(xiàn)的類,那么所有的這些類產(chǎn)生的對象在使用時,從用法上來說都一樣。
歸一化,讓使用者無需關(guān)心對象的類是什么,只需要的知道這些對象都具備某些功能就可以了,這極大地降低了使用者的使用難度。
比如:我們定義一個動物接口,接口里定義了有跑、吃、呼吸等接口函數(shù),這樣老鼠的類去實現(xiàn)了該接口,松鼠的類也去實現(xiàn)了該接口,由二者分別產(chǎn)生一只老鼠和一只松鼠送到你面前,即便是你分別不到底哪只是什么鼠你肯定知道他倆都會跑,都會吃,都能呼吸。
再比如:我們有一個汽車接口,里面定義了汽車所有的功能,然后由本田汽車的類,奧迪汽車的類,大眾汽車的類,他們都實現(xiàn)了汽車接口,這樣就好辦了,大家只需要學(xué)會了怎么開汽車,那么無論是本田,還是奧迪,還是大眾我們都會開了,開的時候根本無需關(guān)心我開的是哪一類車,操作手法(函數(shù)調(diào)用)都一樣
4.6 抽象類
1 什么是抽象類
??? 與java一樣,python也有抽象類的概念但是同樣需要借助模塊實現(xiàn),抽象類是一個特殊的類,它的特殊之處在于只能被繼承,不能被實例化
2 為什么要有抽象類
??? 如果說類是從一堆對象中抽取相同的內(nèi)容而來的,那么抽象類就是從一堆類中抽取相同的內(nèi)容而來的,內(nèi)容包括數(shù)據(jù)屬性和函數(shù)屬性。
比如我們有香蕉的類,有蘋果的類,有桃子的類,從這些類抽取相同的內(nèi)容就是水果這個抽象的類,你吃水果時,要么是吃一個具體的香蕉,要么是吃一個具體的桃子。。。。。。你永遠(yuǎn)無法吃到一個叫做水果的東西。
??? 從設(shè)計角度去看,如果類是從現(xiàn)實對象抽象而來的,那么抽象類就是基于類抽象而來的。
從實現(xiàn)角度來看,抽象類與普通類的不同之處在于:抽象類中只能有抽象方法(沒有實現(xiàn)功能),該類不能被實例化,只能被繼承,且子類必須實現(xiàn)抽象方法。這一點與接口有點類似,但其實是不同的,即將揭曉答案
3. 在python中實現(xiàn)抽象類
#_*_coding:utf-8_*_ #一切皆文件 import abc #利用abc模塊實現(xiàn)抽象類class All_file(metaclass=abc.ABCMeta):all_type='file'@abc.abstractmethod #定義抽象方法,無需實現(xiàn)功能def read(self):'子類必須定義讀功能'pass@abc.abstractmethod #定義抽象方法,無需實現(xiàn)功能def write(self):'子類必須定義寫功能'pass# class Txt(All_file): # pass # # t1=Txt() #報錯,子類沒有定義抽象方法class Txt(All_file): #子類繼承抽象類,但是必須定義read和write方法def read(self):print('文本數(shù)據(jù)的讀取方法')def write(self):print('文本數(shù)據(jù)的讀取方法')class Sata(All_file): #子類繼承抽象類,但是必須定義read和write方法def read(self):print('硬盤數(shù)據(jù)的讀取方法')def write(self):print('硬盤數(shù)據(jù)的讀取方法')class Process(All_file): #子類繼承抽象類,但是必須定義read和write方法def read(self):print('進(jìn)程數(shù)據(jù)的讀取方法')def write(self):print('進(jìn)程數(shù)據(jù)的讀取方法')wenbenwenjian=Txt()yingpanwenjian=Sata()jinchengwenjian=Process()#這樣大家都是被歸一化了,也就是一切皆文件的思想 wenbenwenjian.read() yingpanwenjian.write() jinchengwenjian.read()print(wenbenwenjian.all_type) print(yingpanwenjian.all_type) print(jinchengwenjian.all_type) view code4. 抽象類與接口
抽象類的本質(zhì)還是類,指的是一組類的相似性,包括數(shù)據(jù)屬性(如all_type)和函數(shù)屬性(如read、write),而接口只強(qiáng)調(diào)函數(shù)屬性的相似性。
抽象類是一個介于類和接口直接的一個概念,同時具備類和接口的部分特性,可以用來實現(xiàn)歸一化設(shè)計
4.7 繼承實現(xiàn)的原理(繼承順序)
1 繼承順序
class A(object):def test(self):print('from A')class B(A):def test(self):print('from B')class C(A):def test(self):print('from C')class D(B):def test(self):print('from D')class E(C):def test(self):print('from E')class F(D,E):# def test(self):# print('from F')pass f1=F() f1.test() print(F.__mro__) #只有新式才有這個屬性可以查看線性列表,經(jīng)典類沒有這個屬性#新式類繼承順序:F->D->B->E->C->A #經(jīng)典類繼承順序:F->D->B->A->E->C #python3中統(tǒng)一都是新式類 #pyhon2中才分新式類與經(jīng)典類 view code2 繼承原理(python如何實現(xiàn)的繼承)
python到底是如何實現(xiàn)繼承的,對于你定義的每一個類,python會計算出一個方法解析順序(MRO)列表,這個MRO列表就是一個簡單的所有基類的線性順序列表,例如
>>> F.mro() #等同于F.__mro__ [<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>] view code為了實現(xiàn)繼承,python會在MRO列表上從左到右開始查找基類,直到找到第一個匹配這個屬性的類為止。
而這個MRO列表的構(gòu)造是通過一個C3線性化算法來實現(xiàn)的。我們不去深究這個算法的數(shù)學(xué)原理,它實際上就是合并所有父類的MRO列表并遵循如下三條準(zhǔn)則:
1.子類會先于父類被檢查
2.多個父類會根據(jù)它們在列表中的順序被檢查
3.如果對下一個類存在兩個合法的選擇,選擇第一個父類
4.8 子類中調(diào)用父類方法
子類繼承了父類的方法,然后想進(jìn)行修改,注意了是基于原有的基礎(chǔ)上修改,那么就需要在子類中調(diào)用父類的方法
方法一:父類名.父類方法()
#_*_coding:utf-8_*_ class Vehicle: #定義交通工具類Country='China'def __init__(self,name,speed,load,power):self.name=nameself.speed=speedself.load=loadself.power=powerdef run(self):print('開動啦...')class Subway(Vehicle): #地鐵def __init__(self,name,speed,load,power,line):Vehicle.__init__(self,name,speed,load,power)self.line=linedef run(self):print('地鐵%s號線歡迎您' %self.line)Vehicle.run(self)line13=Subway('中國地鐵','180m/s','1000人/箱','電',13) line13.run() view code方法二:super()
class Vehicle: #定義交通工具類Country='China'def __init__(self,name,speed,load,power):self.name=nameself.speed=speedself.load=loadself.power=powerdef run(self):print('開動啦...')class Subway(Vehicle): #地鐵def __init__(self,name,speed,load,power,line):#super(Subway,self) 就相當(dāng)于實例本身 在python3中super()等同于super(Subway,self)super().__init__(name,speed,load,power)self.line=linedef run(self):print('地鐵%s號線歡迎您' %self.line)super(Subway,self).run()class Mobike(Vehicle):#摩拜單車passline13=Subway('中國地鐵','180m/s','1000人/箱','電',13) line13.run() view code不用super引發(fā)的慘案
#每個類中都繼承了且重寫了父類的方法 class A:def __init__(self):print('A的構(gòu)造方法') class B(A):def __init__(self):print('B的構(gòu)造方法')A.__init__(self)class C(A):def __init__(self):print('C的構(gòu)造方法')A.__init__(self)class D(B,C):def __init__(self):print('D的構(gòu)造方法')B.__init__(self)C.__init__(self)pass f1=D()print(D.__mro__) #python2中沒有這個屬性 view code當(dāng)你使用super()函數(shù)時,Python會在MRO列表上繼續(xù)搜索下一個類。只要每個重定義的方法統(tǒng)一使用super()并只調(diào)用它一次,那么控制流最終會遍歷完整個MRO列表,每個方法也只會被調(diào)用一次
(注意注意注意:使用super調(diào)用的所有屬性,都是從MRO列表當(dāng)前的位置往后找,千萬不要通過看代碼去找繼承關(guān)系,一定要看MRO列表)
#每個類中都繼承了且重寫了父類的方法 class A:def __init__(self):print('A的構(gòu)造方法') class B(A):def __init__(self):print('B的構(gòu)造方法')super(B,self).__init__()class C(A):def __init__(self):print('C的構(gòu)造方法')super(C,self).__init__()class D(B,C):def __init__(self):print('D的構(gòu)造方法')super(D,self).__init__()f1=D()print(D.__mro__) #python2中沒有這個屬性 view code五 多態(tài)與多態(tài)性
5.1 多態(tài)
多態(tài)指的是一類事物有多種形態(tài),(一個抽象類有多個子類,因而多態(tài)的概念依賴于繼承)
1. 序列類型有多種形態(tài):字符串,列表,元組。
2. 動物有多種形態(tài):人,狗,豬
import abc class Animal(metaclass=abc.ABCMeta): #同一類事物:動物@abc.abstractmethoddef talk(self):passclass People(Animal): #動物的形態(tài)之一:人def talk(self):print('say hello')class Dog(Animal): #動物的形態(tài)之二:狗def talk(self):print('say wangwang')class Pig(Animal): #動物的形態(tài)之三:豬def talk(self):print('say aoao') view code3. 文件有多種形態(tài):文件文件,可執(zhí)行文件
import abc class File(metaclass=abc.ABCMeta): #同一類事物:文件@abc.abstractmethoddef click(self):passclass Text(File): #文件的形態(tài)之一:文本文件def click(self):print('open file')class ExeFile(File): #文件的形態(tài)之二:可執(zhí)行文件def click(self):print('execute file') view code5.2 多態(tài)性
一 什么是多態(tài)性(請務(wù)必注意注意注意:多態(tài)與多態(tài)性是兩種概念。)
多態(tài)性是指具有不同功能的函數(shù)可以使用相同的函數(shù)名,這樣就可以用一個函數(shù)名調(diào)用不同內(nèi)容的函數(shù)。
在面向?qū)ο蠓椒ㄖ幸话闶沁@樣表述多態(tài)性:向不同的對象發(fā)送同一條消息,不同的對象在接收時會產(chǎn)生不同的行為(即方法)。也就是說,每個對象可以用自己的方式去響應(yīng)共同的消息。所謂消息,就是調(diào)用函數(shù),不同的行為就是指不同的實現(xiàn),即執(zhí)行不同的函數(shù)。
多態(tài)性分為靜態(tài)多態(tài)性和動態(tài)多態(tài)性
靜態(tài)多態(tài)性:如任何類型都可以用運算符+進(jìn)行運算
動態(tài)多態(tài)性:如下
1.?
2.
>>> def func(animal): #參數(shù)animal就是對態(tài)性的體現(xiàn) ... animal.talk() ... >>> people1=People() #產(chǎn)生一個人的對象 >>> pig1=Pig() #產(chǎn)生一個豬的對象 >>> dog1=Dog() #產(chǎn)生一個狗的對象 >>> func(people1) say hello >>> func(pig1) say aoao >>> func(dog1) say wangwang view code3.?
>>> def func(f): ... f.click() ... >>> t1=Text() >>> e1=ExeFile() >>> func(t1) open file >>> func(e1) execute file view code綜上我們也可以說,多態(tài)性是‘一個接口(函數(shù)func),多種實現(xiàn)(如f.click())’
二 為什么要用多態(tài)性(多態(tài)性的好處)
其實大家從上面多態(tài)性的例子可以看出,我們并沒有增加什么新的知識,也就是說python本身就是支持多態(tài)性的,這么做的好處是什么呢?
1.增加了程序的靈活性
以不變應(yīng)萬變,不論對象千變?nèi)f化,使用者都是同一種形式去調(diào)用,如func(animal)
2.增加了程序額可擴(kuò)展性
通過繼承animal類創(chuàng)建了一個新的類,使用者無需更改自己的代碼,還是用func(animal)去調(diào)用?
>>> class Cat(Animal): #屬于動物的另外一種形態(tài):貓 ... def talk(self): ... print('say miao') ... >>> def func(animal): #對于使用者來說,自己的代碼根本無需改動 ... animal.talk() ... >>> cat1=Cat() #實例出一只貓 >>> func(cat1) #甚至連調(diào)用方式也無需改變,就能調(diào)用貓的talk功能 say miao''' 這樣我們新增了一個形態(tài)Cat,由Cat類產(chǎn)生的實例cat1,使用者可以在完全不需要修改自己代碼的情況下。使用和人、狗、豬一樣的方式調(diào)用cat1的talk方法,即func(cat1) ''' view code六 封裝
從封裝本身的意思去理解,封裝就好像是拿來一個麻袋,把小貓,小狗,小王八,還有alex一起裝進(jìn)麻袋,然后把麻袋封上口子。但其實這種理解相當(dāng)片面
??? 首先我們要了解
6.1 要封裝什么
你錢包的有多少錢(數(shù)據(jù)的封裝)
你的性取向(數(shù)據(jù)的封裝)
你撒尿的具體功能是怎么實現(xiàn)的(方法的封裝)
6.2 為什么要封裝
封裝數(shù)據(jù)的主要原因是:保護(hù)隱私(作為男人的你,臉上就寫著:我喜歡男人,你害怕么?)
封裝方法的主要原因是:隔離復(fù)雜度(快門就是傻瓜相機(jī)為傻瓜們提供的方法,該方法將內(nèi)部復(fù)雜的照相功能都隱藏起來了,比如你不必知道你自己的尿是怎么流出來的,你直接掏出自己的接口就能用尿這個功能)
你的身體沒有一處不體現(xiàn)著封裝的概念:你的身體把膀胱尿道等等這些尿的功能隱藏了起來,然后為你提供一個尿的接口就可以了(接口就是你的。。。,),你總不能把膀胱掛在身體外面,上廁所的時候就跟別人炫耀:hi,man,你瞅我的膀胱,看看我是怎么尿的。還有你的頭把你的腦子封裝到了腦殼里,然后提供了眼睛這個接口....
提示:在編程語言里,對外提供的接口,就是函數(shù),稱為接口函數(shù),這與接口的概念還不一樣,接口代表一組接口函數(shù)的集合體,后續(xù)我們將介紹到。
6.3 封裝分為兩個層面
封裝其實分為兩個層面,但很多資料中都混在一起去說,很容易讓初學(xué)者懵逼:
第一個層面的封裝(什么都不用做):創(chuàng)建類和對象會分別創(chuàng)建二者的名稱空間,我們只能用類名.或者obj.的方式去訪問里面的名字,這本身就是一種封裝
>>> r1.nickname '草叢倫' >>> Riven.camp 'Noxus'注意:對于這一層面的封裝(隱藏),類名.和實例名.就是訪問隱藏屬性的接口(或者叫入口)
第二個層面的封裝:類中把某些屬性和方法隱藏起來(或者說定義成私有的),只在類的內(nèi)部使用、外部無法訪問,或者留下少量接口(函數(shù))供外部訪問。
在python中用雙下劃線的方式實現(xiàn)隱藏屬性(設(shè)置成私有的)
類中所有雙下劃線開頭的名稱如__x都會自動變形成:_類名__x的形式:
class A:__N=0 #類的數(shù)據(jù)屬性就應(yīng)該是共享的,但是語法上是可以把類的數(shù)據(jù)屬性設(shè)置成私有的如__N,會變形為_A__Ndef __init__(self):self.__X=10 #變形為self._A__Xdef __foo(self): #變形為_A__fooprint('from A')def bar(self):self.__foo() #只有在類內(nèi)部才可以通過__foo的形式訪問到.這種自動變形的特點:
1.類中定義的__x只能在內(nèi)部使用,如self.__x,引用的就是變形的結(jié)果。
2.這種變形其實正是針對外部的變形,在外部是無法通過__x這個名字訪問到的。
2.在子類定義的__x不會覆蓋在父類定義的__x,因為子類中變形成了:_子類名__x,而父類中變形成了:_父類名__x,即雙下滑線開頭的屬性在繼承給子類時,子類是無法覆蓋的。
注意:對于這一層面的封裝(隱藏),我們需要在類中定義一個函數(shù)(接口函數(shù))在它內(nèi)部訪問被隱藏的屬性,然后外部就可以使用了
也可以用6.4所講的特性property來解決,即將介紹
? 這種變形需要注意的問題是:
1.這種機(jī)制也并沒有真正意義上限制我們從外部直接訪問屬性,知道了類名和屬性名就可以拼出名字:_類名__屬性,然后就可以訪問了,如a._A__N
>>> a=A() >>> a._A__N 0 >>> a._A__X 10 >>> A._A__N 02.變形的過程只在類的定義是發(fā)生一次,在定義后的賦值操作,不會變形
3.在繼承中,父類如果不想讓子類覆蓋自己的方法,可以將方法定義為私有的
#正常情況 >>> class A: ... def fa(self): ... print('from A') ... def test(self): ... self.fa() ... >>> class B(A): ... def fa(self): ... print('from B') ... >>> b=B() >>> b.test() from B #把fa定義成私有的,即__fa >>> class A: ... def __fa(self): #在定義時就變形為_A__fa ... print('from A') ... def test(self): ... self.__fa() #只會與自己所在的類為準(zhǔn),即調(diào)用_A__fa ... >>> class B(A): ... def __fa(self): ... print('from B') ... >>> b=B() >>> b.test() from Apython并不會真的阻止你訪問私有的屬性,模塊也遵循這種約定,如果模塊名以單下劃線開頭,那么from module import *時不能被導(dǎo)入,但是你from module import _private_module依然是可以導(dǎo)入的
其實很多時候你去調(diào)用一個模塊的功能時會遇到單下劃線開頭的(socket._socket,sys._home,sys._clear_type_cache),這些都是私有的,原則上是供內(nèi)部調(diào)用的,作為外部的你,一意孤行也是可以用的,只不過顯得稍微傻逼一點點
6.4 特性(property)
1 什么是特性property
property是一種特殊的屬性,訪問它時會執(zhí)行一段功能(函數(shù))然后返回值
import math class Circle:def __init__(self,radius): #圓的半徑radiusself.radius=radius@propertydef area(self):return math.pi * self.radius**2 #計算面積@propertydef perimeter(self):return 2*math.pi*self.radius #計算周長c=Circle(10) print(c.radius) print(c.area) #可以向訪問數(shù)據(jù)屬性一樣去訪問area,會觸發(fā)一個函數(shù)的執(zhí)行,動態(tài)計算出一個值 print(c.perimeter) #同上 ''' 輸出結(jié)果: 10 314.1592653589793 62.83185307179586 '''注意:此時的特性arear和perimeter不能被賦值
c.area=3 #為特性area賦值 ''' 拋出異常: AttributeError: can't set attribute '''2 為什么要用property
將一個類的函數(shù)定義成特性以后,對象再去使用的時候obj.name,根本無法察覺自己的name是執(zhí)行了一個函數(shù)然后計算出來的,這種特性的使用方式遵循了統(tǒng)一訪問的原則
除此之外,看下
ps:面向?qū)ο蟮姆庋b有三種方式: 【public】 這種其實就是不封裝,是對外公開的 【protected】 這種封裝方式對外不公開,但對朋友(friend)或者子類(形象的說法是“兒子”,但我不知道為什么大家 不說“女兒”,就像“parent”本來是“父母”的意思,但中文都是叫“父類”)公開 【private】 這種封裝對誰都不公開python并沒有在語法上把它們?nèi)齻€內(nèi)建到自己的class機(jī)制中,在C++里一般會將所有的所有的數(shù)據(jù)都設(shè)置為私有的,然后提供set和get方法(接口)去設(shè)置和獲取,在python中通過property方法可以實現(xiàn)
class Foo:def __init__(self,val):self.__NAME=val #將所有的數(shù)據(jù)屬性都隱藏起來@propertydef name(self):return self.__NAME #obj.name訪問的是self.__NAME(這也是真實值的存放位置)@name.setterdef name(self,value):if not isinstance(value,str): #在設(shè)定值之前進(jìn)行類型檢查raise TypeError('%s must be str' %value)self.__NAME=value #通過類型檢查后,將值value存放到真實的位置self.__NAME@name.deleterdef name(self):raise TypeError('Can not delete')f=Foo('egon') print(f.name) # f.name=10 #拋出異常'TypeError: 10 must be str' del f.name #拋出異常'TypeError: Can not delete view code class Foo:def __init__(self,val):self.__NAME=val #將所有的數(shù)據(jù)屬性都隱藏起來def getname(self):return self.__NAME #obj.name訪問的是self.__NAME(這也是真實值的存放位置)def setname(self,value):if not isinstance(value,str): #在設(shè)定值之前進(jìn)行類型檢查raise TypeError('%s must be str' %value)self.__NAME=value #通過類型檢查后,將值value存放到真實的位置self.__NAMEdef delname(self):raise TypeError('Can not delete')name=property(getname,setname,delname) #不如裝飾器的方式清晰 view code6.5 封裝與擴(kuò)展性
封裝在于明確區(qū)分內(nèi)外,使得類實現(xiàn)者可以修改封裝內(nèi)的東西而不影響外部調(diào)用者的代碼;而外部使用用者只知道一個接口(函數(shù)),只要接口(函數(shù))名、參數(shù)不變,使用者的代碼永遠(yuǎn)無需改變。這就提供一個良好的合作基礎(chǔ)——或者說,只要接口這個基礎(chǔ)約定不變,則代碼改變不足為慮。
#類的設(shè)計者 class Room:def __init__(self,name,owner,width,length,high):self.name=nameself.owner=ownerself.__width=widthself.__length=lengthself.__high=highdef tell_area(self): #對外提供的接口,隱藏了內(nèi)部的實現(xiàn)細(xì)節(jié),此時我們想求的是面積return self.__width * self.__length view code #使用者 >>> r1=Room('臥室','egon',20,20,20) >>> r1.tell_area() #使用者調(diào)用接口tell_area 400 view code #類的設(shè)計者,輕松的擴(kuò)展了功能,而類的使用者完全不需要改變自己的代碼 class Room:def __init__(self,name,owner,width,length,high):self.name=nameself.owner=ownerself.__width=widthself.__length=lengthself.__high=highdef tell_area(self): #對外提供的接口,隱藏內(nèi)部實現(xiàn),此時我們想求的是體積,內(nèi)部邏輯變了,只需求修該下列一行就可以很簡答的實現(xiàn),而且外部調(diào)用感知不到,仍然使用該方法,但是功能已經(jīng)變了return self.__width * self.__length * self.__high view code對于仍然在使用tell_area接口的人來說,根本無需改動自己的代碼,就可以用上新功能
>>> r1.tell_area() 8000七 靜態(tài)方法和類方法
通常情況下,在類中定義的所有函數(shù)(注意了,這里說的就是所有,跟self啥的沒關(guān)系,self也只是一個再普通不過的參數(shù)而已)都是對象的綁定方法,對象在調(diào)用綁定方法時會自動將自己作為參數(shù)傳遞給方法的第一個參數(shù)。除此之外還有兩種常見的方法:靜態(tài)方法和類方法,二者是為類量身定制的,但是實例非要使用,也不會報錯,后續(xù)將介紹。
1 靜態(tài)方法
是一種普通函數(shù),位于類定義的命名空間中,不會對任何實例類型進(jìn)行操作,python為我們內(nèi)置了函數(shù)staticmethod來把類中的函數(shù)定義成靜態(tài)方法
class Foo:def spam(x,y,z): #類中的一個函數(shù),千萬不要懵逼,self和x啥的沒有不同都是參數(shù)名print(x,y,z)spam=staticmethod(spam) #把spam函數(shù)做成靜態(tài)方法基于之前所學(xué)裝飾器的知識,@staticmethod 等同于spam=staticmethod(spam),于是
class Foo:@staticmethod #裝飾器def spam(x,y,z):print(x,y,z)使用演示
print(type(Foo.spam)) #類型本質(zhì)就是函數(shù) Foo.spam(1,2,3) #調(diào)用函數(shù)應(yīng)該有幾個參數(shù)就傳幾個參數(shù)f1=Foo() f1.spam(3,3,3) #實例也可以使用,但通常靜態(tài)方法都是給類用的,實例在使用時喪失了自動傳值的機(jī)制''' <class 'function'> 1 2 3 3 3 3 '''應(yīng)用場景:編寫類時需要采用很多不同的方式來創(chuàng)建實例,而我們只有一個__init__函數(shù),此時靜態(tài)方法就派上用場了
class Date:def __init__(self,year,month,day):self.year=yearself.month=monthself.day=day@staticmethoddef now(): #用Date.now()的形式去產(chǎn)生實例,該實例用的是當(dāng)前時間t=time.localtime() #獲取結(jié)構(gòu)化的時間格式return Date(t.tm_year,t.tm_mon,t.tm_mday) #新建實例并且返回@staticmethoddef tomorrow():#用Date.tomorrow()的形式去產(chǎn)生實例,該實例用的是明天的時間t=time.localtime(time.time()+86400)return Date(t.tm_year,t.tm_mon,t.tm_mday)a=Date('1987',11,27) #自己定義時間 b=Date.now() #采用當(dāng)前時間 c=Date.tomorrow() #采用明天的時間print(a.year,a.month,a.day) print(b.year,b.month,b.day) print(c.year,c.month,c.day)2 類方法
類方法是給類用的,類在使用時會將類本身當(dāng)做參數(shù)傳給類方法的第一個參數(shù),python為我們內(nèi)置了函數(shù)classmethod來把類中的函數(shù)定義成類方法
class A:x=1@classmethoddef test(cls):print(cls,cls.x)class B(A):x=2 B.test()''' 輸出結(jié)果: <class '__main__.B'> 2 '''應(yīng)用場景:
import time class Date:def __init__(self,year,month,day):self.year=yearself.month=monthself.day=day@staticmethoddef now():t=time.localtime()return Date(t.tm_year,t.tm_mon,t.tm_mday)class EuroDate(Date):def __str__(self):return 'year:%s month:%s day:%s' %(self.year,self.month,self.day)e=EuroDate.now() print(e) #我們的意圖是想觸發(fā)EuroDate.__str__,但是結(jié)果為 ''' 輸出結(jié)果: <__main__.Date object at 0x1013f9d68> '''因為e就是用Date類產(chǎn)生的,所以根本不會觸發(fā)EuroDate.__str__,解決方法就是用classmethod
import time class Date:def __init__(self,year,month,day):self.year=yearself.month=monthself.day=day# @staticmethod# def now():# t=time.localtime()# return Date(t.tm_year,t.tm_mon,t.tm_mday)@classmethod #改成類方法def now(cls):t=time.localtime()return cls(t.tm_year,t.tm_mon,t.tm_mday) #哪個類來調(diào)用,即用哪個類cls來實例化class EuroDate(Date):def __str__(self):return 'year:%s month:%s day:%s' %(self.year,self.month,self.day)e=EuroDate.now() print(e) #我們的意圖是想觸發(fā)EuroDate.__str__,此時e就是由EuroDate產(chǎn)生的,所以會如我們所愿 ''' 輸出結(jié)果: year:2017 month:3 day:3 '''強(qiáng)調(diào),注意注意注意:靜態(tài)方法和類方法雖然是給類準(zhǔn)備的,但是如果實例去用,也是可以用的,只不過實例去調(diào)用的時候容易讓人混淆,不知道你要干啥
x=e.now() #通過實例e去調(diào)用類方法也一樣可以使用,靜態(tài)方法也一樣 print(x) ''' 輸出結(jié)果: year:2017 month:3 day:3 '''八 面向?qū)ο蟮能浖_發(fā)
很多人在學(xué)完了python的class機(jī)制之后,遇到一個生產(chǎn)中的問題,還是會懵逼,這其實太正常了,因為任何程序的開發(fā)都是先設(shè)計后編程,python的class機(jī)制只不過是一種編程方式,如果你硬要拿著class去和你的問題死磕,變得更加懵逼都是分分鐘的事,在以前,軟件的開發(fā)相對簡單,從任務(wù)的分析到編寫程序,再到程序的調(diào)試,可以由一個人或一個小組去完成。但是隨著軟件規(guī)模的迅速增大,軟件任意面臨的問題十分復(fù)雜,需要考慮的因素太多,在一個軟件中所產(chǎn)生的錯誤和隱藏的錯誤、未知的錯誤可能達(dá)到驚人的程度,這也不是在設(shè)計階段就完全解決的。
??? 所以軟件的開發(fā)其實一整套規(guī)范,我們所學(xué)的只是其中的一小部分,一個完整的開發(fā)過程,需要明確每個階段的任務(wù),在保證一個階段正確的前提下再進(jìn)行下一個階段的工作,稱之為軟件工程
??? 面向?qū)ο蟮能浖こ贪ㄏ旅鎺讉€部:
1.面向?qū)ο蠓治?#xff08;object oriented analysis ,OOA)
??? 軟件工程中的系統(tǒng)分析階段,要求分析員和用戶結(jié)合在一起,對用戶的需求做出精確的分析和明確的表述,從大的方面解析軟件系統(tǒng)應(yīng)該做什么,而不是怎么去做。面向?qū)ο蟮姆治鲆凑彰嫦驅(qū)ο蟮母拍詈头椒?#xff0c;在對任務(wù)的分析中,從客觀存在的事物和事物之間的關(guān)系,貴南出有關(guān)的對象(對象的‘特征’和‘技能’)以及對象之間的聯(lián)系,并將具有相同屬性和行為的對象用一個類class來標(biāo)識。
??? 建立一個能反映這是工作情況的需求模型,此時的模型是粗略的。
2 面向?qū)ο笤O(shè)計(object oriented design,OOD)
??? 根據(jù)面向?qū)ο蠓治鲭A段形成的需求模型,對每一部分分別進(jìn)行具體的設(shè)計。
??? 首先是類的設(shè)計,類的設(shè)計可能包含多個層次(利用繼承與派生機(jī)制)。然后以這些類為基礎(chǔ)提出程序設(shè)計的思路和方法,包括對算法的設(shè)計。
??? 在設(shè)計階段并不牽涉任何一門具體的計算機(jī)語言,而是用一種更通用的描述工具(如偽代碼或流程圖)來描述
3 面向?qū)ο缶幊?#xff08;object oriented programming,OOP)
??? 根據(jù)面向?qū)ο笤O(shè)計的結(jié)果,選擇一種計算機(jī)語言把它寫成程序,可以是python
4 面向?qū)ο鬁y試(object oriented test,OOT)
??? 在寫好程序后交給用戶使用前,必須對程序進(jìn)行嚴(yán)格的測試,測試的目的是發(fā)現(xiàn)程序中的錯誤并修正它。
??? 面向?qū)Φ臏y試是用面向?qū)ο蟮姆椒ㄟM(jìn)行測試,以類作為測試的基本單元。
5 面向?qū)ο缶S護(hù)(object oriendted soft maintenance,OOSM)
??? 正如對任何產(chǎn)品都需要進(jìn)行售后服務(wù)和維護(hù)一樣,軟件在使用時也會出現(xiàn)一些問題,或者軟件商想改進(jìn)軟件的性能,這就需要修改程序。
??? 由于使用了面向?qū)ο蟮姆椒ㄩ_發(fā)程序,使用程序的維護(hù)比較容易。
??? 因為對象的封裝性,修改一個對象對其他的對象影響很小,利用面向?qū)ο蟮姆椒ňS護(hù)程序,大大提高了軟件維護(hù)的效率,可擴(kuò)展性高。
??? 在面向?qū)ο蠓椒ㄖ?#xff0c;最早發(fā)展的肯定是面向?qū)ο缶幊?OOP),那時OOA和OOD都還沒有發(fā)展起來,因此程序設(shè)計者為了寫出面向?qū)ο蟮某绦?#xff0c;還必須深入到分析和設(shè)計領(lǐng)域,尤其是設(shè)計領(lǐng)域,那時的OOP實際上包含了現(xiàn)在的OOD和OOP兩個階段,這對程序設(shè)計者要求比較高,許多人感到很難掌握。
??? 現(xiàn)在設(shè)計一個大的軟件,是嚴(yán)格按照面向?qū)ο筌浖こ痰?個階段進(jìn)行的,這個5個階段的工作不是由一個人從頭到尾完成的,而是由不同的人分別完成,這樣OOP階段的任務(wù)就比較簡單了。程序編寫者只需要根據(jù)OOd提出的思路,用面向?qū)ο笳Z言編寫出程序既可。
??? 在一個大型軟件開發(fā)過程中,OOP只是很小的一個部分。
??? 對于全棧開發(fā)的你來說,這五個階段都有了,對于簡單的問題,不必嚴(yán)格按照這個5個階段進(jìn)行,往往由程序設(shè)計者按照面向?qū)ο蟮姆椒ㄟM(jìn)行程序設(shè)計,包括類的設(shè)計和程序的設(shè)計
九 小白容易犯的錯誤
1.面向?qū)ο蟮某绦蛟O(shè)計看起來高大上,所以我在編程時就應(yīng)該保證通篇class,這樣寫出的程序一定是好的程序(面向?qū)ο笾贿m合那些可擴(kuò)展性要求比較高的場景)
2.很多人喜歡說面向?qū)ο笕筇匦?#xff08;這是從哪傳出來的,封裝,多態(tài),繼承?漏洞太多太多,好吧暫且稱為三大特性),那么我在基于面向?qū)ο缶幊虝r,我一定要讓我定義的類中完整的包含這三種特性,這樣寫肯定是好的程序
好家伙,我說降龍十八掌有十八掌,那么你每次跟人干仗都要從第一掌打到第18掌這才顯得你會了是么,我來一萬個人你需要打10000*18掌對么,傻叉
3.類有類屬性,實例有實例屬性,所以我們在定義class時一定要定義出那么幾個類屬性,想不到怎么辦,那就使勁的想,定義的越多越牛逼
這就犯了一個嚴(yán)重的錯誤,程序越早面向?qū)ο?#xff0c;死的越早,為啥面向?qū)ο?#xff0c;因為我們要將數(shù)據(jù)與功能結(jié)合到一起,程序整體的結(jié)構(gòu)都沒有出來,或者說需要考慮的問題你都沒有搞清楚個八九不離十,你就開始面向?qū)ο罅?#xff0c;這就導(dǎo)致了,你在那里干想,自以為想通了,定義了一堆屬性,結(jié)果后來又都用不到,或者想不通到底應(yīng)該定義啥,那就一直想吧,想著想著就瘋了。
你見過哪家公司要開發(fā)一個軟件,上來就開始寫,肯定是頻繁的開會討論計劃,請看第八節(jié)
4.既然這么麻煩,那么我徹底解脫了,我們不要用面向?qū)ο缶幊塘?#xff0c;你啊,你有大才,你能成事啊,傻叉。
十 python中關(guān)于OOP的常用術(shù)語
抽象/實現(xiàn)
抽象指對現(xiàn)實世界問題和實體的本質(zhì)表現(xiàn),行為和特征建模,建立一個相關(guān)的子集,可以用于 繪程序結(jié)構(gòu),從而實現(xiàn)這種模型。抽象不僅包括這種模型的數(shù)據(jù)屬性,還定義了這些數(shù)據(jù)的接口。
對某種抽象的實現(xiàn)就是對此數(shù)據(jù)及與之相關(guān)接口的現(xiàn)實化(realization)。現(xiàn)實化這個過程對于客戶 程序應(yīng)當(dāng)是透明而且無關(guān)的。?
封裝/接口
封裝描述了對數(shù)據(jù)/信息進(jìn)行隱藏的觀念,它對數(shù)據(jù)屬性提供接口和訪問函數(shù)。通過任何客戶端直接對數(shù)據(jù)的訪問,無視接口,與封裝性都是背道而馳的,除非程序員允許這些操作。作為實現(xiàn)的 一部分,客戶端根本就不需要知道在封裝之后,數(shù)據(jù)屬性是如何組織的。在Python中,所有的類屬性都是公開的,但名字可能被“混淆”了,以阻止未經(jīng)授權(quán)的訪問,但僅此而已,再沒有其他預(yù)防措施了。這就需要在設(shè)計時,對數(shù)據(jù)提供相應(yīng)的接口,以免客戶程序通過不規(guī)范的操作來存取封裝的數(shù)據(jù)屬性。
注意:封裝絕不是等于“把不想讓別人看到、以后可能修改的東西用private隱藏起來”
真正的封裝是,經(jīng)過深入的思考,做出良好的抽象,給出“完整且最小”的接口,并使得內(nèi)部細(xì)節(jié)可以對外透明
(注意:對外透明的意思是,外部調(diào)用者可以順利的得到自己想要的任何功能,完全意識不到內(nèi)部細(xì)節(jié)的存在)
合成
合成擴(kuò)充了對類的 述,使得多個不同的類合成為一個大的類,來解決現(xiàn)實問題。合成 述了 一個異常復(fù)雜的系統(tǒng),比如一個類由其它類組成,更小的組件也可能是其它的類,數(shù)據(jù)屬性及行為, 所有這些合在一起,彼此是“有一個”的關(guān)系。
派生/繼承/繼承結(jié)構(gòu)
派生描述了子類衍生出新的特性,新類保留已存類類型中所有需要的數(shù)據(jù)和行為,但允許修改或者其它的自定義操作,都不會修改原類的定義。
繼承描述了子類屬性從祖先類繼承這樣一種方式
繼承結(jié)構(gòu)表示多“代”派生,可以述成一個“族譜”,連續(xù)的子類,與祖先類都有關(guān)系。
泛化/特化
基于繼承
泛化表示所有子類與其父類及祖先類有一樣的特點。
特化描述所有子類的自定義,也就是,什么屬性讓它與其祖先類不同。
多態(tài)與多態(tài)性
多態(tài)指的是同一種事物的多種狀態(tài):水這種事物有多種不同的狀態(tài):冰,水蒸氣
多態(tài)性的概念指出了對象如何通過他們共同的屬性和動作來操作及訪問,而不需考慮他們具體的類。
冰,水蒸氣,都繼承于水,它們都有一個同名的方法就是變成云,但是冰.變云(),與水蒸氣.變云()是截然不同的過程,雖然調(diào)用的方法都一樣
自省/反射
自省也稱作反射,這個性質(zhì)展示了某對象是如何在運行期取得自身信息的。如果傳一個對象給你,你可以查出它有什么能力,這是一項強(qiáng)大的特性。如果Python不支持某種形式的自省功能,dir和type內(nèi)建函數(shù),將很難正常工作。還有那些特殊屬性,像__dict__,__name__及__doc__
轉(zhuǎn)載于:https://www.cnblogs.com/can-H/articles/6506475.html
總結(jié)
以上是生活随笔為你收集整理的08-面向对象----的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: SQL注入漏洞解决方法
- 下一篇: IP地址控件