python學習------面向物件的程式設計
一 面向物件的程式設計的由來
1940年以前:面向機器 最早的程式設計都是採用機器語言來編寫的,直接使用二進位制碼來表示機器能夠識別和執行的指令和數 據。簡單來說,就是直接編寫 0 和 1 的序列來代表程式語言。例如:使用 0000 代表 載入(LOAD),0001 代表 儲存(STORE)等。 機器語言由機器直接執行,速度快,但一個很明顯的缺點就是:寫起來實在是太困難了,一旦你發現自己 寫錯了,改起來更蛋疼!這樣直接導致程式編寫效率十分低下,編寫程式花費的時間往往是實際執行時間 的幾十倍或幾百倍。 有一個關於機器語言和比爾蓋茨的笑話,是說比爾蓋茨拿著繡花針在一張光碟上戳,把 Windows 給戳出 來了由來!但如果真的讓你去戳,不要說 Windows,連一個簡單的“Hello world”都要讓人戳到眼睛冒煙! 由於機器語言實在是太難編寫了,於是就發展出了組合語言。組合語言亦稱符號語言,用助記符代替機器 指令的操作碼,用地址符號(Symbol)或標號(Label)代替指令或運算元的地址,。組合語言由於是採用 了助記符號來編寫程式,比用機器語言的二進位制程式碼程式設計要方便些,在一定程度上簡化了程式設計過程。例如 使用 LOAD 來代替 0000,使用 STORE 來代替 0001。 即使組合語言相比機器語言提升了可讀性,但其本質上還是一種面向機器的語言,編寫同樣困難,也很容 易出錯。相信很多計算機畢業的學生至今都對學校的彙編課程中的練習程式心有餘悸。 脫離機器第一步:面向過程 面向機器的語言通常情況下被認為是一種“低階語言”,為了解決面向機器的語言存在的問題,計算機科 學的前輩們又建立了面向過程的語言。面向過程的語言被認為是一種“高階語言”,相比面向機器的語言 來說,面向過程的語言已經不再關注機器本身的操作指令、儲存等方面,而是關注如何一步一步的解決具體的問題,即:解決問題的過程,這應該也是面向過程說法的來由。 相比面向機器的思想來說,面向過程是一次思想上的飛躍,將程式設計師從複雜的機器操作和執行的細節中解 放出來,轉而關注具體需要解決的問題;面向過程的語言也不再需要和具體的機器繫結,從而具備了移植 性和通用性;面向過程的語言本身也更加容易編寫和維護。這些因素疊加起來,大大減輕了程式設計師的負擔, 提升了程式設計師的工作效率,從而促進了軟體行業的快速發展。 典型的面向過程的語言有:COBOL、FORTRAN、BASIC、C 語言等。 第一次軟體危機:結構化程式設計 根本原因就是一些面向過程語言中的goto語句導致的麵條式程式碼,極大的限制了程式的規模。結構化程式設計(英語:Structured programming),一種程式設計範型。它採用子程式(函式就是一種子程式)、程式碼區塊、for迴圈以及while迴圈等結構,來替換傳統的goto。希望藉此來改善計算機程式的明晰性、質量以及開發時間,並且避免寫出麵條式程式碼。 隨著計算機硬體的飛速發展,以及應用複雜度越來越高,軟體規模越來越大,原有的程式開發方式已經越 來越不能滿足需求了。1960 年代中期開始爆發了第一次軟體危機,典型表現有軟體質量低下、專案無法 如期完成、專案嚴重超支等,因為軟體而導致的重大事故時有發生。例如 1963 年美國 (http://en.wikipedia.org/wiki/Mariner_1) 的水手一號火箭發射失敗事故,就是因為一行 FORTRAN 程式碼 錯誤導致的。 軟體危機最典型的例子莫過於 IBM 的 System/360 的作業系統開發。佛瑞德·布魯克斯(Frederick P. Brooks, Jr.)作為專案主管,率領 2000 多個程式設計師夜以繼日的工作,共計花費了 5000 人一年的工作量,寫出將 近 100 萬行的原始碼,總共投入 5 億美元,是美國的“曼哈頓”原子彈計劃投入的 1/4。儘管投入如此巨大, 但專案進度卻一再延遲,軟體質量也得不到保障。布魯克斯後來基於這個專案經驗而總結的《人月神話》 一書,成了史上最暢銷的軟體工程書籍。 為了解決問題,在 1968、1969 年連續召開兩次著名的 NATO 會議,會議正式創造了“軟體危機”一詞, 並提出了針對性的解決方法“軟體工程”。雖然“軟體工程”提出之後也曾被視為軟體領域的銀彈,但後 來事實證明,軟體工程同樣無法解決軟體危機。 差不多同一時間,“結構化程式設計”作為另外一種解決軟體危機的方案被提出來了。 Edsger Dijkstra 於 1968 發表了著名的《GOTO 有害論》的論文,引起了長達數年的論戰,並由此產生了結構化程式設計方 法。同時,第一個結構化的程式語言 Pascal 也在此時誕生,並迅速流行起來。 結構化程式設計的主要特點是拋棄 goto 語句,採取“自頂向下、逐步細化、模組化”的指導思想。結構 化程式設計本質上還是一種面向過程的設計思想,但通過“自頂向下、逐步細化、模組化”的方法,將軟 件的複雜度控制在一定範圍內,從而從整體上降低了軟體開發的複雜度。結構化程式方法成為了 1970 年 代軟體開發的潮流。 科學研究證明,人腦存在人類短期記憶一般一次只能記住 5-9 個事物,這就是著名的 7+- 2 原理。結構化 程式設計是面向過程設計思想的一個改進,使得軟體開發更加符合人類思維的 7+-2 特點。 第二次軟體危機:面向物件程式設計 結構化程式設計的風靡在一定程度上緩解了軟體危機,然而好景不長,隨著硬體的快速發展,業務需求越來越 複雜,以及程式設計應用領域越來越廣泛,第二次軟體危機很快就到來了。 第二次軟體危機的根本原因還是在於軟體生產力遠遠跟不上硬體和業務的發展,相比第一次軟體危機主要 體現在“複雜性”,第二次軟體危機主要體現在“可擴充套件性”、“可維護性”上面。傳統的面向過程(包括 結構化程式設計)方法已經越來越不能適應快速多變的業務需求了,軟體領域迫切希望找到新的銀彈來解 決軟體危機,在這種背景下,面向物件的思想開始流行起來。 面向物件的思想並不是在第二次軟體危機後才出現的,早在 1967 年的 Simula 語言中就開始提出來了,但 第二次軟體危機促進了面向物件的發展。 面向物件真正開始流行是在 1980s 年代,主要得益於 C++的功 勞,後來的 Java、C#把面向物件推向了新的高峰。到現在為止,面向物件已經成為了主流的開發思想。 雖然面向物件開始也被當做解決軟體危機的銀彈,但事實證明,和軟體工程一樣,面向物件也不是銀彈, 而只是一種新的軟體方法而已。 雖然面向物件並不是解決軟體危機的銀彈,但和麵向過程相比,面向物件的思想更加貼近人類思維的特點, 更加脫離機器思維,是一次軟體設計思想上的飛躍。
二 什麼是面向物件的程式設計及為什麼要有它
面向過程的程式設計:核心是過程二字,過程指的是解決問題的步驟,即先幹什麼再幹什麼......面向過程的設計就好比精心設計好一條流水線,是一種機械式的思維方式。
優點是:複雜度的問題流程化,進而簡單化(一個複雜的問題,分成一個個小的步驟去實現,實現小的步驟將會非常簡單)
缺點是:一套流水線或者流程就是用來解決一個問題,生產汽水的流水線無法生產汽車,即便是能,也得是大改,改一個元件,牽一髮而動全身。
應用場景:一旦完成基本很少改變的場景,著名的例子有Linux核心,git,以及Apache HTTP Server等。
面向物件的程式設計:核心是物件二字,(要理解物件為何物,必須把自己當成上帝,上帝眼裡世間存在的萬物皆為物件,不存在的也可以創造出來。面向物件的程式設計好比如來設計西遊記,如來要解決的問題是把經書傳給東土大唐,如來想了想解決這個問題需要四個人:唐僧,沙和尚,豬八戒,孫悟空,每個人都有各自的特徵和技能(這就是物件的概念,特徵和技能分別對應物件的資料屬性和方法屬性),然而這並不好玩,於是如來又安排了一群妖魔鬼怪,為了防止師徒四人在取經路上被搞死,又安排了一群神仙保駕護航,這些都是物件。然後取經開始,師徒四人與妖魔鬼怪神仙互動著直到最後取得真經。如來根本不會管師徒四人按照什麼流程去取),物件是特徵與技能的結合體,基於面向物件設計程式就好比在創造一個世界,你就是這個世界的上帝,存在的皆為物件,不存在的也可以創造出來,與面向過程機械式的思維方式形成鮮明對比,面向物件更加註重對現實世界的模擬,是一種“上帝式”的思維方式。
優點是:解決了程式的擴充套件性。對某一個物件單獨修改,會立刻反映到整個體系中,如對遊戲中一個人物引數的特徵和技能修改都很容易。
缺點:
1. 程式設計的複雜度遠高於面向過程,不瞭解面向物件而立即上手基於它設計程式,極容易出現過度設計的問題。一些擴充套件性要求低的場景使用面向物件會徒增程式設計難度,比如管理linux系統的shell指令碼就不適合用面向物件去設計,面向過程反而更加適合。
2. 無法向面向過程的程式設計流水線式的可以很精準的預測問題的處理流程與結果,面向物件的程式一旦開始就由物件之間的互動解決問題,即便是上帝也無法準確地預測最終結果。於是我們經常看到對戰類遊戲,新增一個遊戲人物,在對戰的過程中極容易出現陰霸的技能,一刀砍死3個人,這種情況是無法準確預知的,只有物件之間互動才能準確地知道最終的結果。
應用場景:需求經常變化的軟體,一般需求的變化都集中在使用者層,網際網路應用,企業內部軟體,遊戲等都是面向物件的程式設計大顯身手的好地方
面向物件的程式設計並不是全部。對於一個軟體質量來說,面向物件的程式設計只是用來解決擴充套件性。
三 類與物件
類即類別、種類,是面向物件設計最重要的概念,物件是特徵與技能的結合體,而類則是一系列物件相似的特徵與技能的結合體
那麼問題來了,先有的一個個具體存在的物件(比如一個具體存在的人),還是先有的人類這個概念,這個問題需要分兩種情況去看
在現實世界中:先有物件,再有類
世界上肯定是先出現各種各樣的實際存在的物體,然後隨著人類文明的發展,人類站在不同的角度總結出了不同的種類,如人類、動物類、植物類等概念
也就說,物件是具體的存在,而類僅僅只是一個概念,並不真實存在
在程式中:務必保證先定義類,後產生物件
這與函式的使用是類似的,先定義函式,後呼叫函式,類也是一樣的,在程式中需要先定義類,後呼叫類
不一樣的是,呼叫函式會執行函式體程式碼返回的是函式體執行的結果,而呼叫類會產生物件,返回的是物件
按照上述步驟,我們來定義一個類(我們站在學校的角度去看,在座的各位都是學生)
#在現實世界中,站在老男孩學校的角度:先有物件,再有類 物件1:李坦克 特徵: 學校=oldboy 姓名=李坦克 性別=男 年齡=18 技能: 學習 吃飯 睡覺 物件2:王大炮 特徵: 學校=oldboy 姓名=王大炮 性別=女 年齡=38 技能: 學習 吃飯 睡覺 物件3:牛榴彈 特徵: 學校=oldboy 姓名=牛榴彈 性別=男 年齡=78 技能: 學習 吃飯 睡覺 現實中的老男孩學生類 相似的特徵: 學校=oldboy 相似的技能: 學習 吃飯 睡覺在現實世界中:先有物件,再有類
#在程式中,務必保證:先定義(類),後使用(產生物件) PS: 1. 在程式中特徵用變數標識,技能用函式標識 2. 因而類中最常見的無非是:變數和函式的定義 #程式中的類 class OldboyStudent: school='oldboy' def learn(self): print('is learning') def eat(self): print('is eating') def sleep(self): print('is sleeping') #注意: 1.類中可以有任意python程式碼,這些程式碼在類定義階段便會執行 2.因而會產生新的名稱空間,用來存放類的變數名與函式名,可以通過OldboyStudent.__dict__檢視 3.對於經典類來說我們可以通過該字典操作類名稱空間的名字(新式類有限制),但python為我們提供專門的.語法 4.點是訪問屬性的語法,類中定義的名字,都是類的屬性 #程式中類的用法 .:專門用來訪問屬性,本質操作的就是__dict__ OldboyStudent.school #等於經典類的操作OldboyStudent.__dict__['school'] OldboyStudent.school='Oldboy' #等於經典類的操作OldboyStudent.__dict__['school']='Oldboy' OldboyStudent.x=1 #等於經典類的操作OldboyStudent.__dict__['x']=1 del OldboyStudent.x #等於經典類的操作OldboyStudent.__dict__.pop('x') #程式中的物件 #呼叫類,或稱為例項化,得到物件 s1=OldboyStudent() s2=OldboyStudent() s3=OldboyStudent() #如此,s1、s2、s3都一樣了,而這三者除了相似的屬性之外還各種不同的屬性,這就用到了__init__ #注意:該方法是在物件產生之後才會執行,只用來為物件進行初始化操作,可以有任意程式碼,但一定不能有返回值 class OldboyStudent: ...... def __init__(self,name,age,sex): self.name=name self.age=age self.sex=sex ...... s1=OldboyStudent('李坦克','男',18) #先呼叫類產生空物件s1,然後呼叫OldboyStudent.__init__(s1,'李坦克','男',18) s2=OldboyStudent('王大炮','女',38) s3=OldboyStudent('牛榴彈','男',78) #程式中物件的用法 #執行__init__,s1.name='牛榴彈',很明顯也會產生物件的名稱空間 s2.__dict__ {'name': '王大炮', 'age': '女', 'sex': 38} s2.name #s2.__dict__['name'] s2.name='王三炮' #s2.__dict__['name']='王三炮' s2.course='python' #s2.__dict__['course']='python' del s2.course #s2.__dict__.pop('course')在程式中:先定義類,後產生物件
!!!細說__init__方法!!!
#方式一、為物件初始化自己獨有的特徵 class People: country='China' x=1 def run(self): print('----->', self) # 例項化出三個空物件 obj1=People() obj2=People() obj3=People() # 為物件定製自己獨有的特徵 obj1.name='egon' obj1.age=18 obj1.sex='male' obj2.name='lxx' obj2.age=38 obj2.sex='female' obj3.name='alex' obj3.age=38 obj3.sex='female' # print(obj1.__dict__) # print(obj2.__dict__) # print(obj3.__dict__) # print(People.__dict__) #方式二、為物件初始化自己獨有的特徵 class People: country='China' x=1 def run(self): print('----->', self) # 例項化出三個空物件 obj1=People() obj2=People() obj3=People() # 為物件定製自己獨有的特徵 def chu_shi_hua(obj, x, y, z): #obj=obj1,x='egon',y=18,z='male' obj.name = x obj.age = y obj.sex = z chu_shi_hua(obj1,'egon',18,'male') chu_shi_hua(obj2,'lxx',38,'female') chu_shi_hua(obj3,'alex',38,'female') #方式三、為物件初始化自己獨有的特徵 class People: country='China' x=1 def chu_shi_hua(obj, x, y, z): #obj=obj1,x='egon',y=18,z='male' obj.name = x obj.age = y obj.sex = z def run(self): print('----->', self) obj1=People() # print(People.chu_shi_hua) People.chu_shi_hua(obj1,'egon',18,'male') obj2=People() People.chu_shi_hua(obj2,'lxx',38,'female') obj3=People() People.chu_shi_hua(obj3,'alex',38,'female') # 方式四、為物件初始化自己獨有的特徵 class People: country='China' x=1 def __init__(obj, x, y, z): #obj=obj1,x='egon',y=18,z='male' obj.name = x obj.age = y obj.sex = z def run(self): print('----->', self) obj1=People('egon',18,'male') #People.__init__(obj1,'egon',18,'male') obj2=People('lxx',38,'female') #People.__init__(obj2,'lxx',38,'female') obj3=People('alex',38,'female') #People.__init__(obj3,'alex',38,'female') # __init__方法 # 強調: # 1、該方法內可以有任意的python程式碼 # 2、一定不能有返回值 class People: country='China' x=1 def __init__(obj, name, age, sex): #obj=obj1,x='egon',y=18,z='male' # if type(name) is not str: # raise TypeError('名字必須是字串型別') obj.name = name obj.age = age obj.sex = sex def run(self): print('----->', self) # obj1=People('egon',18,'male') obj1=People(3537,18,'male') # print(obj1.run) # obj1.run() #People.run(obj1) # print(People.run)!!!__init__方法之為物件定製自己獨有的特徵
PS:
1. 站的角度不同,定義出的類是截然不同的,詳見面向物件實戰之需求分析
2. 現實中的類並不完全等於程式中的類,比如現實中的公司類,在程式中有時需要拆分成部門類,業務類......
3. 有時為了程式設計需求,程式中也可能會定義現實中不存在的類,比如策略類,現實中並不存在,但是在程式中卻是一個很常見的類
#python為類內建的特殊屬性 類名.__name__# 類的名字(字串) 類名.__doc__# 類的文件字串 類名.__base__# 類的第一個父類(在講繼承時會講) 類名.__bases__# 類所有父類構成的元組(在講繼承時會講) 類名.__dict__# 類的字典屬性 類名.__module__# 類定義所在的模組 類名.__class__# 例項對應的類(僅新式類中)類的特殊屬性(瞭解即可)
!!!補充說明:從程式碼級別看面向物件 !!!
#1、在沒有學習類這個概念時,資料與功能是分離的 def exc1(host,port,db,charset): conn=connect(host,port,db,charset) conn.execute(sql) return xxx def exc2(host,port,db,charset,proc_name) conn=connect(host,port,db,charset) conn.call_proc(sql) return xxx #每次呼叫都需要重複傳入一堆引數 exc1('127.0.0.1',3306,'db1','utf8','select * from tb1;') exc2('127.0.0.1',3306,'db1','utf8','儲存過程的名字') #2、我們能想到的解決方法是,把這些變數都定義成全域性變數 HOST=‘127.0.0.1’ PORT=3306 DB=‘db1’ CHARSET=‘utf8’ def exc1(host,port,db,charset): conn=connect(host,port,db,charset) conn.execute(sql) return xxx def exc2(host,port,db,charset,proc_name) conn=connect(host,port,db,charset) conn.call_proc(sql) return xxx exc1(HOST,PORT,DB,CHARSET,'select * from tb1;') exc2(HOST,PORT,DB,CHARSET,'儲存過程的名字') #3、但是2的解決方法也是有問題的,按照2的思路,我們將會定義一大堆全域性變數,這些全域性變數並沒有做任何區分,即能夠被所有功能使用,然而事實上只有HOST,PORT,DB,CHARSET是給exc1和exc2這兩個功能用的。言外之意:我們必須找出一種能夠將資料與操作資料的方法組合到一起的解決方法,這就是我們說的類了 class MySQLHandler: def __init__(self,host,port,db,charset='utf8'): self.host=host self.port=port self.db=db self.charset=charset def exc1(self,sql): conn=connect(self.host,self.port,self.db,self.charset) res=conn.execute(sql) return res def exc2(self,sql): conn=connect(self.host,self.port,self.db,self.charset) res=conn.call_proc(sql) return res obj=MySQLHandler('127.0.0.1',3306,'db1') obj.exc1('select * from tb1;') obj.exc2('儲存過程的名字') #改進 class MySQLHandler: def __init__(self,host,port,db,charset='utf8'): self.host=host self.port=port self.db=db self.charset=charset self.conn=connect(self.host,self.port,self.db,self.charset) def exc1(self,sql): return self.conn.execute(sql) def exc2(self,sql): return self.conn.call_proc(sql) obj=MySQLHandler('127.0.0.1',3306,'db1') obj.exc1('select * from tb1;') obj.exc2('儲存過程的名字')資料與專門操作該資料的功能組合到一起
四 屬性查詢
類有兩種屬性:資料屬性和函式屬性
1. 類的資料屬性是所有物件共享的
2. 類的函式屬性是繫結給物件用的
#類的資料屬性是所有物件共享的,id都一樣 print(id(OldboyStudent.school)) print(id(s1.school)) print(id(s2.school)) print(id(s3.school)) ''' 4377347328 4377347328 ''' #類的函式屬性是繫結給物件使用的,obj.method稱為繫結方法,記憶體地址都不一樣 #ps:id是python的實現機制,並不能真實反映記憶體地址,如果有記憶體地址,還是以記憶體地址為準 print(OldboyStudent.learn) print(s1.learn) print(s2.learn) print(s3.learn) ''' <function OldboyStudent.learn at 0x1021329d8> <bound method OldboyStudent.learn of <__main__.OldboyStudent object at 0x1021466d8>> <bound method OldboyStudent.learn of <__main__.OldboyStudent object at 0x102146710>> <bound method OldboyStudent.learn of <__main__.OldboyStudent object at 0x102146748>> '''View Code
在obj.name會先從obj自己的名稱空間裡找name,找不到則去類中找,類也找不到就找父類...最後都找不到就丟擲異常
練習:編寫一個學生類,產生一堆學生物件,要求有一個計數器(屬性),統計總共例項了多少個物件
五 繫結到物件的方法的特殊之處
#改寫 class OldboyStudent: school='oldboy' def __init__(self,name,age,sex): self.name=name self.age=age self.sex=sex def learn(self): print('%s is learning' %self.name) #新增self.name def eat(self): print('%s is eating' %self.name) def sleep(self): print('%s is sleeping' %self.name) s1=OldboyStudent('李坦克','男',18) s2=OldboyStudent('王大炮','女',38) s3=OldboyStudent('牛榴彈','男',78)
類中定義的函式(沒有被任何裝飾器裝飾的)是類的函式屬性,類可以使用,但必須遵循函式的引數規則,有幾個引數需要傳幾個引數
OldboyStudent.learn(s1) #李坦克 is learning
OldboyStudent.learn(s2) #王大炮 is learning
OldboyStudent.learn(s3) #牛榴彈 is learning
類中定義的函式(沒有被任何裝飾器裝飾的),其實主要是給物件使用的,而且是繫結到物件的,雖然所有物件指向的都是相同的功能,但是繫結到不同的物件就是不同的繫結方法
強調:繫結到物件的方法的特殊之處在於,繫結給誰就由誰來呼叫,誰來呼叫,就會將‘誰’本身當做第一個引數傳給方法,即自動傳值(方法__init__也是一樣的道理)
s1.learn() #等同於OldboyStudent.learn(s1) s2.learn() #等同於OldboyStudent.learn(s2) s3.learn() #等同於OldboyStudent.learn(s3)
注意:繫結到物件的方法的這種自動傳值的特徵,決定了在類中定義的函式都要預設寫一個引數self,self可以是任意名字,但是約定俗成地寫出self。
類即型別
提示:python的class術語與c++有一定區別,與 Modula-3更像。
python中一切皆為物件,且python3中類與型別是一個概念,型別就是類
#型別dict就是類dict >>> list <class 'list'> #例項化的到3個物件l1,l2,l3 >>> l1=list() >>> l2=list() >>> l3=list() #三個物件都有繫結方法append,是相同的功能,但記憶體地址不同 >>> l1.append <built-in method append of list object at 0x10b482b48> >>> l2.append <built-in method append of list object at 0x10b482b88> >>> l3.append <built-in method append of list object at 0x10b482bc8> #操作繫結方法l1.append(3),就是在往l1新增3,絕對不會將3新增到l2或l3 >>> l1.append(3) >>> l1 [3] >>> l2 [] >>> l3 [] #呼叫類list.append(l3,111)等同於l3.append(111) >>> list.append(l3,111) #l3.append(111) >>> l3 [111]
六 物件之間的互動
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 #根據自己的攻擊力,攻擊敵人就減掉敵人的生命值。
我們可以仿照garen類再建立一個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 #根據自己的攻擊力,攻擊敵人就減掉敵人的生命值。
例項出倆英雄
>>> g1=Garen('草叢倫') >>> r1=Riven('銳雯雯')
互動:銳雯雯攻擊草叢倫,反之一樣
>>> g1.life_value 455 >>> r1.attack(g1) >>> g1.life_value 401
補充:
garen_hero.Q()稱為向garen_hero這個物件傳送了一條訊息,讓他去執行Q這個功能,類似的有:
garen_hero.W()
garen_hero.E()
garen_hero.R()
七 練習
八 繼承與派生
九 多型與多型性
十 封裝
十一 繫結方法與非繫結方法
十二 小白容易犯的錯誤
1.面向物件的程式設計看起來高大上,所以我在程式設計時就應該保證通篇class,這樣寫出的程式一定是好的程式(面向物件只適合那些可擴充套件性要求比較高的場景)
2.很多人喜歡說面向物件三大特性(這是從哪傳出來的,封裝,多型,繼承?漏洞太多太多,好吧暫且稱為三大特性),那麼我在基於面向物件程式設計時,我一定要讓我定義的類中完整的包含這三種特性,這樣寫肯定是好的程式
好傢伙,我說降龍十八掌有十八掌,那麼你每次跟人幹仗都要從第一掌打到第18掌這才顯得你會了是麼:面對敵人,你打到第三掌對方就已經倒下了,你說,不行,你給老子起來,老子還沒有show完...
3.類有類屬性,例項有例項屬性,所以我們在定義class時一定要定義出那麼幾個類屬性,想不到怎麼辦,那就使勁的想,定義的越多越牛逼
這就犯了一個嚴重的錯誤,程式越早面向物件,死的越早,為啥面向物件,因為我們要將資料與功能結合到一起,程式整體的結構都沒有出來,或者說需要考慮的問題你都沒有搞清楚個八九不離十,你就開始面向物件了,這就導致了,你在那裡幹想,自以為想通了,定義了一堆屬性,結果後來又都用不到,或者想不通到底應該定義啥,那就一直想吧,想著想著就瘋了。
你見過哪家公司要開發一個軟體,上來就開始寫,肯定是頻繁的開會討論計劃,請看第八節
4.既然這麼麻煩,那麼我徹底解脫了,我們不要用面向物件程式設計了,你啊,你有大才,你能成事啊,傻叉。
十三 python中關於OOP的常用術語
抽象/實現
抽象指對現實世界問題和實體的本質表現,行為和特徵建模,建立一個相關的子集,可以用於 繪程式結構,從而實現這種模型。抽象不僅包括這種模型的資料屬性,還定義了這些資料的介面。
對某種抽象的實現就是對此資料及與之相關介面的現實化(realization)。現實化這個過程對於客戶 程式應當是透明而且無關的。
封裝/介面
封裝描述了對資料/資訊進行隱藏的觀念,它對資料屬性提供介面和訪問函式。通過任何客戶端直接對資料的訪問,無視介面,與封裝性都是背道而馳的,除非程式設計師允許這些操作。作為實現的 一部分,客戶端根本就不需要知道在封裝之後,資料屬性是如何組織的。在Python中,所有的類屬性都是公開的,但名字可能被“混淆”了,以阻止未經授權的訪問,但僅此而已,再沒有其他預防措施了。這就需要在設計時,對資料提供相應的介面,以免客戶程式通過不規範的操作來存取封裝的資料屬性。
注意:封裝絕不是等於“把不想讓別人看到、以後可能修改的東西用private隱藏起來”
真正的封裝是,經過深入的思考,做出良好的抽象,給出“完整且最小”的介面,並使得內部細節可以對外透明
(注意:對外透明的意思是,外部呼叫者可以順利的得到自己想要的任何功能,完全意識不到內部細節的存在)
合成
合成擴充了對類的 述,使得多個不同的類合成為一個大的類,來解決現實問題。合成 述了 一個異常複雜的系統,比如一個類由其它類組成,更小的元件也可能是其它的類,資料屬性及行為, 所有這些合在一起,彼此是“有一個”的關係。
派生/繼承/繼承結構
派生描述了子類衍生出新的特性,新類保留已存類型別中所有需要的資料和行為,但允許修改或者其它的自定義操作,都不會修改原類的定義。
繼承描述了子類屬性從祖先類繼承這樣一種方式
繼承結構表示多“代”派生,可以述成一個“族譜”,連續的子類,與祖先類都有關係。
泛化/特化
基於繼承
泛化表示所有子類與其父類及祖先類有一樣的特點。
特化描述所有子類的自定義,也就是,什麼屬性讓它與其祖先類不同。
多型與多型性
多型指的是同一種事物的多種狀態:水這種事物有多種不同的狀態:冰,水蒸氣
多型性的概念指出了物件如何通過他們共同的屬性和動作來操作及訪問,而不需考慮他們具體的類。
冰,水蒸氣,都繼承於水,它們都有一個同名的方法就是變成雲,但是冰.變雲(),與水蒸氣.變雲()是截然不同的過程,雖然呼叫的方法都一樣
自省/反射
自省也稱作反射,這個性質展示了某物件是如何在執行期取得自身資訊的。如果傳一個物件給你,你可以查出它有什麼能力,這是一項強大的特性。如果Python不支援某種形式的自省功能,dir和type內建函式,將很難正常工作。還有那些特殊屬性,像__dict__,__name__及__doc__
十四 面向物件的軟體開發
很多人在學完了python的class機制之後,遇到一個生產中的問題,還是會懵逼,這其實太正常了,因為任何程式的開發都是先設計後程式設計,python的class機制只不過是一種程式設計方式,如果你硬要拿著class去和你的問題死磕,變得更加懵逼都是分分鐘的事,在以前,軟體的開發相對簡單,從任務的分析到編寫程式,再到程式的除錯,可以由一個人或一個小組去完成。但是隨著軟體規模的迅速增大,軟體任意麵臨的問題十分複雜,需要考慮的因素太多,在一個軟體中所產生的錯誤和隱藏的錯誤、未知的錯誤可能達到驚人的程度,這也不是在設計階段就完全解決的。
所以軟體的開發其實一整套規範,我們所學的只是其中的一小部分,一個完整的開發過程,需要明確每個階段的任務,在保證一個階段正確的前提下再進行下一個階段的工作,稱之為軟體工程
面向物件的軟體工程包括下面幾個部:
1.面向物件分析(object oriented analysis ,OOA)
軟體工程中的系統分析階段,要求分析員和使用者結合在一起,對使用者的需求做出精確的分析和明確的表述,從大的方面解析軟體系統應該做什麼,而不是怎麼去做。面向物件的分析要按照面向物件的概念和方法,在對任務的分析中,從客觀存在的事物和事物之間的關係,貴南出有關的物件(物件的‘特徵’和‘技能’)以及物件之間的聯絡,並將具有相同屬性和行為的物件用一個類class來標識。
建立一個能反映這是工作情況的需求模型,此時的模型是粗略的。
2 面向物件設計(object oriented design,OOD)
根據面向物件分析階段形成的需求模型,對每一部分分別進行具體的設計。
首先是類的設計,類的設計可能包含多個層次(利用繼承與派生機制)。然後以這些類為基礎提出程式設計的思路和方法,包括對演算法的設計。
在設計階段並不牽涉任何一門具體的計算機語言,而是用一種更通用的描述工具(如虛擬碼或流程圖)來描述
3 面向物件程式設計(object oriented programming,OOP)
根據面向物件設計的結果,選擇一種計算機語言把它寫成程式,可以是python
4 面向物件測試(object oriented test,OOT)
在寫好程式後交給使用者使用前,必須對程式進行嚴格的測試,測試的目的是發現程式中的錯誤並修正它。
面向對的測試是用面向物件的方法進行測試,以類作為測試的基本單元。
5 面向物件維護(object oriendted soft maintenance,OOSM)
正如對任何產品都需要進行售後服務和維護一樣,軟體在使用時也會出現一些問題,或者軟體商想改進軟體的效能,這就需要修改程式。
由於使用了面向物件的方法開發程式,使用程式的維護比較容易。
因為物件的封裝性,修改一個物件對其他的物件影響很小,利用面向物件的方法維護程式,大大提高了軟體維護的效率,可擴充套件性高。
在面向物件方法中,最早發展的肯定是面向物件程式設計(OOP),那時OOA和OOD都還沒有發展起來,因此程式設計者為了寫出面向物件的程式,還必須深入到分析和設計領域,尤其是設計領域,那時的OOP實際上包含了現在的OOD和OOP兩個階段,這對程式設計者要求比較高,許多人感到很難掌握。
現在設計一個大的軟體,是嚴格按照面向物件軟體工程的5個階段進行的,這個5個階段的工作不是由一個人從頭到尾完成的,而是由不同的人分別完成,這樣OOP階段的任務就比較簡單了。程式編寫者只需要根據OOd提出的思路,用面嚮物件語言編寫出程式既可。
十五 面向物件實戰
本週作業
角色:學校、學員、課程、講師
要求:
1. 建立北京、上海 2 所學校
2. 建立linux , python , go 3個課程 , linux\py 在北京開, go 在上海開
3. 課程包含,週期,價格,通過學校建立課程
4. 通過學校建立班級, 班級關聯課程、講師
5. 建立學員時,選擇學校,關聯班級
5. 建立講師角色時要關聯學校,
6. 提供兩個角色介面
6.1 學員檢視, 可以註冊, 交學費, 選擇班級,
6.2 講師檢視, 講師可管理自己的班級, 上課時選擇班級, 檢視班級學員列表 , 修改所管理的學員的成績
6.3 管理檢視,建立講師, 建立班級,建立課程
7. 上面的操作產生的資料都通過pickle序列化儲存到檔案裡