一文理清面向对象(封装、继承、多态)+ 实战案例
python是一門面向?qū)ο缶幊陶Z言,對面向?qū)ο笳Z言編碼的過程叫做面向?qū)ο缶幊獭?/p>
面向?qū)ο笫且环N思想,與之相對的是面向過程。我們先簡單說一下面向過程。
面向過程其實就是把過程當(dāng)做設(shè)計核心,根據(jù)問題的發(fā)展順序,依次解決問題,盡可能的把過程中涉及到的問題完善解決。他有他的優(yōu)點,當(dāng)拿到一個問題時,可以方便的按執(zhí)行的步驟寫代碼,但是當(dāng)邏輯關(guān)系變得復(fù)雜時,有一個地方出現(xiàn)差錯就會導(dǎo)致整個程序無從下手。
面向?qū)ο蟮木幊陶Z言還是很多的,例如C++、Java等。面向?qū)ο蟪绦蛟O(shè)計把計算機程序的執(zhí)行看做一組對象的集合,每個對象之間進行消息的傳送處理。有一個顯著的優(yōu)點就是,對某個對象進行修改,整個程序不會受到影響,自定義數(shù)據(jù)類型就是面向?qū)ο笾械念惖母拍?#xff0c;而我們需要把他們的接口處理好就很好辦了。說了這么多話,有些小白已經(jīng)看不下去了,那接下來我們進入主題。
上面說了,自定義數(shù)據(jù)類型就是面向?qū)ο笾械念惖母拍睢N覀兿冉榻B一下待會兒會用到的一些術(shù)語(我認(rèn)為還是通過個例子更容易讓人理解):
# 首先我們定義一個類 class?A(object): # 這是一個類,class是創(chuàng)建一個類的標(biāo)志# 類變量(類屬性):類屬性是指類的屬性,屬性就是我們剛學(xué)編程的時候聽過的變量。x = 7y = "asdf"def?__init__(self,name,age):self.name = nameself.age = age# 方法:方法就是在類外面我們寫的函數(shù),放在類里就叫做一個方法def?func(self):c = 8????# 實例變量:定義在方法中的變量只作用于當(dāng)前實例的類print("Hello World!")a = A() # 創(chuàng)建一個對象,實例化上面的代碼還需要再解釋一下:
object:注意類名后面括號里有個參數(shù)object,他代表所有類的基類,也叫作超類。
這就有了一個新式類和舊式類的概念:當(dāng)用到多繼承的時候,如果子類中沒有想用的方法名或?qū)傩悦?#xff0c;他會自動回到上面去找。那么按廣度優(yōu)先遍歷的方法去尋找就是新式類(object);深度優(yōu)先(括號里啥也沒有)。
__init__():構(gòu)造函數(shù),實例化的時候若不顯示的定義,那么默認(rèn)調(diào)用一個無參的,是初始化的意思。
封裝
含義:對外面隱藏對象的屬性和方法,僅提供接口。
作用:安全性(通過私有變量改變對外的使用),復(fù)用性。
# 以下程序關(guān)于學(xué)生成績,通過封裝函數(shù),實現(xiàn)修改、顯示分?jǐn)?shù)的功能 class?Student(object):def?__init__(self, name, score):# 屬性僅前面有兩個下劃線代表私有變量,外部無法訪問,因此我們定義了兩個新的方法,這樣可以避免外部通過score亂改分?jǐn)?shù),僅當(dāng)我們自己知道接口才可以修改self.__name = nameself.__score = scoredef?info(self):print('name: %s ; score: %d'?% (self.__name,self.__score))def?getScore(self):return?self.__scoredef?setScore(self, score):self.__score = scorestu = Student('Tom',99) # 實例化 print('修改前分?jǐn)?shù):',stu.getScore()) stu.info() stu.setScore(59) # 重置分?jǐn)?shù) print('修改后分?jǐn)?shù):',stu.getScore()) stu.info()繼承
含義
前面我們提到過,面向?qū)ο缶幊逃袀€好處就是代碼復(fù)用,而其中一種方法就是通過繼承機制。繼承就是說定義的一個新類,繼承現(xiàn)有的類,獲得現(xiàn)有類的非私有屬性、方法。提到個私有,就是上面提到的那個前面加兩個下劃線的那個東西,他在外部無法調(diào)用,繼承他的子類也不能。被繼承的那個類稱為基類、父類或超類,子類也可以叫做派生類。
特點
1、在繼承中,基類的構(gòu)造方法(__init__()方法)不會被自動調(diào)用,需要在子類的構(gòu)造方法中專門調(diào)用。
2、在調(diào)用基類的方法時需要加上基類的類名前綴,并帶上self參數(shù)變量。區(qū)別于在類中調(diào)用普通函數(shù)時不需要帶self參數(shù)。
3、在python中,首先查找對應(yīng)類型的方法,如果在子類中找不到對應(yīng)的方法,才到基類中逐個查找。
單繼承
直接上代碼,仔細理解一下里面的關(guān)系,我把講解都寫在注釋的地方。
(注:不同的軟件導(dǎo)入自定義庫的方式不太一樣,如果使用我的程序無法執(zhí)行,可能是由于你的環(huán)境中不需要from 單繼承的實現(xiàn).person import Person,而是直接from person import Person,后續(xù)代碼同理)
# 這是定義了一個基類 class?Person(object):def?__init__(self, name, age, money):self.name = nameself.age = ageself.__money = money # 私有屬性# 被引入時,繼承不了,但他們的set,get函數(shù)可以繼承def?setMoney(self,money):self.__money = moneydef?getMoney(self):return?self.__moneydef?run(self):print("run")def?eat(self):print("eat")下面是定義的一個子類,繼承自上方的類,來使用父類中的方法和屬性。
# 由于我將每個類寫在了不同的文件里,所以需要引入一下,這就和我們調(diào)用庫一樣 from 單繼承的實現(xiàn).person import Personclass?Student(Person):def?__init__(self,name,age,stuid,money):# 調(diào)用父類中的__init__(),supper括號中的內(nèi)容,在python3以后可以不寫,寫上更安全些super(Student,self).__init__(name,age,money) # 讓父類的self當(dāng)做子類的對象# 子類可以由一些自己獨有的屬性或者方法self.stuid = stuid創(chuàng)建對象,通過子類使用父類的屬性和方法。
from?單繼承的實現(xiàn).student import?Studentstu = Student('Tom',18,111,999) # 創(chuàng)建Student對象 # 下列方法和屬性均是在父類Person中定義的,在Student繼承之后,便可以直接使用 print(stu.name, stu.age) stu.run() print(stu.getMoney())顯示結(jié)果如下:
多繼承
上面的單繼承要多理解一下,單繼承理解了之后,多繼承也只是同時繼承了不止一個父類而已。下面直接給一個例子瞧瞧。
class?Father(object):def?__init__(self,money):self.money = moneydef?play(self):print("play")def?func(self):print("func1")class?Mother(object):def?__init__(self,facevalue):self.facevalue = facevaluedef?eat(self):print("eat")def?func(self):print("func2")class?Children(Father,Mother):def?__init__(self,money,facevalue):# 多繼承時調(diào)用父類的屬性Father.__init__(self,money)Mother.__init__(self,facevalue)def?main():c = Children(300,100)print(c.money,c.facevalue)c.play()c.eat()# 注意:如果多個父類中有相同的方法名,默認(rèn)調(diào)用括號中前面的類c.func()if?__name__?== "__main__":main()多態(tài)
多態(tài):是指一種事物的多種形態(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ù)。
eg:在python中的“+”號,它既可以表示數(shù)字的加法,也可以表示字符串的拼接。
class?Animal(object):def?__init__(self, name):self.name = namedef?run(self):passdef?animalRun(self):self.run()class?Cat(Animal):def?run(self):print('cat is running')class?Dog(Animal):def?run(self):print('dog is running')d = Dog('dog') c = Cat('cat')Animal.animalRun(c) Animal.animalRun(d)看過上面多繼承和多態(tài)的例子你有沒有什么感覺,繼承是一個繼承多個,而多態(tài)是多個繼承一個。
小栗子
下面,小栗子來了,內(nèi)容不要緊,關(guān)鍵是要理解面向?qū)ο蟮乃枷?#xff08;python中,萬物皆對象)。這個例子來源于小時候經(jīng)常聯(lián)機玩的cs,當(dāng)時的cs還沒有現(xiàn)在這么豐富。扯遠了,繼續(xù)談對象。
首先,確定里面有哪些對象,當(dāng)然只是示意,不會搞很復(fù)雜的內(nèi)容。主要有好人、壞人、槍(槍的彈夾也可以寫一個類)、手榴彈,也就這些東西吧。接下來就要分別寫每個對象的內(nèi)容了。
#?壞蛋/好人 class?Gengster(Person):# 初始化,血量默認(rèn)為100def?__init__(self, gun, grenade, blood=100):self.gun = gunself.grenade = grenadeself.blood = blood# 人有開槍的功能def?fire(self,person):person.blood.amount -= 5?# 對誰開槍,那個人就要減血self.gun.shoot() # 這個人開槍,這又調(diào)用了槍的類,關(guān)于子彈的減少在槍的類里# 扔手榴彈,實際上是和槍一樣的def?fire2(self,person):person.blood -= 10self.grenade.damage() # 同樣通過另一個類來控制數(shù)量的減少,使代碼看起來簡潔點# 給彈夾里加子彈def?fillbullet(self):self.gun.bulletbox.bulletcount += 10# 補血,并保證滿血只能是100def?fillblood(self,num):self.blood += numif?self.blood > 100:self.blood = 100print("補血后血量:"?+ str(self.blood))# 槍class?Gun(object):# 初始化,把彈夾放里面,通過人來控制槍,槍再來控制彈夾def?__init__(self,bulletbox):self.bulletbox = bulletboxdef?shoot(self):if?self.bulletbox.bulletcount == 0:print('沒子彈了')else:self.bulletbox.bulletcount -= 1print(str(self) + '開一槍,還剩%d顆子彈'?% (self.bulletbox.bulletcount))# 彈夾class?Bulletbox(object):# 彈夾只需控制數(shù)量就好了def?__init__(self,bulletcount):self.bulletcount = bulletcount# 手榴彈,與槍類似class?Grenade(object):def?__init__(self,grenadecount):self.grenadecount = grenadecountdef?damage(self):if?self.grenadecount == 0:print('手雷沒有了')else:self.grenadecount -= 1print(str(self) + "轟他一炮,手雷還剩%d顆"?% (self.grenadecount))那么,現(xiàn)在人和武器都有了,就可以開始戰(zhàn)斗了。
現(xiàn)在這一套流程就結(jié)束了,剛開始看也許看不太懂,要仔細看一下每個類之間的關(guān)系,先想清楚了,再來看代碼是如何實現(xiàn)的。
有沒有看出來點區(qū)別,面向過程編程是就事論事,而面向?qū)ο?#xff0c;先把對象找出來,通過對象之間的關(guān)系把他們聯(lián)系起來。想想如果要用面向過程來實現(xiàn)這個,代碼會寫成什么樣子呢。
然而并沒有結(jié)束,前面好人和壞人的程序基本上就差不多的,如果考慮含有不同的話,這時候就用到了上面講到的繼承,繼承的一個特點就是復(fù)用。我們可以用繼承來寫一下,如果你說這個也沒少幾行代碼嘛,如果在實際當(dāng)中你要創(chuàng)建成百上千的對象呢,難道還要每個都復(fù)制粘貼改代碼嗎,還占空間對不對。
首先,好人壞人都是人對吧,那么就可以先創(chuàng)建一個人的類,然后分別寫好人壞人,繼承一下人的類就好了。
class?Person(object):def?__init__(self, gun, grenade, blood):self.gun = gunself.grenade = grenadeself.blood = blooddef?fire(self, person):person.blood -= 5self.gun.shoot()print(str(person) + "血量減少5,剩余"?+ str(person.blood) )def?fire2(self, person):person.blood -= 10self.grenade.damage()print(str(person) + "血量減少10,剩余"?+ str(person.blood) )def?fillbullet(self):self.gun.bulletbox.bulletcount += 10def?fillblood(self,num):self.blood += numif?self.blood > 100:self.blood = 100print(str(self) + "補血后血量:"?+ str(self.blood))from cs.person import Personclass?Profector(Person):def?__init__(self, gun, grenade, blood = 100):super(Profector,self).__init__(gun, grenade, blood)class?Gengster(Person):def?__init__(self, gun, grenade, blood=100):super(Gengster, self).__init__(gun, grenade, blood)看到這里,也許有人已經(jīng)懵了,不要著急,慢慢理解其中的關(guān)系。其實仔細看結(jié)果發(fā)現(xiàn)還有問題,血量減少的人的對象還是正常的,然而看一下開槍的人。有沒有發(fā)現(xiàn)好人1和好人2的對象時同一個地址呢,他們的子彈也是累積的遞減;壞人使用手榴彈也是。為什么開槍的人會是這樣,而受傷的人卻是正常的呢?提醒一下,我們前面創(chuàng)建的那些對象,有些是為了下一個對象調(diào)用而準(zhǔn)備的,看看是在哪里出錯的。
END
來和小伙伴們一起向上生長呀~~~
掃描下方二維碼,添加小詹微信,可領(lǐng)取千元大禮包并申請加入 Python學(xué)習(xí)交流群,群內(nèi)僅供學(xué)術(shù)交流,日常互動,如果是想發(fā)推文、廣告、砍價小程序的敬請繞道!一定記得備注「交流學(xué)習(xí)」,我會盡快通過好友申請哦!
(添加人數(shù)較多,請耐心等待)
總結(jié)
以上是生活随笔為你收集整理的一文理清面向对象(封装、继承、多态)+ 实战案例的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 为什么我们程序员不把软件开发当回事?
- 下一篇: 微信重大更新!这特么是为上班摸鱼开发的吧