?
?
抽象類與接口類
?
接口類
?
繼承有兩種用途:
?
一:繼承基類的方法,并且做出自己的改變或者擴展(代碼重用) ?
?
二:聲明某個子類兼容于某基類,定義一個接口類Interface,接口類中定義了一些接口名(就是函數名)且并未實現接口的功能,子類繼承接口類,并且實現接口中的功能
# 一:這樣不好,我要統一一下支付的規則.class QQpay:def pay(self,money):print('使用qq支付%s元' % money)class Alipay:def pay(self,money):print('使用阿里支付%s元' % money)a = Alipay()
a.pay(100)b = QQpay()
b.pay(200)# 二,統一支付的規則 歸一化設計,統一 pay接口
class QQpay:def pay(self,money):print('使用qq支付%s元' % money)class Alipay:def pay(self,money):print('使用阿里支付%s元' % money)def pay(obj,money):obj.pay(money)a = Alipay()
b = QQpay()pay(a,100)
pay(b,200)# 三,但是,來了一個野生程序員,他不知道你的約定俗成的規則,就會出問題class QQpay:def pay(self,money):print('使用qq支付%s元' % money)class Alipay:def pay(self,money):print('使用阿里支付%s元' % money)class Wechatpay:def fuqian(self,money):print('使用微信支付%s元' % money)def pay(obj,money):obj.pay(money)a = Alipay()
b = QQpay()pay(a,100)
pay(b,200)c = Wechatpay()
c.fuqian(300)# 四,解決方式
# 定義一個父類,什么都不寫,只是要求繼承我的所有類有一個pay方法,這樣就制定了一個規范,這就叫做接口類,后者抽象類.
class Payment:def pay(self):passclass QQpay(Payment):def pay(self,money):print('使用qq支付%s元' % money)class Alipay(Payment):def pay(self,money):print('使用阿里支付%s元' % money)class Wechatpay(Payment):def fuqian(self,money):print('使用微信支付%s元' % money)def pay(obj,money):obj.pay(money)a = Alipay()
b = QQpay()pay(a,100)
pay(b,200)c = Wechatpay()
c.fuqian(300)#五,他還是不知道看你這些都繼承了一個類,所以你要制定一個規范,強制他執行.
# 創建一個規范
from abc import ABCMeta,abstractmethod
class Payment(metaclass=ABCMeta): # 抽象類 接口類 規范和約束 metaclass指定的是一個元類
@abstractmethoddef pay(self):pass # 抽象方法class Alipay(Payment):def pay(self,money):print('使用支付寶支付了%s元'%money)class QQpay(Payment):def pay(self,money):print('使用qq支付了%s元'%money)class Wechatpay(Payment):# def pay(self,money):# print('使用微信支付了%s元'%money)def recharge(self):passdef pay(a,money):a.pay(money)a = Alipay()
a.pay(100)
pay(a,100) # 歸一化設計:不管是哪一個類的對象,都調用同一個函數去完成相似的功能
q = QQpay()
q.pay(100)
pay(q,100)
w = Wechatpay()
pay(w,100) # 到用的時候才會報錯# 抽象類和接口類做的事情 :建立規范
# 制定一個類的metaclass是ABCMeta,
# 那么這個類就變成了一個抽象類(接口類)
# 這個類的主要功能就是建立一個規范
實踐中,繼承的第一種含義意義并不很大,甚至常常是有害的。因為它使得子類與基類出現強耦合。
繼承的第二種含義非常重要。它又叫“接口繼承”。
接口繼承實質上是要求“做出一個良好的抽象,這個抽象規定了一個兼容接口,使得外部調用者無需關心具體細節,可一視同仁的處理實現了特定接口的所有對象”——這在程序設計上,叫做歸一化。
歸一化使得高層的外部使用者可以不加區分的處理所有接口兼容的對象集合——就好象linux的泛文件概念一樣,所有東西都可以當文件處理,不必關心它是內存、磁盤、網絡還是屏幕(當然,對底層設計者,當然也可以區分出“字符設備”和“塊設備”,然后做出針對性的設計:細致到什么程度,視需求而定)。
依賴倒置原則:
高層模塊不應該依賴低層模塊,二者都應該依賴其抽象;抽象不應該應該依賴細節;細節應該依賴抽象。換言之,要針對接口編程,而不是針對實現編程
在python中根本就沒有一個叫做interface的關鍵字,上面的代碼只是看起來像接口,其實并沒有起到接口的作用,子類完全可以不用去實現接口?,如果非要去模仿接口的概念,可以借助第三方模塊:
http://pypi.python.org/pypi/zope.interface
twisted的twisted\internet\interface.py里使用zope.interface
文檔https://zopeinterface.readthedocs.io/en/latest/
設計模式:https://github.com/faif/python-patterns
接口提取了一群類共同的函數,可以把接口當做一個函數的集合。然后讓子類去實現接口中的函數。這么做的意義在于歸一化,什么叫歸一化,就是只要是基于同一個接口實現的類,那么所有的這些類產生的對象在使用時,從用法上來說都一樣。歸一化,讓使用者無需關心對象的類是什么,只需要的知道這些對象都具備某些功能就可以了,這極大地降低了使用者的使用難度。比如:我們定義一個動物接口,接口里定義了有跑、吃、呼吸等接口函數,這樣老鼠的類去實現了該接口,松鼠的類也去實現了該接口,由二者
分別產生一只老鼠和一只松鼠送到你面前,即便是你分別不到底哪只是什么鼠你肯定知道他倆都會跑,都會吃,都能呼吸。再比如:我們有一個汽車接口,里面定義了汽車所有的功能,然后由本田汽車的類,奧迪汽車的類,大眾汽車的類,他們都實現了汽車接口,
這樣就好辦了,大家只需要學會了怎么開汽車,那么無論是本田,還是奧迪,還是大眾我們都會開了,開的時候根本無需關心我開的是哪一類車,
操作手法(函數調用)都一樣
抽象類
什么是抽象類
? ? 與java一樣,python也有抽象類的概念但是同樣需要借助模塊實現,抽象類是一個特殊的類,它的特殊之處在于只能被繼承,不能被實例化
為什么要有抽象類
??? 如果說類是從一堆對象中抽取相同的內容而來的,那么抽象類就是從一堆類中抽取相同的內容而來的,內容包括數據屬性和函數屬性。
比如我們有香蕉的類,有蘋果的類,有桃子的類,從這些類抽取相同的內容就是水果這個抽象的類,你吃水果時,要么是吃一個具體的香蕉,要么是吃一個具體的桃子。。。。。。你永遠無法吃到一個叫做水果的東西。
? ? 從設計角度去看,如果類是從現實對象抽象而來的,那么抽象類就是基于類抽象而來的。
從實現角度來看,抽象類與普通類的不同之處在于:抽象類中有抽象方法,該類不能被實例化,只能被繼承,且子類必須實現抽象方法。這一點與接口有點類似,但其實是不同的,即將揭曉答案
抽象類與接口類
抽象類的本質還是類,指的是一組類的相似性,包括數據屬性(如all_type)和函數屬性(如read、write),而接口只強調函數屬性的相似性。
抽象類是一個介于類和接口直接的一個概念,同時具備類和接口的部分特性,可以用來實現歸一化設計?
在python中,并沒有接口類這種東西,即便不通過專門的模塊定義接口,我們也應該有一些基本的概念。
1.多繼承問題
在繼承抽象類的過程中,我們應該盡量避免多繼承;
而在繼承接口的時候,我們反而鼓勵你來多繼承接口
接口隔離原則:
使用多個專門的接口,而不使用單一的總接口。即客戶端不應該依賴那些不需要的接口。
2.方法的實現
在抽象類中,我們可以對一些抽象方法做出基礎實現;
而在接口類中,任何方法都只是一種規范,具體的功能需要子類實現
多態
Pyhon不支持Java和C#這一類強類型語言中多態的寫法,但是原生多態,其Python崇尚“鴨子類型”。
class F1:passclass S1(F1):def show(self):print 'S1.show'class S2(F1):def show(self):print 'S2.show'# 由于在Java或C#中定義函數參數時,必須指定參數的類型
# 為了讓Func函數既可以執行S1對象的show方法,又可以執行S2對象的show方法,所以,定義了一個S1和S2類的父類
# 而實際傳入的參數是:S1對象和S2對象def Func(F1 obj):"""Func函數需要接收一個F1類型或者F1子類的類型"""print obj.show()s1_obj = S1()
Func(s1_obj) # 在Func函數中傳入S1類的對象 s1_obj,執行 S1 的show方法,結果:S1.show
s2_obj = S2()
Func(s2_obj) # 在Func函數中傳入Ss類的對象 ss_obj,執行 Ss 的show方法,結果:S2.show
Python偽代碼實現Java或C#的多態
class F1:passclass S1(F1):def show(self):print 'S1.show'class S2(F1):def show(self):print 'S2.show'def Func(obj):print obj.show()s1_obj = S1()
Func(s1_obj) s2_obj = S2()
Func(s2_obj) Python “鴨子類型”
封裝
封裝,顧名思義就是將內容封裝到某個地方,以后再去調用被封裝在某處的內容。
所以,在使用面向對象的封裝特性時,需要:
- 將內容封裝到某處
- 從某處調用被封裝的內容
第一步:將內容封裝到某處
self 是一個形式參數,當執行 obj1 = Foo('wupeiqi', 18 ) 時,self 等于 obj1
? ? ? ? ? ? ? ? ? ? ? ? ? ? ??當執行 obj2 = Foo('alex', 78 ) 時,self 等于 obj2
所以,內容其實被封裝到了對象 obj1 和 obj2 中,每個對象中都有 name 和 age 屬性,在內存里類似于下圖來保存。
?
第二步:從某處調用被封裝的內容
調用被封裝的內容時,有兩種情況:
- 通過對象直接調用
- 通過self間接調用
1、通過對象直接調用被封裝的內容
上圖展示了對象 obj1 和 obj2 在內存中保存的方式,根據保存格式可以如此調用被封裝的內容:對象.屬性名
1 class Foo:
2
3 def __init__(self, name, age):
4 self.name = name
5 self.age = age
6
7 obj1 = Foo('wupeiqi', 18)
8 print obj1.name # 直接調用obj1對象的name屬性
9 print obj1.age # 直接調用obj1對象的age屬性
10
11 obj2 = Foo('alex', 73)
12 print obj2.name # 直接調用obj2對象的name屬性
13 print obj2.age # 直接調用obj2對象的age屬性
2、通過self間接調用被封裝的內容
執行類中的方法時,需要通過self間接調用被封裝的內容
class Foo:def __init__(self, name, age):self.name = nameself.age = agedef detail(self):print self.nameprint self.ageobj1 = Foo('wupeiqi', 18)
obj1.detail() # Python默認會將obj1傳給self參數,即:obj1.detail(obj1),所以,此時方法內部的 self = obj1,即:self.name 是 wupeiqi ;self.age 是 18
obj2 = Foo('alex', 73)
obj2.detail() # Python默認會將obj2傳給self參數,即:obj1.detail(obj2),所以,此時方法內部的 self = obj2,即:self.name 是 alex ; self.age 是 78
綜上所述,對于面向對象的封裝來說,其實就是使用構造方法將內容封裝到 對象 中,然后通過對象直接或者self間接獲取被封裝的內容。
?