Python之路,Day6 - 面向對象學習
本節內容:
面向對象編程介紹
爲什麼要用面向對象進行開發?
面向對象的特性:封裝、繼承、多態
類、方法、
引子
你現在是一家遊戲公司的開發人員,現在需要你開發一款叫做<人狗大戰>的遊戲,你就思考呀,人狗作戰,那至少需要2個角色,一個是人, 一個是狗,且人和狗都有不同的技能,比如人拿棍打狗, 狗可以咬人,怎麼描述這種不同的角色和他們的功能呢?
你搜羅了自己掌握的所有技能,寫出了下面的代碼來描述這兩個角色
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
上面兩個方法相當於造了兩個模子,遊戲開始,你得生成一個人和狗的實際對象吧,怎麼生成呢?
d1 = dog("李磊","京巴")
p1 = person("嚴帥",36,"F","運維")
p2 = person("林海峯",27,"F","Teacher")
兩個角色對象生成了,狗和人還有不同的功能呀,狗會咬人,人會打狗,對不對? 怎麼實現呢,。。想到了, 可以每個功能再寫一個函數,想執行哪個功能,直接 調用 就可以了,對不?
def bark(d):
print("dog %s:wang.wang..wang..."%d['name'])
def walk(p):
print("person %s is walking..." %p['name'])<br><br>
--
d1 = dog("李磊","京巴")
p1 = person("嚴帥",36,"F","運維")
p2 = person("林海峯",27,"F","Teacher")
--
walk(p1) bark(d1)
上面的功能實現的簡直是完美!
但是仔細玩耍一會,你就不小心幹了下面這件事
p1 = person("嚴帥",36,"F","運維")
bark(p1) #把人的對象傳給了狗的方法
事實 上,這並沒出錯。很顯然,人是不能調用狗的功能的,如何在代碼級別實現這個限制呢?
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","運維")
p2 = person("林海峯",27,"F","Teacher")<br><br>
--
d1['bark'](p1) #把人的對象傳給了狗的方法
你是如此的機智,這樣就實現了限制人只能用人自己的功能啦。
但,我的哥,不要高興太早,剛纔你只是阻止了兩個完全 不同的角色 之前的功能混用, 但有沒有可能 ,同一個種角色,但有些屬性是不同的呢? 比如 ,大家都打過cs吧,cs裏有警察和恐怖份子,但因爲都 是人, 所以你寫一個角色叫 person(), 警察和恐怖份子都 可以 互相射擊,但警察不可以殺人質,恐怖分子可以,這怎麼實現呢? 你想了說想,說,簡單,只需要在殺人質的功能里加個判斷,如果是警察,就不讓殺不就ok了麼。 沒錯, 這雖然 解決了殺人質的問題,但其實你會發現,警察和恐怖分子的區別還有很多,同時又有很多共性,如果 在每個區別處都 單獨做判斷,那得累死。
你想了想說, 那就直接寫2個角色吧, 反正 這麼多區別, 我的哥, 不能寫兩個角色呀,因爲他們還有很多共性 , 寫兩個不同的角色,就代表 相同的功能 也要重寫了,是不是我的哥? 。。。
好了, 話題就給你點到這, 再多說你的智商 也理解不了了!
面向過程 VS 面向對象
編程範式
編程是 程序 員 用特定的語法+數據結構+算法組成的代碼來告訴計算機如何執行任務的過程 , 一個程序是程序員爲了得到一個任務結果而編寫的一組指令的集合,正所謂條條大路通羅馬,實現一個任務的方式有很多種不同的方式, 對這些不同的編程方式的特點進行歸納總結得出來的編程方式類別,即爲編程範式。 不同的編程範式本質上代表對各種類型的任務採取的不同的解決問題的思路, 大多數語言只支持一種編程範式,當然也有些語言可以同時支持多種編程範式。 兩種最重要的編程範式分別是面向過程編程和麪向對象編程。
面向過程編程(Procedural Programming)
Procedural programming uses a list of instructions to tell the computer what to do step-by-step.
面向過程編程依賴 - 你猜到了- procedures,一個procedure包含一組要被進行計算的步驟, 面向過程又被稱爲top-down languages, 就是程序從上到下一步步執行,一步步從上到下,從頭到尾的解決問題 。基本設計思路就是程序一開始是要着手解決一個大的問題,然後把一個大問題分解成很多個小問題或子過程,這些子過程再執行的過程再繼續分解直到小問題足夠簡單到可以在一個小步驟範圍內解決。
舉個典型的面向過程的例子, 數據庫備份, 分三步,連接數據庫,備份數據庫,測試備份文件可用性。
代碼如下
def db_conn():
print("connecting db...")
def db_backup(dbname):
print("導出數據庫...",dbname)
print("將備份文件打包,移至相應目錄...")
def db_backup_test():
print("將備份文件導入測試庫,看導入是否成功")
def main():
db_conn()
db_backup('my_db')
db_backup_test()
if __name__ == '__main__':
main()
這樣做的問題也是顯而易見的,就是如果你要對程序進行修改,對你修改的那部分有依賴的各個部分你都也要跟着修改, 舉個例子,如果程序開頭你設置了一個變量值 爲1 , 但如果其它子過程依賴這個值 爲1的變量才能正常運行,那如果你改了這個變量,那這個子過程你也要修改,假如又有一個其它子程序依賴這個子過程 , 那就會發生一連串的影響,隨着程序越來越大, 這種編程方式的維護難度會越來越高。
所以我們一般認爲, 如果你只是寫一些簡單的腳本,去做一些一次性任務,用面向過程的方式是極好的,但如果你要處理的任務是複雜的,且需要不斷迭代和維護 的, 那還是用面向對象最方便了。
面向對象編程
OOP編程是利用“類”和“對象”來創建各種模型來實現對真實世界的描述,使用面向對象編程的原因一方面是因爲它可以使程序的維護和擴展變得更簡單,並且可以大大提高程序開發效率 ,另外,基於面向對象的程序可以使它人更加容易理解你的代碼邏輯,從而使團隊開發變得更從容。
面向對象的幾個核心特性如下
Class 類
一個類即是對一類擁有相同屬性的對象的抽象、藍圖、原型。在類中定義了這些對象的都具備的屬性(variables(data))、共同的方法
Object 對象
一個對象即是一個類的實例化後實例,一個類必須經過實例化後方可在程序中調用,一個類可以實例化多個對象,每個對象亦可以有不同的屬性,就像人類是指所有人,每個人是指具體的對象,人與人之前有共性,亦有不同
Encapsulation 封裝
在類中對數據的賦值、內部調用對外部用戶是透明的,這使類變成了一個膠囊或容器,裏面包含着類的數據和方法
Inheritance 繼承
一個類可以派生出子類,在這個父類裏定義的屬性、方法自動被子類繼承
Polymorphism 多態
多態是面向對象的重要特性,簡單點說:“一個接口,多種實現”,指一個基類中派生出了不同的子類,且每個子類在繼承了同樣的方法名的同時又對父類的方法做了不同的實現,這就是同一種事物表現出的多種形態。
編程其實就是一個將具體世界進行抽象化的過程,多態就是抽象化的一種體現,把一系列具體事物的共同點抽象出來, 再通過這個抽象的事物, 與不同的具體事物進行對話。
對不同類的對象發出相同的消息將會有不同的行爲。比如,你的老闆讓所有員工在九點鐘開始工作, 他只要在九點鐘的時候說:“開始工作”即可,而不需要對銷售人員說:“開始銷售工作”,對技術人員說:“開始技術工作”, 因爲“員工”是一個抽象的事物, 只要是員工就可以開始工作,他知道這一點就行了。至於每個員工,當然會各司其職,做各自的工作。
多態允許將子類的對象當作父類的對象使用,某父類型的引用指向其子類型的對象,調用的方法是該子類型的方法。這裏引用和調用方法的代碼編譯前就已經決定了,而引用所指向的對象可以在運行期間動態綁定
面向對象編程(Object-Oriented Programming )介紹
對於編程語言的初學者來講,OOP不是一個很容易理解的編程方式,大家雖然都按老師講的都知道OOP的三大特性是繼承、封裝、多態,並且大家也都知道了如何定義類、方法等面向對象的常用語法,但是一到真正寫程序的時候,還是很多人喜歡用函數式編程來寫代碼,特別是初學者,很容易陷入一個窘境就是“我知道面向對象,我也會寫類,但我依然沒發現在使用了面向對象後,對我們的程序開發效率或其它方面帶來什麼好處,因爲我使用函數編程就可以減少重複代碼並做到程序可擴展了,爲啥子還用面向對象?”。 對於此,我個人覺得原因應該還是因爲你沒有充分瞭解到面向對象能帶來的好處,今天我就寫一篇關於面向對象的入門文章,希望能幫大家更好的理解和使用面向對象編程。
無論用什麼形式來編程,我們都要明確記住以下原則:
- 寫重複代碼是非常不好的低級行爲
- 你寫的代碼需要經常變更
開發正規的程序跟那種寫個運行一次就扔了的小腳本一個很大不同就是,你的代碼總是需要不斷的更改,不是修改bug就是添加新功能等,所以爲了日後方便程序的修改及擴展,你寫的代碼一定要遵循易讀、易改的原則(專業數據叫可讀性好、易擴展)。
如果你把一段同樣的代碼複製、粘貼到了程序的多個地方以實現在程序的各個地方調用 這個功能,那日後你再對這個功能進行修改時,就需要把程序裏多個地方都改一遍,這種寫程序的方式是有問題的,因爲如果你不小心漏掉了一個地方沒改,那可能會導致整個程序的運行都 出問題。 因此我們知道 在開發中一定要努力避免寫重複的代碼,否則就相當於給自己再挖坑。
還好,函數的出現就能幫我們輕鬆的解決重複代碼的問題,對於需要重複調用的功能,只需要把它寫成一個函數,然後在程序的各個地方直接調用這個函數名就好了,並且當需要修改這個功能時,只需改函數代碼,然後整個程序就都更新了。
其實OOP編程的主要作用也是使你的代碼修改和擴展變的更容易,那麼小白要問了,既然函數都能實現這個需求了,還要OOP幹毛線用呢? 呵呵,說這話就像,古時候,人們打仗殺人都用刀,後來出來了槍,它的主要功能跟刀一樣,也是殺人,然後小白就問,既然刀能殺人了,那還要槍幹毛線,哈哈,顯而易見,因爲槍能更好更快更容易的殺人。函數編程與OOP的主要區別就是OOP可以使程序更加容易擴展和易更改。
小白說,我讀書少,你別騙我,口說無憑,證明一下,好吧,那我們就下面的例子證明給小白看。
相信大家都打過CS遊戲吧,我們就自己開發一個簡單版的CS來玩一玩。
暫不考慮開發場地等複雜的東西,我們先從人物角色下手, 角色很簡單,就倆個,恐怖份子、警察,他們除了角色不同,其它基本都 一樣,每個人都有生命值、武器等。 咱們先用非OOP的方式寫出遊戲的基本角色
#role 1
name = 'Alex'
role = 'terrorist'
weapon = 'AK47'
life_value = 100
#rolw 2
name2 = 'Jack'
role2 = 'police'
weapon2 = 'B22'
life_value2 = 100
上面定義了一個恐怖份子Alex和一個警察Jack,但只2個人不好玩呀,一干就死了,沒意思,那我們再分別一個恐怖分子和警察吧,
#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個角色雖然創建好了,但是有個問題就是,每創建一個角色,我都要單獨命名,name1,name2,name3,name4…,後面的調用的時候這個變量名你還都得記着,要是再讓多加幾個角色,估計調用時就很容易弄混啦,所以我們想一想,能否所有的角色的變量名都是一樣的,但調用的時候又能區分開分別是誰?
當然可以,我們只需要把上面的變量改成字典的格式就可以啦。
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
很好,這個以後調用這些角色時只需要roles[1],roles[2]就可以啦,角色的基本屬性設計完了後,我們接下來爲每個角色開發以下幾個功能
- 被打中後就會掉血的功能
- 開槍功能
- 換×××
- 買槍
- 跑、走、跳、下蹲等動作
- 保護人質(僅適用於警察)
- 不能殺同伴
- 。。。
我們可以把每個功能寫成一個函數,類似如下:
def shot(by_who):
#開了槍後要減×××數
pass
def got_shot(who):
#中槍後要減血
who[‘life_value’] -= 10
pass
def buy_gun(who,gun_name):
#檢查錢夠不夠,買了槍後要扣錢
pass
...
so far so good, 繼續按照這個思路設計,再完善一下代碼,遊戲的簡單版就出來了,但是在往下走之前,我們來看看上面的這種代碼寫法有沒有問題,至少從上面的代碼設計中,我看到以下幾點缺陷:
- 每個角色定義的屬性名稱是一樣的,但這種命名規則是我們自己約定的,從程序上來講,並沒有進行屬性合法性檢測,也就是說role 1定義的代表武器的屬性是weapon, role 2 ,3,4也是一樣的,不過如果我在新增一個角色時不小心把weapon 寫成了wepon , 這個程序本身是檢測 不到的
- terrorist 和police這2個角色有些功能是不同的,比如police是不能殺人質的,但是terrorist可能,隨着這個遊戲開發的更復雜,我們會發現這2個角色後續有更多的不同之處, 但現在的這種寫法,我們是沒辦法 把這2個角色適用的功能區分開來的,也就是說,每個角色都可以直接調用任意功能,沒有任何限制。
- 我們在上面定義了got_shot()後要減血,也就是說減血這個動作是應該通過被擊中這個事件來引起的,我們調用get_shot(),got_shot()這個函數再調用每個角色裏的life_value變量來減血。 但其實我不通過got_shot(),直接調用角色roles[role_id][‘life_value’] 減血也可以呀,但是如果這樣調用的話,那可以就是簡單粗暴啦,因爲減血之前其它還應該判斷此角色是否穿了防彈衣等,如果穿了的話,傷害值肯定要減少,got_shot()函數裏就做了這樣的檢測,你這裏直接繞過的話,程序就亂了。 因此這裏應該設計 成除了通過got_shot(),其它的方式是沒有辦法給角色減血的,不過在上面的程序設計裏,是沒有辦法實現的。
- 現在需要給所有角色添加一個可以穿防彈衣的功能,那很顯然你得在每個角色裏放一個屬性來存儲此角色是否穿 了防彈衣,那就要更改每個角色的代碼,給添加一個新屬性,這樣太low了,不符合代碼可複用的原則
上面這4點問題如果不解決,以後肯定會引出更大的坑,有同學說了,解決也不復雜呀,直接在每個功能調用時做一下角色判斷啥就好了,沒錯,你要非得這麼霸王硬上弓的搞也肯定是可以實現的,那你自己就開發相應的代碼來對上面提到的問題進行處理好啦。 但這些問題其實能過OOP就可以很簡單的解決。
之前的代碼改成用OOP中的“類”來實現的話如下:
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’) #生成一個角色
r2 = Role('Jack','terrorist','B22’) #生成一個角色
先不考慮語法細節,相比靠函數拼湊出來的寫法,上面用面向對象中的類來寫最直接的改進有以下2點:
- 代碼量少了近一半
- 角色和它所具有的功能可以一目瞭然看出來
在真正開始分解上面代碼含義之之前,我們現來了解一些類的基本定義
類的語法
class Dog(object):
print("hello,I am a dog!")
d = Dog() #實例化這個類,
#此時的d就是類Dog的實例化對象
#實例化,其實就是以Dog類爲模版,在內存裏開闢一塊空間,存上數據,賦值成一個變量名
上面的代碼其實有問題,想給狗起名字傳不進去。
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? 此時的你一臉蒙逼,相信不畫個圖,你的智商是理解不了的!
畫圖之前, 你先註釋掉這兩句
# d = Dog('LiChuang', "京巴")
# d.sayhi()
print(Dog)
沒實例直接打印Dog輸出如下
<class '__main__.Dog'>
這代表什麼?代表 即使不實例化,這個Dog類本身也是已經存在內存裏的對不對, yes, cool,那實例化時,會產生什麼化學反應呢?
根據上圖我們得知,其實self,就是實例本身!你實例化時python會自動把這個實例本身通過self參數傳進去。
你說好吧,假裝懂了, 但下面這段代碼你又不明白了, 爲何sayhi(self),要寫個self呢?
class Dog(object):
...
def sayhi(self):
print("hello,I am a dog, my name is ",self.name)
這個原因,我課上在講。。。
<br><br><br><br>
好了,明白 了類的基本定義,接下來我們一起分解一下上面的代碼分別 是什麼意思
class Role(object): #定義一個類, class是定義類的語法,Role是類名,(object)是新式類的寫法,必須這樣寫,以後再講爲什麼
def __init__(self,name,role,weapon,life_value=100,money=15000): #初始化函數,在生成一個角色時要初始化的一些屬性就填寫在這裏
self.name = name #__init__中的第一個參數self,和這裏的self都 是什麼意思? 看下面解釋
self.role = role
self.weapon = weapon
self.life_value = life_value
self.money = money
上面的這個init()叫做初始化方法(或構造方法), 在類被調用時,這個方法(雖然它是函數形式,但在類中就不叫函數了,叫方法)會自動執行,進行一些初始化的動作,所以我們這裏寫的init(self,name,role,weapon,life_value=100,money=15000)就是要在創建一個角色時給它設置這些屬性,那麼這第一個參數self是幹毛用的呢?
初始化一個角色,就需要調用這個類一次:
r1 = Role('Alex','police','AK47’) #生成一個角色 , 會自動把參數傳給Role下面的__init__(...)方法
r2 = Role('Jack','terrorist','B22’) #生成一個角色
我們看到,上面的創建角色時,我們並沒有給init傳值,程序也沒未報錯,是因爲,類在調用它自己的init(…)時自己幫你給self參數賦值了,
r1 = Role('Alex','police','AK47’) #此時self 相當於 r1 , Role(r1,'Alex','police','AK47’)
r2 = Role('Jack','terrorist','B22’)#此時self 相當於 r2, Role(r2,'Jack','terrorist','B22’)
爲什麼這樣子?你拉着我說你有些猶豫,怎麼會這樣子?
你執行r1 = Role('Alex','police','AK47’)時,python的解釋器其實幹了兩件事:
- 在內存中開闢一塊空間指向r1這個變量名
- 調用Role這個類並執行其中的init(…)方法,相當於Role.init(r1,'Alex','police',’AK47’),這麼做是爲什麼呢? 是爲了把'Alex','police',’AK47’這3個值跟剛開闢的r1關聯起來,是爲了把'Alex','police',’AK47’這3個值跟剛開闢的r1關聯起來,是爲了把'Alex','police',’AK47’這3個值跟剛開闢的r1關聯起來,重要的事情說3次, 因爲關聯起來後,你就可以直接r1.name, r1.weapon 這樣來調用啦。所以,爲實現這種關聯,在調用init方法時,就必須把r1這個變量也傳進去,否則init不知道要把那3個參數跟誰關聯呀。
- 明白了麼哥?所以這個init(…)方法裏的,self.name = name , self.role = role 等等的意思就是要把這幾個值 存到r1的內存空間裏。
如果還不明白的話,哥,去測試一下智商吧, 應該不會超過70,哈哈。
爲了暴露自己的智商,此時你假裝懂了,但又問, init(…)我懂了,但後面的那幾個函數,噢 不對,後面那幾個方法 爲什麼也還需要self參數麼? 不是在初始化角色的時候 ,就已經把角色的屬性跟r1綁定好了麼?
good question, 先來看一下上面類中的一個buy_gun的方法:
def buy_gun(self,gun_name):
print(“%s has just bought %s” %(self.name,gun_name) )
上面這個方法通過類調用的話要寫成如下:
r1 = Role('Alex','police','AK47')
r1.buy_gun("B21”) #python 會自動幫你轉成 Role.buy_gun(r1,”B21")
執行結果
#Alex has just bought B21
依然沒給self傳值 ,但Python還是會自動的幫你把r1 賦值給self這個參數, 爲什麼呢? 因爲,你在buy_gun(..)方法中可能要訪問r1的一些其它屬性呀, 比如這裏就訪問 了r1的名字,怎麼訪問呢?你得告訴這個方法呀,於是就把r1傳給了這個self參數,然後在buy_gun裏調用 self.name 就相當於調用r1.name 啦,如果還想知道r1的生命值 有多少,直接寫成self.life_value就可以了。 說白了就是在調用類中的一個方法時,你得告訴人家你是誰。
好啦, 總結一下2點:
- 上面的這個r1 = Role('Alex','police','AK47’)動作,叫做類的“實例化”, 就是把一個虛擬的抽象的類,通過這個動作,變成了一個具體的對象了, 這個對象就叫做實例
- 剛纔定義的這個類體現了面向對象的第一個基本特性,封裝,其實就是使用構造方法將內容封裝到某個具體對象中,然後通過對象直接或者self間接獲取被封裝的內容
面向對象的特性:
封裝
封裝最好理解了。封裝是面向對象的特徵之一,是對象和類概念的主要特性。
封裝,也就是把客觀事物封裝成抽象的類,並且類可以把自己的數據和方法只讓可信的類或者對象操作,對不可信的進行信息隱藏。
繼承
面向對象編程 (OOP) 語言的一個主要功能就是“繼承”。繼承是指這樣一種能力:它可以使用現有類的所有功能,並在無需重新編寫原來的類的情況下對這些功能進行擴展。
通過繼承創建的新類稱爲“子類”或“派生類”。
被繼承的類稱爲“基類”、“父類”或“超類”。
繼承的過程,就是從一般到特殊的過程。
要實現繼承,可以通過“繼承”(Inheritance)和“組合”(Composition)來實現。
在某些 OOP 語言中,一個子類可以繼承多個基類。但是一般情況下,一個子類只能有一個基類,要實現多重繼承,可以通過多級繼承來實現。
繼承概念的實現方式主要有2類:實現繼承、接口繼承。
Ø 實現繼承是指使用基類的屬性和方法而無需額外編碼的能力;
Ø 接口繼承是指僅使用屬性和方法的名稱、但是子類必須提供實現的能力(子類重構爹類方法);
在考慮使用繼承時,有一點需要注意,那就是兩個類之間的關係應該是“屬於”關係。例如,Employee 是一個人,Manager 也是一個人,因此這兩個類都可以繼承 Person 類。但是 Leg 類卻不能繼承 Person 類,因爲腿並不是一個人。
抽象類僅定義將由子類創建的一般屬性和方法。
OO開發範式大致爲:劃分對象→抽象類→將類組織成爲層次化結構(繼承和合成) →用類與實例進行設計和實現幾個階段。
繼承示例
#!_*_coding:utf-8_*_
#__author__:"Alex Li"
class SchoolMember(object):
members = 0 #初始學校人數爲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):
'''析構方法'''
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()
多態
多態性(polymorphisn)是允許你將父對象設置成爲和一個或更多的他的子對象相等的技術,賦值之後,父對象就可以根據當前賦值給它的子對象的特性以不同的方式運作。簡單的說,就是一句話:允許將子類類型的指針賦值給父類類型的指針。
那麼,多態的作用是什麼呢?我們知道,封裝可以隱藏實現細節,使得代碼模塊化;繼承可以擴展已存在的代碼模塊(類);它們的目的都是爲了——代碼重用。而多態則是爲了實現另一個目的——接口重用!多態的作用,就是爲了類在繼承和派生的時候,保證使用“家譜”中任一類的實例的某一屬性時的正確調用。
Pyhon 很多語法都是支持多態的,比如 len(),sorted(), 你給len傳字符串就返回字符串的長度,傳列表就返回列表長度。
Python多態示例
#_*_coding:utf-8_*_
class Animal(object):
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):
print('%s: 喵喵喵!' %self.name)
class Dog(Animal):
def talk(self):
print('%s: 汪!汪!汪!' %self.name)
def func(obj): #一個接口,多種形態
obj.talk()
c1 = Cat('小晴')
d1 = Dog('李磊')
func(c1)
func(d1)
領域模型
好了,你現在會了面向對象的各種語法了, 那請看下本章最後的作業需求,我相信你可能是矇蔽的, 很多同學都是學會了面向對象的語法,卻依然寫不出面向對象的程序,原因是什麼呢?原因就是因爲你還沒掌握一門面向對象設計利器, 你說我讀書少別騙我, 什麼利器?
答案就是:領域建模。 從領域模型開始,我們就開始了面向對象的分析和設計過程,可以說,領域模型是完成從需求分析到面向 對象設計的一座橋樑。
領域模型,顧名思義,就是需求所涉及的領域的一個建模,更通俗的講法是業務模型。 參考百度百科(http://baike.baidu.cn/view/757895.htm ),領域模型定義如下:
從這個定義我們可以看出,領域模型有兩個主要的作用:
- 發掘重要的業務領域概念
- 建立業務領域概念之間的關係
領域建模三字經
領域模型如此重要,很多同學可能會認爲領域建模很複雜,需要很高的技巧。然而事實上領域建模非常簡 單,簡單得有點難以讓人相信,領域建模的方法概括一下就是“找名詞”! 許多同學看到這個方法後估計都會笑出來:太假了吧,這麼簡單,找個初中生都會啊,那我們公司那些分 析師和設計師還有什麼用哦?
分析師和設計師當然有用,後面我們會看到,即使是簡單的找名詞這樣的操作,也涉及到分析和提煉,而 不是簡單的摘取出來就可,這種情況下分析師和設計師的經驗和技能就能夠派上用場了。但領域模型分析 也確實相對簡單,即使沒有豐富的經驗和高超的技巧,至少也能完成一個能用的領域模型。
雖然我們說“找名詞”很簡單,但一個關鍵的問題還沒有說明:從×××? 如果你還記得領域模型是“需求到面向對象的橋樑”,那麼你肯定一下子就能想到:從需求模型中找,具 體來說就是從用例中找。
歸納一下域建模的方法就是“從用例中找名詞”。 當然,找到名詞後,爲了能夠更加符合面向對象的要求和特點,我們還需要對這些名詞進一步完善,這就 是接下來的步驟:加屬性,連關係!
最後我們總結出領域建模的三字經方法:找名詞、加屬性、連關係。
找名詞
who : 學員、講師、管理員
用例:
-
管理員 創建了 北京 和 上海 兩個校區
-
管理員 創建了 Linux \ Python \ Go 3個課程
-
管理員 創建了 北京校區的Python 16期, Go開發第一期,和上海校區的Linux 36期 班級
-
管理員 創建了 北京校區的 學員 小晴 ,並將其 分配 在了 班級 python 16期
-
管理員 創建了 講師 Alex , 並將其分配 給了 班級 python 16期 和全棧脫產5期
-
講師 Alex 創建 了一條 python 16期的 上課紀錄 Day6
-
講師 Alex 爲Day6這節課 所有的學員 批了作業 ,小晴得了A, 李磊得了C-, 嚴帥得了B
-
學員小晴 在 python 16 的 day6裏 提交了作業
-
學員李磊 查看了自己所報的所有課程
-
學員 李磊 在 查看了 自己在 py16期 的 成績列表 ,然後自殺了
- 學員小晴 跟 講師 Alex 表白了
名詞列表:
管理員、校區、課程、班級、上課紀錄、作業、成績、講師、學員
加屬性
連關係
有了類,也有了屬性,接下來自然就是找出它們的關係了。
本節作業: 選課系統
角色:學校、學員、課程、講師
要求:
- 創建北京、上海 2 所學校
- 創建linux , python , go 3個課程 , linux\py 在北京開, go 在上海開
- 課程包含,週期,價格,通過學校創建課程
- 通過學校創建班級, 班級關聯課程、講師
- 創建學員時,選擇學校,關聯班級
- 創建講師角色時要關聯學校,
-
提供兩個角色接口
- 學員視圖, 可以註冊, 交學費, 選擇班級,
- 講師視圖, 講師可管理自己的班級, 上課時選擇班級, 查看班級學員列表 , 修改所管理的學員的成績
- 管理視圖,創建講師, 創建班級,創建課程
- 上面的操作產生的數據都通過pickle序列化保存到文件裏
注:該文章由alex的blog搬運而來