為什么Python不用設(shè)計模式?
在遙遠的Python王國,有一位少年,非常熱愛編程,他的父母想給他報一個班,問了***的朋友圈以后,發(fā)現(xiàn)大家都推薦同一個老師,人稱吉先生。
于是他的父母毫不猶豫就交了一筆不菲的學(xué)費,每周六日下午讓孩子去學(xué)習(xí)。
少年學(xué)習(xí)非??炭?,很快就學(xué)會了Python語法、工具和框架。
老師像是見到了可以雕刻的美玉, 傾囊相授,告訴他不僅要把代碼寫對,還要讓代碼漂亮、優(yōu)雅、可讀、可維護。
少年又學(xué)會了單元測試,TDD,重構(gòu),努力讓自己的代碼達到老師所要求的標準。
他還把“Python 之禪”貼在了自己的墻上,經(jīng)常對照自己的代碼,從來都不敢違反。
- 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的大師,直到有一天,老師給他布置了一個大作業(yè),其實是個大項目,業(yè)務(wù)非常復(fù)雜。
少年通宵達旦地編程,可他悲慘地發(fā)現(xiàn),無論他怎么努力,他的代碼都是亂糟糟的,沒有美感,他所寫出的類,模塊混成了一團。
于是他只好去請教老師: “老師,我的Python和Flask框架已經(jīng)用得滾瓜爛熟了,為什么完成不了這個項目呢?”
老師說:“孩子,原來你只需要把框架的類給import進來,稍微寫點兒代碼就行了,現(xiàn)在你需要自己去設(shè)計類,自己去做出抽象了!”
“怎么設(shè)計呢?”
“為師送你一本古書,《設(shè)計模式》 ,你回去好好看看吧。”
少年如獲至寶, 廢寢忘食地去研究這本20多年前出的、泛黃的古書,還是用C++描述的。
他看得云里霧里,似乎明白,又似乎不明白,只好再去請教老師。
這一次,老師給了他另外一本書, 《Head First 設(shè)計模式》
少年翻開一看,這本書是用Java寫的,于是又一頭扎到了Java語言當中。
這本書比較通俗易懂,少年看得大呼過癮。
終于,他信心十足地用Python開始那個大項目了。
他用Python語言實現(xiàn)設(shè)計模式,解決一些設(shè)計問題,可是總覺得不對勁,和Java , C++相比,感覺怪怪的。
另外他感覺到了動態(tài)語言的不爽之處,每次想重構(gòu)的時候,總是不敢下手,他把困惑給老師說了。
老師笑道:“我在Java王國的時候,人們總是說‘動態(tài)一時爽,重構(gòu)火葬場’, 現(xiàn)在你體會到了吧!”
“Java就能避免這個問題嗎?”
“Java是一門靜態(tài)語言,變量類型一旦確定就不能改變,對重構(gòu)的支持非常好,你有沒有興趣去看看?那里有很多的框架,像Spring,Spring Boot,MyBatis, Dubbo, Netty,非常繁榮發(fā)達。”
少年心生向往,于是老師就給他寫了個條子,告訴他說到了Java王國,找到IO大臣,一切事情都會暢通無阻。
少年辭別老師,奔向了Java帝國,老師整了整衣冠, 望著東方Java帝國的方向,莊嚴地拜了三拜:“五年了,IO大人,我沒有辜負您的重托,又忽悠了一個人去做Java了!”
原來這位老師就是吉森! IO大臣派來傳播Java文化和價值觀的傳教士,入境后不幸被識破,軟禁在了Python王國。
吉森的故事請移步《Java帝國對Python的滲透能成功嗎?》
Python沒有接口?
Python國王收到邊關(guān)的奏報,說是最近有不少年輕人奔向了Java王國,不知道是不是國內(nèi)政策有變,導(dǎo)致人心浮動。
Python國王震怒,下令嚴查。 查來查去,所有的線索都指向了一個人:吉森。
這一天,Python特使帶著士兵來到了吉森的住所,果然發(fā)現(xiàn)他又在忽悠年輕人了。
特使又氣又笑:“你學(xué)了半吊子的Python,居然敢來蠱惑人心,實在是可笑。”
吉森看到自己的計謀已被識破,依然很鎮(zhèn)靜:“大人誤會了,我教的就是正宗的面向?qū)ο蟮脑O(shè)計和設(shè)計模式啊,這設(shè)計模式用Python實現(xiàn)起來很別扭,我就推薦他們?nèi)W(xué)Java啊。”
“胡說,Python寫設(shè)計模式怎么會很別扭? Java 由于語法所限,表達能力比較弱,對于一些問題,只好用笨拙的設(shè)計模式來解決,我們Python有可能在語法層面就解決問題了!”
“那你說說,設(shè)計模式的原則是什么?” 吉森問道。
“1. 面向接口編程,而不是面向?qū)崿F(xiàn)編程。2. 優(yōu)先使用組合而不是繼承。” 這是難不住特使的。
“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)
“看到?jīng)]有, Duck和Airplane都沒有實現(xiàn)你所謂的接口,但是都可以調(diào)用fly()方法,這難道不是面向接口編程, 如果你非要類比的話,這個fly就是一個自動化的接口啊。”
吉森確實沒想到這一層,至于第二個原則,優(yōu)先使用組合而不是繼承,可以是每個面向?qū)ο蟮恼Z言都可以實現(xiàn)的,他嘆了口氣,也就不問了。
Adapter模式
特使接著說:“Duck Typing非常強大,你不是提到了設(shè)計模式嗎,在Duck Typing面前,很多設(shè)計模式純屬多此一舉。我來給你舉個例子,Adapter模式。假設(shè)客戶端有這么一段代碼,可以把一段日志寫入文件當中。”
- def log(file,msg):
- file.write('[{}] - {}'.format(datetime.now(), msg))
“現(xiàn)在來了新的需求,要把日志寫入數(shù)據(jù)庫, 而數(shù)據(jù)庫并沒有write 方法,怎么辦? 那就寫個Adapter吧。”
- class DBAdapter:
- def __init__(self, db):
- self.db = db
- def write(self, msg):
- self.db.insert(msg)
“注意這個DBAdapter并不需要實現(xiàn)什么接口(我大Python也沒有接口),就是一個單獨的類,只需要有個write方法就可以了。”
- db_adapter = DBAdapter(db)
- log(db_adapter, "sev1 error occurred")
確實是很簡單,只要有write 方法, 不管你是任何對象,都可以進行調(diào)用, 典型的Duck Typing 。
既然Adapter可以這么寫,那Proxy模式也是類似了,只要你的Proxy類和被代理的類的方法一樣,那就可以被客戶使用。
但是這種方法的弊端就是,不知道log方法的參數(shù)類型,想要重構(gòu)可就難了。
單例模式
吉森又想到了一個問題,繼續(xù)挑戰(zhàn)特使:“Python連個private 關(guān)鍵字都沒有,怎么隱藏一個類的構(gòu)造函數(shù),怎么去實現(xiàn)單例?”
特使不屑地說:“忘掉你那套Java思維吧,在Python中想寫個singleton有很多辦法,我給你展示一個比較Python的方式,用module的方式來實現(xiàn)。”
- #singleton.py
- class Singleton:
- def __init__(self):
- self.name = "i'm singleton"
- instance = Singleton()
- del Singleton # 把構(gòu)造函數(shù)刪除
使用Singleton:
- import singleton
- print(singleton.instance.name) # i'm singleton
- instance = Singleton() # NameError: name 'Singleton' is not defined
吉森確實沒有想到這種寫法,利用Python的module來實現(xiàn)信息的隱藏。
Visitor模式
不是每個設(shè)計模式都能這么干吧? 吉森心中暗想,他腦海中浮現(xiàn)了一個難于理解的模式:Visitor,自己當初為了學(xué)習(xí)它可是下了苦工。
吉森說:“那你說說,對于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只是學(xué)了點皮毛吧,Visitor的本質(zhì)是在分離結(jié)構(gòu)和操作, 在Python中使用generator可以更加優(yōu)雅地實現(xiàn)。”
- 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)
不得不承認,這種方式使用起來更加簡潔,同時達到了結(jié)構(gòu)和操作進行分離的目的。
特使說道: “看到了吧,Python在語言層面對一些模式提供了支持,所以很多設(shè)計模式在我大Python看起來非常笨拙,我們這里并不提倡,當然我們還是要掌握面向?qū)ο笤O(shè)計的原則SOLID和設(shè)計模式的思想,發(fā)現(xiàn)變化并且封裝變化,這樣才能寫出優(yōu)雅的程序出來。”
吉森嘆了一口氣,感慨自己學(xué)藝不精,不再反抗,束手就擒。
尾聲
Python王國審判了吉森,本來要判他死刑,但是Java帝國重兵壓境,要求釋放,否則就開戰(zhàn)。
吉森被送回Java王國,成為了人們心目中的英雄,回家他仔細對比了Java和Python,在Java虛擬機上把Python語言給實現(xiàn)了!國王為了表彰他的英勇事跡,把這個語言叫做Jython。
【本文為51CTO專欄作者“劉欣”的原創(chuàng)稿件,轉(zhuǎn)載請通過作者微信公眾號coderising獲取授權(quán)】