面向過程 VS 面向?qū)ο?/p>
編程范式
編程是 程序 員 用特定的語法+數(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í)行,一步步從上到下,從頭到尾的解決問題 ?;驹O(shè)計(jì)思路就是程序一開始是要著手解決一個(gè)大的問題,然后把一個(gè)大問題分解成很多個(gè)小問題或子過程,這些子過程再執(zhí)行的過程再繼續(xù)分解直到小問題足夠簡單到可以在一個(gè)小步驟范圍內(nèi)解決。
舉個(gè)典型的面向過程的例子, 有個(gè)需求是對網(wǎng)站日志進(jìn)行分析,生成郵件報(bào)告,整個(gè)流程分以下幾步:
1. 到各臺服務(wù)器上收集日志,因?yàn)橛卸嗯_網(wǎng)站服務(wù)器,共同對外提供服務(wù)
2. 對日志進(jìn)行各種維度分析,比如pv,uv, 來源地區(qū)、訪問的設(shè)備等
3. 生成報(bào)告,發(fā)送郵件
代碼如下
# 1 整合日志
def collect_logs():
? ? print("log on server A ,get access.log")
? ? print("log on server B ,get access.log")
? ? print("log on server C ,get access.log")
? ? print("combine logs in to one file")
# 2 日志分析
def log_analyze(log_file):
? ? print("pv、uv分析....")
? ? print("用戶來源分析....")
? ? print("訪問的設(shè)備來源分析....")
? ? print("頁面停留時(shí)間分析....")
? ? print("入口頁面分析....")
# 3 生成報(bào)告并發(fā)送
def send_report(report_data):
? ? print("connect email server...")
? ? print("send email....")
def main():
? ? collect_logs()
? ? log_analyze('my_db')
? ? send_report()
if __name__ == '__main__':
? ? main()
這樣做的問題也是顯而易見的,就是如果你要對程序進(jìn)行修改,對你修改的那部分有依賴的各個(gè)部分你都也要跟著修改, 舉個(gè)例子,如果程序開頭你設(shè)置了一個(gè)變量值 為1 , 但如果其它子過程依賴這個(gè)值 為1的變量才能正常運(yùn)行,那如果你改了這個(gè)變量,那這個(gè)子過程你也要修改,假如又有一個(gè)其它子程序依賴這個(gè)子過程 , 那就會(huì)發(fā)生一連串的影響,隨著程序越來越大, 這種編程方式的維護(hù)難度會(huì)越來越高。?
所以我們一般認(rèn)為, 如果你只是寫一些簡單的腳本,去做一些一次性任務(wù),用面向過程的方式是極好的,但如果你要處理的任務(wù)是復(fù)雜的,且需要不斷迭代和維護(hù) 的, 那還是用面向?qū)ο笞罘奖懔恕?/p>
面向?qū)ο缶幊?Object-Oriented Programming)
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è)父類里定義的屬性、方法自動(dòng)被子類繼承
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ā)出相同的消息將會(huì)有不同的行為。比如,你的老板讓所有員工在九點(diǎn)鐘開始工作, 他只要在九點(diǎn)鐘的時(shí)候說:“開始工作”即可,而不需要對銷售人員說:“開始銷售工作”,對技術(shù)人員說:“開始技術(shù)工作”, 因?yàn)椤皢T工”是一個(gè)抽象的事物, 只要是員工就可以開始工作,他知道這一點(diǎn)就行了。至于每個(gè)員工,當(dāng)然會(huì)各司其職,做各自的工作。
多態(tài)允許將子類的對象當(dāng)作父類的對象使用,某父類型的引用指向其子類型的對象,調(diào)用的方法是該子類型的方法。這里引用和調(diào)用方法的代碼編譯前就已經(jīng)決定了,而引用所指向的對象可以在運(yùn)行期間動(dòng)態(tài)綁定
面向?qū)ο髒s面向過程總結(jié)
面向過程的程序設(shè)計(jì)的核心是過程(流水線式思維),過程即解決問題的步驟,面向過程的設(shè)計(jì)就好比精心設(shè)計(jì)好一條流水線,考慮周全什么時(shí)候處理什么東西。
優(yōu)點(diǎn)是:極大的降低了寫程序的復(fù)雜度,只需要順著要執(zhí)行的步驟,堆疊代碼即可。
缺點(diǎn)是:一套流水線或者流程就是用來解決一個(gè)問題,代碼牽一發(fā)而動(dòng)全身。
面向?qū)ο蟮某绦蛟O(shè)計(jì)的核心是對象(上帝式思維),要理解對象為何物,必須把自己當(dāng)成上帝,上帝眼里世間存在的萬物皆為對象,不存在的也可以創(chuàng)造出來。面向?qū)ο蟮某绦蛟O(shè)計(jì)好比如來設(shè)計(jì)西游記,如來要解決的問題是把經(jīng)書傳給東土大唐,如來想了想解決這個(gè)問題需要四個(gè)人:唐僧,沙和尚,豬八戒,孫悟空,每個(gè)人都有各自的特征和技能(這就是對象的概念,特征和技能分別對應(yīng)對象的屬性和方法),然而這并不好玩,于是如來又安排了一群妖魔鬼怪,為了防止師徒四人在取經(jīng)路上被搞死,又安排了一群神仙保駕護(hù)航,這些都是對象。然后取經(jīng)開始,師徒四人與妖魔鬼怪神仙互相纏斗著直到最后取得真經(jīng)。如來根本不會(huì)管師徒四人按照什么流程去取。
面向?qū)ο蟮某绦蛟O(shè)計(jì)的
優(yōu)點(diǎn)是:解決了程序的擴(kuò)展性。對某一個(gè)對象單獨(dú)修改,會(huì)立刻反映到整個(gè)體系中,如對游戲中一個(gè)人物參數(shù)的特征和技能修改都很容易。
缺點(diǎn):可控性差,無法向面向過程的程序設(shè)計(jì)流水線式的可以很精準(zhǔn)的預(yù)測問題的處理流程與結(jié)果,面向?qū)ο蟮某绦蛞坏╅_始就由對象之間的交互解決問題,即便是上帝也無法預(yù)測最終結(jié)果。于是我們經(jīng)??吹揭粋€(gè)游戲人某一參數(shù)的修改極有可能導(dǎo)致陰霸的技能出現(xiàn),一刀砍死3個(gè)人,這個(gè)游戲就失去平衡。
應(yīng)用場景:需求經(jīng)常變化的軟件,一般需求的變化都集中在用戶層,互聯(lián)網(wǎng)應(yīng)用,企業(yè)內(nèi)部軟件,游戲等都是面向?qū)ο蟮某绦蛟O(shè)計(jì)大顯身手的好地方。
類的定義
class Dog:? # 類名首字母要大寫,駝峰體
? ? d_type = "京巴"? # 公共屬性,又稱類變量
? ? def say_hi(self):? # 類的方法,必須帶一個(gè)self參數(shù),代表實(shí)例本身,具體先不解釋
? ? ? ? print("hello , I am a dog,my type is ",self.d_type) # 想調(diào)用類里的屬性,都要加上self., 原因先不表
d = Dog()? # 生成一個(gè)狗的實(shí)例
d2 = Dog()? # 生成一個(gè)狗的實(shí)例
d.say_hi()? # 調(diào)用狗這個(gè)類的方法
d2.say_hi()
print(d.d_type)? # 調(diào)用Dog類的公共屬性
以上代碼就是定義好了Dog這個(gè)類,相當(dāng)于先生成了一個(gè)模板,接下來生成了2個(gè)實(shí)例d, d2,相當(dāng)于2條有血有肉的狗被創(chuàng)造出來了。
d_type是類變量,是Dog類下所有實(shí)例共有的屬性,它存在Dog類本身的內(nèi)存里。你可以查看d1.d_type,d2.d_type的內(nèi)存地址,指向的是同一處
除了共有屬性,有沒有私有的呢? 比如每條狗的名字、年齡、主人都不一樣??梢缘?,如下操作就行:
class Dog:? # 類名首字母要大寫,駝峰體
? ? d_type = "京巴"? # 公共屬性,又稱類變量
? ? def __init__(self,name,age,master): # 初始化函數(shù),只要一實(shí)例化,就會(huì)自動(dòng)執(zhí)行
? ? ? ? print('初始化這個(gè)實(shí)例....',name)
? ? ? ? self.name = name? # self.name 就是實(shí)例自己的變量
? ? ? ? self.age = age
? ? ? ? self.master = master
? ? def say_hi(self):? # 類的方法,必須帶一個(gè)self參數(shù),代表實(shí)例本身,具體先不解釋
? ? ? ? print("hello , I am a dog,my type is ",self.d_type) # 想調(diào)用類里的屬性,都要加上self., 原因先不表
d = Dog("毛毛",2,"Alex")? # 生成一個(gè)狗的實(shí)例
d2 = Dog("二蛋",3,"Jack")? # 生成一個(gè)狗的實(shí)例
d.say_hi()? # 調(diào)用狗這個(gè)類的方法
d2.say_hi()
print(d2.name, d2.age, d2.master) # 調(diào)用實(shí)例的變量
執(zhí)行輸出
初始化這個(gè)實(shí)例.... 毛毛
初始化這個(gè)實(shí)例.... 二蛋
hello , I am a dog,my type is? 京巴
hello , I am a dog,my type is? 京巴
二蛋 3 Jack
我們并沒有調(diào)用init(self,….),但它會(huì)自動(dòng)執(zhí)行,因?yàn)樗谐跏蓟瘮?shù),就是在實(shí)例化的時(shí)候,用來初始化一些數(shù)據(jù)的,比如初始化你實(shí)例的名字、年齡等屬性啦。
這些寫在init(self,xxxx)里 name,age,master變量,跟d_type有什么區(qū)別呢?
區(qū)別就是, d_type是存在Dog類自己的內(nèi)存里, self.name,self.age,self.master是存在每個(gè)實(shí)例自己的內(nèi)存里

self到底是個(gè)什么鬼?
想明白self什么意思,我們先搞明白 ,實(shí)例化的過程,看下圖;
1. step 1, d = Dog(“毛毛”,2,”Alex”) 會(huì)申請一會(huì)內(nèi)存空間,指向變量名d
2. step 2,?init(xxxx)這個(gè)初始化方法需要把接收到參數(shù)存下來, 存到這個(gè)d的內(nèi)存空間里
3. step 3, 傳給初始化方法里的name,age,master想綁定到d的空間里,怎么存呢? 就得把d的內(nèi)存空間傳到這個(gè)方法里, 所以self就是用來接收d的地址的。d = Dog(“毛毛”,2,”Alex”) 相當(dāng)于Dog(d,”毛毛”,2,”Alex”), 那self.name = name 也就相當(dāng)于d.name = name 。我們在實(shí)例時(shí)沒有手動(dòng)傳遞d到Dog類里, 只寫了d = Dog(“毛毛”,2,”Alex”), 是Python解釋器幫你自動(dòng)干了這個(gè)事。

到此,我們終于明白 ,原來self就是代表實(shí)例本身。你實(shí)例化時(shí)python會(huì)自動(dòng)把這個(gè)實(shí)例本身通過self參數(shù)傳進(jìn)去。
你說好吧,假裝懂了, 但下面這段代碼你又不明白了, 為何say_hi(self),要寫個(gè)self呢?
def say_hi(self):? # 類的方法,必須帶一個(gè)self參數(shù),代表實(shí)例本身
? ? print("hello , I am a dog,my type is ",self.d_type,self.name) # 想調(diào)用類里的屬性,都要加上self.
那是因?yàn)?,你自己也看到了?這個(gè)類的方法其實(shí)就是一堆函數(shù)對吧。函數(shù)被一個(gè)實(shí)例調(diào)用時(shí),它怎么知道是誰在調(diào)用它呢? 函數(shù)內(nèi)部要用到一些實(shí)例的屬性的時(shí)候去哪里取呢? 比如在say_hi函數(shù)里怎么取到d.name,d.age?只能你先傳遞給它。 所以這就是為何類下的每個(gè)方法第一個(gè)參數(shù)都要是self,因?yàn)槭菫榱私邮諏?shí)例這個(gè)對象本身。
*注意:self在實(shí)例化時(shí)自動(dòng)將對象/實(shí)例本身傳給init的第一個(gè)參數(shù),你也可以給他起個(gè)別的名字,但是正常人都不會(huì)這么做,因?yàn)槟阆垢膭e人就不認(rèn)識。
屬性引用
類的公共屬性引用(類名.屬性)
class Dog:? # 類名首字母要大寫,駝峰體
? ? d_type = "京巴"? # 公共屬性,又稱類變量
? ? def say_hi(self):?
? ? ? ? print("hello , I am a dog,my type is ",self.d_type)
print(Dog.d_type) # 查看Dog的d_type屬性
print(Dog.say_hi)? # 引用Dog的say_hi方法,注意只是引用,不是調(diào)用
實(shí)例屬性引用(實(shí)例名.屬性)
d2 = Dog("二蛋",3,"Jack")? # 生成一個(gè)狗的實(shí)例
d2.say_hi() # 調(diào)用狗這個(gè)類的方法
print(d2.name, d2.age, d2.master) # 調(diào)用實(shí)例的屬性
print(d2.d_type) # 注意通過實(shí)例也可以調(diào)用類的公共屬性
練習(xí)一
設(shè)計(jì)一個(gè)類Person,生成若干實(shí)例,在終端輸出如下信息
小明,10歲,男,上山去砍柴
小明,10歲,男,開車去東北
小明,10歲,男,最愛大保健
老李,90歲,男,上山去砍柴
老李,90歲,男,開車去東北
老李,90歲,男,最愛大保健
老張…