面向對象的進階
包裝器:@property(getter)、@setter
之前我們討論過Python中屬性和方法訪問權限的問題,雖然我們不建議將屬性設置為私有的,但是如果直接將屬性暴露給外界也是有問題的,比如我們沒有辦法檢查賦給屬性的值是否有效。我們之前的建議是將屬性命名以單下劃線開頭,通過這種方式來暗示屬性是受保護的,不建議外界直接訪問,那么如果想訪問屬性可以通過屬性的getter(訪問器)和setter(修改器)方法進行對應的操作。如果要做到這點,就可以考慮使用@property包裝器來包裝getter和setter方法,使得對屬性的訪問既安全又方便
__ slots __方法
如果需要限定自定義類型的對象只能綁定某些屬性,可以通過在類中定義slots變量來進行限定。需要注意的是slots的限定只對當前類的對象生效,對子類并不起任何作用
class Person(object):
# 限定Person對象只能綁定_name, _age和_gender屬性
__slots__ = ('_name', '_age', '_gender')
def __init__(self, name, age):
self._name = name
self._age = age
@property
def name(self):
return self._name
@property
def age(self):
return self._age
@age.setter
def age(self, age):
self._age = age
def play(self):
if self._age <= 16:
print('%s正在玩飛行棋.' % self._name)
else:
print('%s正在玩斗地主.' % self._name)
def main():
person = Person('王大錘', 22)
person.play()
person._gender = '男'
# AttributeError: 'Person' object has no attribute '_is_gay'
# person._is_gay = True
靜態方法和類方法
靜態方法:@staticmethod
類方法:@classmethod
類方法的第一個參數約定名為cls,它代表的是當前類相關的信息的對象(類本身也是一個對象,有的地方也稱之為類的元數據對象),通過這個參數我們可以獲取和類相關的信息并且可以創建出類的對象
from math import sqrt
class Triangle(object):
def __init__(self, a, b, c):
self._a = a
self._b = b
self._c = c
@staticmethod
def is_valid(a, b, c):
return a + b > c and b + c > a and a + c > b
def perimeter(self):
return self._a + self._b + self._c
def area(self):
half = self.perimeter() / 2
return sqrt(half * (half - self._a) *
(half - self._b) * (half - self._c))
def main():
a, b, c = 3, 4, 5
# 靜態方法和類方法都是通過給類發消息來調用的
if Triangle.is_valid(a, b, c):
t = Triangle(a, b, c)
print(t.perimeter())
# 也可以通過給類發消息來調用對象方法但是要傳入接收消息的對象作為參數
# print(Triangle.perimeter(t))
print(t.area())
# print(Triangle.area(t))
else:
print('無法構成三角形.')
if __name__ == '__main__':
main()
from time import time, localtime, sleep
class Clock(object):
"""數字時鐘"""
def __init__(self, hour=0, minute=0, second=0):
self._hour = hour
self._minute = minute
self._second = second
@classmethod
def now(cls):
ctime = localtime(time())
return cls(ctime.tm_hour, ctime.tm_min, ctime.tm_sec)
def run(self):
"""走字"""
self._second += 1
if self._second == 60:
self._second = 0
self._minute += 1
if self._minute == 60:
self._minute = 0
self._hour += 1
if self._hour == 24:
self._hour = 0
def show(self):
"""顯示時間"""
return '%02d:%02d:%02d' % \
(self._hour, self._minute, self._second)
def main():
# 通過類方法創建對象并獲取系統時間
clock = Clock.now()
while True:
print(clock.show())
sleep(1)
clock.run()
if __name__ == '__main__':
main()
類之間的關系
1:is-a關系也叫繼承或泛化,比如學生和人的關系
2:has-a關系通常稱之為關聯,比如汽車和引擎的關系都屬于關聯關系;關聯關系如果是整體和部分的關聯,那么我們稱之為聚合關系;如果整體進一步負責了部分的生命周期(整體和部分是不可分割的,同時同在也同時消亡),那么這種就是最強的關聯關系,我們稱之為合成關系。
3:use-a關系通常稱之為依賴,比如司機有一個駕駛的行為,其中使用到了汽車,那么司機和汽車的關系就是依賴關系
繼承和多態
在已有類的基礎上創建新類,這其中的一種做法就是讓一個類從另一個類那里將屬性和方法直接繼承下來,從而減少重復代碼的編寫。提供繼承信息的我們稱之為父類,也叫超類或基類;得到繼承信息的我們稱之為子類,也叫派生類或衍生類。子類除了繼承父類提供的屬性和方法,還可以定義自己特有的屬性和方法,所以子類比父類擁有的更多的能力,在實際開發中,我們經常會用子類對象去替換掉一個父類對象,這是面向對象編程中一個常見的行為,對應的原則稱之為里式替換原則,
子類在繼承了父類的方法后,可以對父類已有的方法給出新的實現版本,這個動作稱之為方法重寫(override)。通過方法重寫我們可以讓父類的同一個行為在子類中擁有不同的實現版本,當我們調用這個經過子類重寫的方法時,不同的子類對象會表現出不同的行為,這個就是多態
補充
函數的參數
必選參數
默認值
可變參數(args)
關鍵字參數(*kwargs)
命名關鍵字參數
def f1(a,b,c=0,*args,**kw):
print(a,b,c,args,kw)
#調用情況:
f1(1,2)
f1(1,2,c=4)
f1(1,3,6,'aw','wad',x=123,y='1232')
其中:
a,b 為必選參數
c=0 為默認參數
*args 為可變參數,可變參數允許你傳入 0個或任意個參數,這些可變參數在函數調用時自動組裝為一個tuple
**kw 為關鍵字參數,關鍵字參數允許你傳入 0個或任意個含參數名的參數,這些關鍵字參數在函數內部自動組裝為一個dict
def person(name,age,*,city='hongkong',job='coder'):
print(name,age,city,job)
person('scofff',212,city='homy',job='eatter')
后面的兩個參數為命名關鍵字參數
對于關鍵字參數,函數的調用者可以傳入任意不受限制的關鍵字參數,至于到底傳入了哪些,就需要在函數內部通過 kw 檢查。
與關鍵字參數**kw不同,命名關鍵字參數需要一個特殊分隔符*,*后面的參數被視為命名關鍵字參數,如果沒有*號,那么后面的參數將被視為普通的未位置參數。
命名關鍵字參數必須傳入參數名,而命名關鍵字參數可以有缺省值,這和位置參數不同。
面向對象的原則
單一職責原則
單一職責原則的定義是就一個類而言,應該僅有一個引起他變化的原因。也就是說一個類應該只負責一件事情。如果一個類負責了方法M1,方法M2兩個不同的事情,當M1方法發生變化的時候,我們需要修改這個類的M1方法,但是這個時候就有可能導致M2方法不能工作。這個不是我們期待的,但是由于這種設計卻很有可能發生。所以這個時候,我們需要把M1方法,M2方法單獨分離成兩個類。讓每個類只專心處理自己的方法。
單一職責原則的好處如下:
1):可以降低類的復雜度,一個類只負責一項職責,這樣邏輯也簡單很多
2):提高類的可讀性,和系統的維護性,因為不會有其他奇怪的方法來干擾我們理解這個類的含義
3):當發生變化的時候,能將變化的影響降到最小,因為只會在這個類中做出修改
開閉原則
開閉原則的定義是軟件中的對象(類,模塊,函數等)應該對于擴展是開放的,但是對于修改是關閉的。
當需求發生改變的時候,我們需要對代碼進行修改,這個時候我們應該盡量去擴展原來的代碼,而不是去修改原來的代碼,因為這樣可能會引起更多的問題。
這個準則和單一職責原則一樣,是一個大家都這樣去認為但是又沒規定具體該如何去做的一種原則。
開閉原則我們可以用一種方式來確保他,我們用抽象去構建框架,用實現擴展細節。這樣當發生修改的時候,我們就直接用抽象了派生一個具體類去實現修改
里氏替換原則
子類可以去擴展父類的功能,但是不能改變父類原有的功能
1.子類可以實現父類的抽象方法,但是不能覆蓋父類的非抽象方法。
2.子類可以增加自己獨有的方法。
3.當子類的方法重載父類的方法時候,方法的形參要比父類的方法的輸入參數更加寬松。
4.當子類的方法實現父類的抽象方法時,方法的返回值要比父類更嚴格。
合成聚合復用原則
合成/聚合復用原則經常又叫做合成復用原則。該原則就是在一個新的對象里面使用一些已有的對象,使之成為新對象的一部分:新的對象通過向這些對象的委派達到復用已有功能的目的
迪米特法則
迪米特原則也被稱為最小知識原則
定義為一個對象應該對其他對象保持最小的了解。
因為類與類之間的關系越密切,耦合度越大,當一個類發生改變時,對另一個類的影響也越大,所以這也是我們提倡的軟件編程的總的原則:低耦合,高內聚。
使用總結:
(1).在類的劃分上,應當盡量創建松耦合的類,類之間的耦合度越低,就越有利于復用,一個處在松耦合中的類一旦被修改,不會對關聯的類造成太大波及;
(2).在類的結構設計上,每一個類都應當盡量降低其成員變量和成員函數的訪問權限;
(3).在類的設計上,只要有可能,一個類型應當設計成不變類;
(4).在對其他類的引用上,一個對象對其他對象的引用應當降到最低。