Python設計模式:為了整潔又時尚的代碼
關鍵時刻,第一時間送達!
GIF
作者ANDREI BOYANOV - 高級軟體工程師 @TOPTAL
讓我們再重申一下:Python是一種具有動態類型和動態綁定的高級編程語言。我將它描述為一個強大的高級動態語言。許多開發人員都喜歡Python,因為它具有清晰的語法、結構良好的模塊和包,以及巨大的靈活性和廣泛的現代功能。
在Python中,並不要求您寫的類和對象進行實例化。如果您的項目中不需要複雜的結構,則可以只編寫函數。更好的是,您可以編寫一個平面腳本來執行一些簡單而快速的任務,而不需要構建代碼。
與此同時,Python是面向對象的100%語言。那是什麼意思呢?簡單地說,Python中的一切都是一個對象。 函數是對象,第一類對象(無論什麼意思)。關於函數是對象的這個事實很重要,所以請記住它。
所以,您可以在Python中編寫簡單的腳本,或者只是打開Python終端,並在那裡執行語句(這非常有用!)。但同時,您可以創建複雜的框架、應用程序、庫等。您可以在Python中做這麼多。當然還有一些限制,但這不是本文的主題。
然而,由於Python是如此強大和靈活,我們在編程時需要一些規則(或模式)。所以,讓我們看看什麼是模式,以及它們與Python的關係。我們還將著手實施一些基本的Python設計模式。
為什麼Python對模式有好處?
任何編程語言對模式都有益。實際上,應該在任何給定的編程語言的上下文中考慮模式。模式,語言語法和類型都對我們的編程產生了限制。來自語言語法和語言本質(動態,功能,面向對象等)的限制可能與其背後存在的原因不同。模式的限制是有原因的,它們是有目的的。這是模式的基本目標; 告訴我們如何做到某事,以及怎麼樣會做不到。稍後我們將討論模式,特別是Python設計模式。
Python是一種動態和靈活的語言。Python設計模式是利用其巨大潛力的好方法。
Python的理念建立在思想最好的做法之上。Python是一種動態語言(我已經說過了嗎?),因此,已經實現了一些流行的設計模式,其中包含幾行代碼。一些設計模式是內置在Python中,所以即使不知道,我們也可以使用它們。由於語言的本質,不需要其他模式。
例如,Factory是一種結構化的Python設計模式,其旨在創建新對象,並在用戶那兒隱藏實例化邏輯。但是在Python中創建對象是動態的,所以不需要像Factory這樣添加。當然,如果您願意,您可以自由地實現它。可能有些情況真的很有用,但它們是一個例外,而不是規範。
Python的哲學有什麼好處?我們從這開始(在Python終端中探索它):
這些可能不是傳統意義上的模式,但這些是以最優雅和最有用的方式定義「Pythonic」編程方法的規則。
我們還有PEP-8編碼指南,有助於構建我們的代碼。對我而言,當然有一些適當的例外。順便說一句,PEP-8本身也鼓勵這些例外:
但最重要的是:知道何時不一致 - 有時風格指南並不適用。如有疑問,請用最好的判斷。看看其他的例子來決定什麼看起來最好。不要猶豫,去問!
將PEP-8與Python的Zen(也是PEP - PEP-20)相結合,您將擁有創建可讀和可維護代碼的完美基礎。添加設計模式,您已準備好創建具有一致性和可演變性的各種軟體系統。
Python設計模式
什麼是設計模式?
一切從四人幫(GOF)開始。如果您不熟悉GOF,請快速進行在線搜索。
設計模式是解決眾所周知問題的常用方式。兩個主要原則是基於GOF定義的設計模式:
面向介面編程,而不是面向實現編程
組合優於繼承
從Python程序員的角度,我們來仔細看看這兩個原則。
面向介面編程,而不是面向實現編程
想想Duck Typing。在Python中,我們不喜歡根據這些介面定義介面和程序類,是這樣嗎?但是,聽我說!這並不意味著我們不會考慮介面,實際上我們一直在做這種Duck Typing。
讓我們來談談一下臭名昭著的Duck Typing方法,看看它是如何適應這種範式的:面向介面編程。
如果它看起來像個鴨子並且像一個鴨子一樣嘎嘎,那它就是一個鴨子!
我們不關心對象的本質,我們不必關心對象是什麼; 我們只想知道是否能夠做我們需要的(我們只對對象的介面感興趣)。
對象可以嘎嘎叫嗎?好吧,讓它嘎嘎叫!
我們為鴨定義了一個介面嗎?沒有!我們是否面向介面編程而不是面向實現?是! 而且,我覺得這很好。
正如Alex Martelli在他關於Python中的設計模式的眾所周知的演講中指出的,「學會鴨子類型需要一段時間,但是以後可以節省您大量的工作!」
組合優於繼承
現在這就是我所說的Pythonic原理!與在其他類中包裝一個類(或更多的是幾個類)相比,我創建了更少的類/子類。
不是這樣做:
我們可以這樣做:
優點很明顯。我們可以限制包裝類的方法是否暴露。我們可以在運行時注入持久化實例!例如,今天它是一個關係型資料庫,但是明天可能是任何需要的介面(再次是那些討厭的鴨子)。
組合對於Python來說是優雅而自然的。
行為模式
行為模式涉及對象之間的通信、對象如何交互以及如何完成給定的任務。根據GOF原則,Python中共有11種行為模式:職能鏈,命令,解釋器,迭代器,中介者,備忘錄,觀察者,狀態,策略,模板方法,訪問者。
行為模式處理對象間通信,控制各種對象如何交互和執行不同的任務。
我發現這些模式非常有用,但這並不意味著其他模式作用不大。
Iterator(迭代器)
迭代器被構建進了Python,這是這個語言最強大的特性之一。多年前,我讀到別人說是迭代器使Python變得非常優秀,到現在我仍然是這麼認為的。如果您對Python迭代器和生成器有足夠多的了解,那您就會知道關於這個獨特Python模式需要知道的一切。
Chain of responsibility(職能鏈)
這個模式為我們提供了一種使用不同方式對待一個請求的方法,每種方法都針對請求的特定部分。您知道的,對於好的代碼來說,最好的原則之一就是單一職責原則。
每一塊代碼必須做一件事,並且只能做一件事。
這一原則深深融入了這一設計模式。
例如,如果我們要過濾一些內容,我們可以實現不同的過濾器,每個過濾器都要做一件精確的事情,並且明確定義其過濾類型。這些過濾器可用於過濾令人反感的單詞,廣告和不適合的視頻內容等。
Command(命令)
這是我作為程序員實現的Python設計模式之一。這提醒了我:模式不是被發明的,而是被發現的。他們存在,我們只需要找到並使用它們。很多年前為了實現我們的一個很大的項目我發現了這一點:一個特殊的所見即所得的XML編輯器。在代碼中集中使用這個模式後,我在另外一些網站上看到了更多關於它的內容。
出於某些原因,我們需要從準備執行的內容開始,然後在需要時執行該命令模式。這樣的好處是在這種方式下封裝的動作可以使Python開發人員添加與執行操作相關的其他功能,例如撤消、重做或者保留操作歷史等。
讓我們看一下簡單但經常使用的例子:
Creational Patterns(創造者模式)
首先需要指出的是創建者模式在Python中並不常用。為什麼?因為Python語言本身的動態性質。
某位比我更有智慧的人曾經說過工廠方法是內置在Python中的。這意味著語言本身為我們提供了一種可以以足夠優雅的方式創建對象的全部靈活性; 我們很少需要在這之上實現任何東西,如單例和工廠方法。
在一個Python設計模式教程中,我發現了有關創造者模式的描述,它是這樣來聲明的「模式提供了一種在隱藏創建邏輯的同時創建對象的方法,而不是使用new運算符直接實例化對象」。
它恰到好處地總結了這個問題:我們不需要在Python中使用new運算符!
不管怎樣,讓我們來看下如何實現其中的某一小部分,看下使用這樣的模式我們能否從中獲得好處。
Singleton(單例)
當我們要保證運行時只有一個給定類的實例存在時,可以使用Singleton模式。在Python中我們是否真的需要這個模式? 根據我的經驗,簡單創建一個實例並且隨後使用它比實現單例模式更為容易。
但是如果您想實現它,這裡有一個好消息:在Python中,我們可以改變實例化過程(以及其他任何東西)。記得我之前提過的__new__()方法嗎?就是它了:
在這個示例中,Logger是一個單例。
在Python中使用單例模式有以下這些備選方案:
使用模塊
在應用程序的頂層創建一個實例,可以是配置文件
將實例傳遞給需要它的每個對象。這是一個依賴注入,它是一個強大而容易掌握的機制。
Dependency Injection(依賴注入)
我不打算討論依賴注入是否是一種設計模式,但我會說這是一個實現松耦合的非常好的機制,它有助於讓我們的應用變的更加可維護和可擴展。把它和鴨子類型結合起來然後力量就總是會與您同在。
鴨子?人類?Python都不關心。它靈活得很!
我之所在這篇文章的創造者模式部分列出來是因為它處理了何時(或者更好是:何地)創建對象這個問題。它是在外部創造的。更準確的說,對象並不是在我們使用它的地方創建的,所以依賴關係也不會在消費它的地方創建。消費者代碼接收這些外部的對象並使用它。更多參考,請閱讀這個Stackoverflow問題最受歡迎的答案。
這是一個關於依賴注入很好的解釋,而且它給了我們關於這個特殊技術一個很好的想法。基本上,答案解釋了以下示例的問題:不要自己從冰箱裡面拿東西來喝,而是聲明一個需要。告訴您的父母您需要在午餐喝點東西。
Python為我們提供了全部需要的東西並且實現起來很簡單。想一下它在Java和C#其他語言中可能的實現,您將很快意識到Python的美麗。
讓我們來看一個簡單的關於依賴注入的例子:
我們在Command這個類中注入了authenticator和authorizer方法。Command類所需要的是成功地執行他們,而無須擔心他們實現的細節。這樣的話,我們可以在Command類中使用任何我們在運行時決定使用的認證和授權機制。
我們已經展示了如何通過構造函數注入依賴關係,但是我們可以通過直接設置對象屬性來輕鬆地注入它們,從而解鎖更多的潛力:
關於依賴注入還有很多的東西要學;例如,好奇的人會去搜索IoC。
但是在您這樣做之前,請閱讀Stackoverflow上的另一個回答,對於這個問題投票最多的回答。
還有,我們只是演示了如何在Python中只使用內置的語言功能來實現這個美妙的設計模式。
讓我們不要忘記這一切的意義:依賴注入技術允許非常靈活和容易的單元測試。想像一下,您可以隨時更改數據存儲的架構。模擬資料庫將會變成一個微不足道的任務,不是嗎?有關進一步的信息,您可以查看Toptal的Python模擬簡介。
您可能也會想研究原型、生成器和工廠方法設計模式。
結構化模式
Facade(外觀模式)
這可能是最為出名的Python設計模式。
假設您有一個系統有著大量的對象。每一個對象都提供了豐富的介面方法 。您可以使用這個系統做很多很多事情,但是如何簡化這個介面呢?為什麼不添加一個介面對象來暴露出所有API方法的精心設計的子集呢?用Facade(外觀模式)!
Facade(外觀模式)是一個優雅的Python設計模式。它是精簡介面的一個完美方式。
Python外觀模式示例:
沒有什麼驚喜,也沒有技巧,Car類是一個外觀模式,就是這樣。
Adapter(適配器)
如果說外觀模式用於精簡介面,那麼適配器就是關於修改介面。就像當系統期待鴨子時卻只有牛一樣。
假設您有一個將信息記錄到給定目的地的工作方法。您的方法期望目標有一個write()方法(例如,每個文件對象都有)。
我會說這是一個寫得很好的帶依賴注入的方法,它擁有巨大的擴展能力。假設您想要紀錄到某些UDP套接字中而不是某個文件里,您知道如何打開這個UDP套接字,但唯一的問題是套接字對象沒有write()方法。您需要一個適配器!
但是為什麼我發現適配器如此重要?好吧,當它有效地和依賴注入結合時,它就會給予我們極大的靈活性。當我們可以實現一個適配器並將新介面轉換為眾所周知的介面時,為什麼要修改我們經過驗證的代碼來支持新的介面?
由於橋接和代理設計模式與適配器的相似性,您還應該了解和掌握它們。想一下在Python中實現他們是多麼的簡單,並且考慮一下在您的項目中可能通過哪些不同的方式來使用它們。
Decorator(裝飾者模式)
哦,我們真是太幸運了!裝飾者模式真的很好,而且我們也已經集成到了這門語言中。在Python中我最喜歡的是使用它來教我們使用最佳的實踐。這並不是指我們不會意識到最佳實踐(尤其和設計模式),但不管怎樣使用Python 我感覺我正在遵循著最佳實踐。就我個人而言,我發現Python的最佳實踐是非常符合直覺的,這是新手和精英開發人員都讚賞的東西。
裝飾者模式是關於引入額外的功能,特別的是它沒有使用繼承。
那麼,讓我們來看下如何不用內建的Python功能來裝飾一個方法。以下是一個直截了當的示例。
這裡不太好的是execute方法不僅僅是執行某些功能,還做了別的事情。我們沒有遵循單一職責原則。
像下面這樣簡單編寫會好點:
我們可以在另一個地方,在裝飾者模式中實現任何授權和認證功能,如下所示:
現在的execute()方法是:
簡單易讀
只做一件事(至少在看代碼時)
用身份驗證裝飾
裝飾有授權
我們使用Python集成的裝飾者語法編寫類似的代碼:
重要的是要注意,您沒有限制像裝飾者那樣的活動。一個裝飾者可能涵蓋了整個類。唯一的要求是它們必須是可調用的。但對此我們毫無疑問;我們只需要定義__call__(self)方法。
您可能也想再進一步了解Python的函數工具(functools)模塊。有待發現的東西還有很多!
結論
我已經展示了使用Python的設計模式是多麼的自然和容易,而且我也展示了如何在Python中簡單的編程。
「簡單比複雜好,」記得嗎?也許您已經注意到,沒有一個設計模式是完全且正式描述的。沒有展示複雜全面的實現。您需要「感覺」並以最適合您的風格和需要的方式實施它們。Python是一種偉大的語言,它給了您創作靈活、可重用代碼的全部力量。
然而,它給您的遠勝於這些。它給了您編寫非常糟糕的代碼的「自由」。不要這麼做!不要重複(DRY),並且不要寫超過80個字元的代碼行。還有不要忘了在合適的地方使用設計模式;它是從別人那裡學習並從他們豐富的免費經驗中獲益的最好方式之一。
來自:python部落
譯者:Sarah
英文原文:https://www.toptal.com/python/python-design-patterns
Python開發整理髮布,轉載請聯繫作者獲得授權
※為什麼Python語言是孩子們學習編程的最佳選擇?
※Python面試攻略
TAG:Python |