」面向接口編程「
寫 Java
的朋友耳朵已經能夠聽出幹繭了吧,固然這個思想在 Java
中很是重要,甚至幾乎全部的編程語言都須要,畢竟程序具備良好的擴展性、維護性誰都不能拒絕。python
最近無心間看到了我剛開始寫 Python
時的部分代碼,當時實現的需求有個很明顯的特色:編程
說人話就是商戶須要接入平臺,接入的步驟相同,但具體實現不一樣。markdown
做爲一個」資深「 Javaer
,需求還沒看完我就洋洋灑灑的把各個實現類寫好了:編程語言
固然最終也順利實現需求,甚至把組裏一個沒寫過 Java
的大哥唬的一愣一愣的,直呼牛逼。ui
不過過後也給我吐槽:spa
截止目前 Python
寫多了,我總算是能總結他的感覺:就是不夠 Pythonic
。翻譯
雖然說 Python
沒有相似 Java
這樣的 Interface
特性,但做爲面向對象的高級語言也是支持繼承的;設計
在這裏咱們也能夠利用繼承的特性來實現面向接口編程:3d
class Car:
def run(self):
pass
class Benz(Car):
def run(self):
print("benz run")
class BMW(Car):
def run(self):
print("bwm run")
def run(car):
car.run()
if __name__ == "__main__":
benz = Benz()
bmw = BMW()
run(benz)
run(bmw)
複製代碼
代碼很是簡單,在 Python
中也沒有相似於 Java
中的 extends
關鍵字,只須要在類聲明末尾用括號包含基類便可。code
這樣在每一個子類中就能單獨實現業務邏輯,方便擴展和維護。
因爲 Python
做爲一個動態類型語言,沒法作到 Java
那樣在編譯期間校驗一個類是否徹底實現了某個接口的全部方法。
爲此 Python
提供瞭解決辦法,那就是 abc(Abstract Base Classes)
,當咱們將基類用 abc
聲明時就能近似作到:
import abc
class Car(abc.ABC):
@abc.abstractmethod
def run(self):
pass
class Benz(Car):
def run(self):
print("benz run")
class BMW(Car):
pass
def run(car):
car.run()
if __name__ == "__main__":
benz = Benz()
bmw = BMW()
run(benz)
run(bmw)
複製代碼
一旦有類沒有實現方法時,運行期間便會拋出異常:
bmw = BMW()
TypeError: Can't instantiate abstract class BMW with abstract methods run 複製代碼
雖然沒法作到在運行以前(畢竟不須要編譯)進行校驗,但有總比沒有好。
以上兩種方式看似已經畢竟優雅的實現面向接口編程了,但實際上也不夠 Pythonic
。
在繼續以前咱們先聊聊接口
的本質究竟是什麼?
在 Java
這類靜態語言中面向接口編程是比較麻煩的,也就是咱們常說的子類向父類轉型,所以須要編寫額外的代碼。
帶來的好處也是顯而易見,只須要父類即可運行。
但咱們也沒必要過於執着於接口,它自己只是一個協議、規範,並不特指 Java
中的 Interface
,甚至有些語言壓根沒有這個關鍵字。
動態語言的特性也不須要強制校驗是否實現了方法。
在 Python
中咱們能夠利用鴨子類型來優雅的實現面向接口編程。
在這以前先了解下鴨子類型,借用維基百科的說法:
我用大白話翻譯下就是:
即使兩個徹底不想幹的類,若是他們都實現了相同的方法,那就能夠把他們當作同一類型的類來使用。
舉個簡單例子:
class Order:
def create(self):
pass
class User:
def create(self):
pass
def create(obj):
obj.create()
if __name__ == "__main__":
order = Order()
user = User()
create(order)
create(user)
複製代碼
這裏的 order
和 user
自己徹底沒有關係,只是他們都有相同方法,又得益於動態語言無法校驗類型的特色,因此徹底能夠在運行的時候認爲他們是同一種類型。
所以基於鴨子類型,以前的代碼咱們能夠稍做簡化:
class Car:
def run(self):
pass
class Benz:
def run(self):
print("benz run")
class BMW:
def run(self):
print("bwm run")
def run(car):
car.run()
if __name__ == "__main__":
benz = Benz()
bmw = BMW()
run(benz)
run(bmw)
複製代碼
由於在鴨子類型中咱們在乎的是它的行爲,而不是他們的類型;因此徹底能夠不用繼承即可以實現面向接口編程。
我以爲平時沒有接觸過動態類型語言的朋友,在瞭解完這些以後會發現新大陸,就像是 Python
老手第一次使用 Java
時;雖然以爲語法囉嗦,但也會羨慕它的類型檢查、參數驗證這類特色。
動靜語言之爭這裏不作討論了,各有各的好,鞋好很差穿只有本身知道。
隨便提一下其實不止動態語言具有鴨子類型,有些靜態語言也能玩這個騷操做,感興趣下次再介紹。