為什麼Python不用設計模式?

碼農翻身2019-03-22 01:11:38

在遙遠的Python王國,有一位少年,非常熱愛編程,他的父母想給他報一個班,問了萬能的朋友圈以後,發現大家都推薦同一個老師,人稱吉先生。 


於是他的父母毫不猶豫就交了一筆不菲的學費,每週六日下午讓孩子去學習。 


少年學習非常刻苦,很快就學會了Python語法、工具和框架。 


老師像是見到了可以雕刻的美玉, 傾囊相授,告訴他不僅要把代碼寫對,還要讓代碼漂亮、優雅、可讀、可維護。 


少年又學會了單元測試,TDD,重構,努力讓自己的代碼達到老師所要求的標準。 


他還把“Python 之禪”貼在了自己的牆上,經常對照自己的代碼,從來都不敢違反。 


The Zen of Python, by Tim Peters 


Beautiful is better than ugly. 

Explicit is better than implicit. 

Simple is better than complex. 

Complex is better than complicated. 

Flat is better than nested. 

Sparse is better than dense. 

Readability counts. 

......


三年以後,少年以為自己成為了Python的大師,直到有一天,老師給他佈置了一個大作業,其實是個大項目,業務非常複雜。


少年通宵達旦地編程,可他悲慘地發現,無論他怎麼努力,他的代碼都是亂糟糟的,沒有美感,他所寫出的類,模塊混成了一團。 


於是他只好去請教老師: “老師,我的Python和Flask框架已經用得滾瓜爛熟了,為什麼完成不了這個項目呢?” 


老師說:“孩子,原來你只需要把框架的類給import進來,稍微寫點兒代碼就行了,現在你需要自己去設計類,自己去做出抽象了!” 


 “怎麼設計呢?” 


“為師送你一本古書,《設計模式》 ,你回去好好看看吧。”



少年如獲至寶, 廢寢忘食地去研究這本20多年前出的、泛黃的古書,還是用C++描述的。  


他看得雲裡霧裡,似乎明白,又似乎不明白,只好再去請教老師。 


這一次,老師給了他另外一本書, 《Head First 設計模式》 



少年翻開一看,這本書是用Java寫的,於是又一頭扎到了Java語言當中。 


這本書比較通俗易懂,少年看得大呼過癮。 


終於,他信心十足地用Python開始那個大項目了。 


他用Python語言實現設計模式,解決一些設計問題,可是總覺得不對勁,和Java , C++相比,感覺怪怪的。 


另外他感覺到了動態語言的不爽之處,每次想重構的時候,總是不敢下手,他把困惑給老師說了。 


老師笑道:“我在Java王國的時候,人們總是說‘動態一時爽,重構火葬場’, 現在你體會到了吧!” 


“Java就能避免這個問題嗎?”  


“Java是一門靜態語言,變量類型一旦確定就不能改變,對重構的支持非常好,你有沒有興趣去看看?那裡有很多的框架,像Spring,Spring Boot,MyBatis, Dubbo, Netty,非常繁榮發達。” 


少年心生嚮往,於是老師就給他寫了個條子,告訴他說到了Java王國,找到IO大臣,一切事情都會暢通無阻。  


少年辭別老師,奔向了Java帝國,老師整了整衣冠, 望著東方Java帝國的方向,莊嚴地拜了三拜:“五年了,IO大人,我沒有辜負您的重託,又忽悠了一個人去做Java了!”  


原來這位老師就是吉森! IO大臣派來傳播Java文化和價值觀的傳教士,入境後不幸被識破,軟禁在了Python王國。 


吉森的故事請移步《Java帝國對Python的滲透能成功嗎?》  


Python沒有接口?


Python國王收到邊關的奏報,說是最近有不少年輕人奔向了Java王國,不知道是不是國內政策有變,導致人心浮動。 


Python國王震怒,下令嚴查。 查來查去,所有的線索都指向了一個人:吉森。 


這一天,Python特使帶著士兵來到了吉森的住所,果然發現他又在忽悠年輕人了。 


特使又氣又笑:“你學了半吊子的Python,居然敢來蠱惑人心,實在是可笑。” 


吉森看到自己的計謀已被識破,依然很鎮靜:“大人誤會了,我教的就是正宗的面向對象的設計和設計模式啊,這設計模式用Python實現起來很彆扭,我就推薦他們去學Java啊。”  


“胡說,Python寫設計模式怎麼會很彆扭? Java 由於語法所限,表達能力比較弱,對於一些問題,只好用笨拙的設計模式來解決,我們Python有可能在語法層面就解決問題了!” 


“那你說說,設計模式的原則是什麼?” 吉森問道。 


1. 面向接口編程,而不是面向實現編程。2. 優先使用組合而不是繼承。” 這是難不住特使的。


 “Python連接口都沒有,怎麼面向接口編程?”  吉森問道。 


特使哈哈大笑:“說你是半吊子吧,你還不服,你以為這裡的接口就是你們Java的interface啊!你忘了Python的Duck Typing了?” 


class Duck:
    def fly(self):
        print("Duck flying")

class Airplane:
    def fly(self):
        print("Airplane flying")


def lift_off(entity):
    entity.fly()


duck = Duck()
plane = Airplane()

lift_off(duck)
lift_off(plane)


“看到沒有, Duck和Airplane都沒有實現你所謂的接口,但是都可以調用fly()方法,這難道不是面向接口編程, 如果你非要類比的話,這個fly就是一個自動化的接口啊。” 


吉森確實沒想到這一層,至於第二個原則,優先使用組合而不是繼承,可以是每個面向對象的語言都可以實現的,他嘆了口氣,也就不問了。 


Adapter模式


特使接著說:“Duck Typing非常強大,你不是提到了設計模式嗎,在Duck Typing面前,很多設計模式純屬多此一舉。我來給你舉個例子,Adapter模式。假設客戶端有這麼一段代碼,可以把一段日誌寫入文件當中。” 


def log(file,msg):
    file.write('[{}] - {}'.format(datetime.now(), msg))


“現在來了新的需求,要把日誌寫入數據庫, 而數據庫並沒有write 方法,怎麼辦? 那就寫個Adapter吧。” 


class DBAdapter:
    def __init__(self, db):
        self.db = db

    def write(self, msg):
        self.db.insert(msg)


“注意這個DBAdapter並不需要實現什麼接口(我大Python也沒有接口),就是一個單獨的類,只需要有個write方法就可以了。” 


db_adapter = DBAdapter(db)
log(db_adapter, "sev1 error occurred")


確實是很簡單,只要有write 方法, 不管你是任何對象,都可以進行調用, 典型的Duck Typing 。   


既然Adapter可以這麼寫,那Proxy模式也是類似了,只要你的Proxy類和被代理的類的方法一樣,那就可以被客戶使用。 


但是這種方法的弊端就是,不知道log方法的參數類型,想要重構可就難了。 


單例模式


吉森又想到了一個問題,繼續挑戰特使:“Python連個private 關鍵字都沒有,怎麼隱藏一個類的構造函數,怎麼去實現單例?” 


特使不屑地說:“忘掉你那套Java思維吧,在Python中想寫個singleton有很多辦法,我給你展示一個比較Python的方式,用module的方式來實現。”


#singleton.py

class Singleton:
    def __init__(self):
        self.name = "i'm singleton"

instance = Singleton()

del Singleton  # 把構造函數刪除


使用Singleton:


import singleton

print(singleton.instance.name)  # i'm singleton

instance = Singleton() # NameError: name 'Singleton' is not defined


吉森確實沒有想到這種寫法,利用Python的module來實現信息的隱藏。


Visitor模式


不是每個設計模式都能這麼幹吧?  吉森心中暗想,他腦海中浮現了一個難於理解的模式:Visitor,自己當初為了學習它可是下了苦工。 


吉森說:“那你說說,對於Visitor,怎麼利用Python的特色?” 


“我知道你心裡想的是什麼,無非就是想讓我寫一個類,然後在寫個Visitor對它進行訪問,是不是?” 


class TreeNode:
    def __init__(self, data):
        self.data = data
        self.left = None
        self.right = None
    def accept(self, visitor):
        if self.left is not None:
            self.left.accept(visitor)

        visitor.visit(self)

        if self.right is not None:
            self.right.accept(visitor)

class PrintVisitor:
    def visit(self,node):
        print(node.data)

root = TreeNode('1')
root.left = TreeNode('2')
root.right = TreeNode('3')

visitor = PrintVisitor()

root.accept(visitor)   #輸出2, 1, 3


吉森說:“是啊, 難道Visitor模式不是這麼寫的嗎? ”


"我就說你的Python只是學了點皮毛吧,Visitor的本質是在分離結構和操作, 在Python中使用generator可以更加優雅地實現。” 


class TreeNode:

    def __iter__(self):
        return self.__generator()

    def __generator(self):
        if self.left is not None:
            yield from iter(self.left) 
        yield from self.data

        if self.right is not None:
            yield from iter(self.right) 

root = TreeNode('1')
root.left = TreeNode('2')
root.right = TreeNode('3')

for ele in root:
    print(ele)


不得不承認,這種方式使用起來更加簡潔,同時達到了結構和操作進行分離的目的。 


特使說道: “看到了吧,Python在語言層面對一些模式提供了支持,所以很多設計模式在我大Python看起來非常笨拙,我們這裡並不提倡,當然我們還是要掌握面向對象設計的原則SOLID和設計模式的思想,發現變化並且封裝變化,這樣才能寫出優雅的程序出來。” 


吉森嘆了一口氣,感慨自己學藝不精,不再反抗,束手就擒。 


尾聲


Python王國審判了吉森,本來要判他死刑,但是Java帝國重兵壓境,要求釋放,否則就開戰。 


吉森被送回Java王國,成為了人們心目中的英雄,回家他仔細對比了Java和Python,在Java虛擬機上把Python語言給實現了!國王為了表彰他的英勇事蹟,把這個語言叫做Jython。


ps. 本文故事受到了這個視頻的啟發:https://www.youtube.com/watch?v=G5OeYHCJuv0



能認真看到這裡,說明是碼農翻身的鐵桿支持者,送出5本《Head First 設計模式》,簽名版+碼農翻身專屬印章(再次感謝讀者@Aries的辛苦雕刻)。


書只有五本,只有靠手氣了, 在公眾號回覆消息“設計模式抽獎”,即可進行抽獎。 

https://weiwenku.net/d/110025684