為何大量設計模式在動態語言中不適用?

本人編程弱雞一枚,在自學Python的時候,看到Fluent Python對設計模式(design pattern)中是這樣描述的:

那麼可以認為,在動態語言中,很多設計模式將不太適用.然而{C#,Java}與{JS,Python}都可以認為 是OOP思想或者偽OOP思想,都存在實例化等概念,除去JS這種弱類型語言,個人覺得是無法嚴格做到對象上轉型,但是Python這種強類型應該是可以做到多態吧?但是至今,從Python和JS中沒有看到如何關於 多態的 提及,而且 Python與JS 都是支持繼承的.如此多的相似之處,究竟是因為動態語言缺少什麼,換而言之,這些運行時檢查才做類型檢查的語言缺少了什麼,才有了文中描述的那個結論?


有些設計模式僅僅是為了克服靜態語言的缺點而存在

==========================================

劃個重點「有些」,原文中也說了,23個pattern中的16個,說明還有7個是適用的。其他的也並不是不適用,而是有更簡單的設計,通常是直接使用類或者callable來代替介面。

而且相同的問題在動態語言當中往往可以有多得多的設計方式,不同設計方式有不同的特點和優劣,並沒有窘迫到只有一種方式能實現。

==========================================

本質上來說,動態語言的多態要比靜態語言的多態更純粹。Java的多態基於interface和類繼承,可以說完全是受實現的性能要求(包括編譯和運行)的妥協。而動態語言,以Python為例,本質上來說其實只有兩個多態的點:callable和getattr

  1. 所有的callable,只要能接受相同的參數,就可以通用
  2. 所有的對象,只要能通過__getattribute__機制獲取到相同的屬性,就可以通用

更重要的是這兩條原則不需要任何的聲明就能夠成立,這樣設計上來說要比基於interface和顯式定義簡潔得多。具體到Python,所有其他特性可以說都是在這個基礎上成立的:

  1. 類是callable,構造對象就是調用類
  2. 類也是對象,構造類是調用metaclass
  3. 許多語法特性本質上是獲取magic方法(__開頭和結束)然後調用
  4. 獲取對象的屬性就是調用對象對應類的__getattribute__。__getattribute__可以認為是設計模式里模板模式的體現,它的默認實現有很多注入點,__getattr__,descriptor,繼承,實例方法,類方法,靜態方法,__dict__等都來自於默認實現。

因為這些特性,原來的所謂設計原則,在動態語言中並不需要特別的設計:

  1. 開閉原則:動態語言天生就是對擴展和修改都開放的,不需要任何特別的設計。只要一個新的對象有以前對象相同的屬性,它就可以替代以前的對象;只要一個新的callable接受相同的參數,它就可以替代以前的callable。甚至,在必要的時候,以前的實現也可以通過修改類來完全替換掉(Python中一般叫monkey patch)。
  2. 里氏代換原則:動態語言中只需要考慮Duck Type,Duck Type都可以互相替代,子類天生是Duck Type,僅此而已。
  3. 依賴倒轉原則:這個仍然是很重要的,但是在動態語言中,可以認為上層用到了哪些特性,哪些特性就是介面,不需要特意定義。這樣不管是增加還是減少都比較容易。要讓這個依賴的部分最小化,仍然需要精心設計。
  4. 介面隔離原則:同上,用到的才是介面,沒有用到的就等於不存在,可以不用實現,可以認為介面天生是最小化的。還可以通過動態檢測判斷輸入是否實現了某個介面,從而自動適應不同特性。
  5. 最少知道原則:這個仍然需要設計保證。
  6. 合成復用原則:在動態語言中,合成與繼承沒有本質的區別,無論使用哪一種都不會造成問題。合成可以通過動態屬性或者直接複製屬性來假裝自己是繼承關係,繼承也可以通過動態屬性屏蔽來自父類的介面(比如在Python中可以raise AttributeError)來假裝自己不是父類的派生類,甚至還可以不調用父類的__init__()。

可以看出,需要嚴格考慮得設計原則本來就要少得多,依賴倒轉和最少知道兩條足矣。

因為這些特性,許多以前在Java中需要技巧實現的場景,在Python中都可以用直接的方式實現,比如:

  1. 類就是自己的Factory
  2. 實例可以動態增加方法,因此不需要Adaptor或者Decorator
  3. callable可以單獨傳遞,觀察者等模式都可以簡化


既然說的是「23個設計模式」,那就是特指「四人幫用來做例子那23個模式」了。

四人幫的《設計模式》這本書,重點在於前三章,說的是

A. 有一種比慣用法(idiom)抽象層次高那麼一點點的東西,我們給這種東西起個名字,叫設計模式。

B. 這裡有一套總結設計模式的套路,我們可以拿著這個套路去審視各種開發活動,把找到的共性的東西按照這個模板總結出來。(參照當年流行過一段時間的的九宮格日記)

C. 面向對象開發,組合優於繼承。

這幾個東西,放到什麼面向對象語言里都適用,只看前兩點的話,隨便什麼編程活動(甚至編程之外的其他活動)都適用。不信你看看《重構》那本書的寫作風格,別說動態語言不用重構。

至於那23個模式,是「怕你們看不明白,這裡把前面那三點應用於C++或Java這類靜態面向對象語言上,看看能找到哪些設計模式。」 這種性質的例子。

那23個例子,本來就是基於靜態語言找的,套在動態語言上當然會「有些變得不那麼顯眼,有些實現起來更簡單,有些甚至不適用」了。這就相當於,我針對開汽車總結出來一堆套路,例如說什麼平行泊車,三點掉頭。你照搬到自行車上,哪有那麼多事,把車子直接扛起來往停車位一放或者原地180度一轉,搞定。同樣,自行車的套路,上坡走S路線省力,下坡不要只剎前輪之類,開汽車的根本就不用管。還有直接開下台階的特技,汽車根本做不到。

比如說,靜態語言講究「對修改封閉,對擴展開放」(打了包分發的東西根本就不能改變其執行邏輯),某些動態語言根本不用管,metaClass,prototype隨便改。但允許這樣改是不是一定好,未必,得看場景。

再比如,靜態語言講究面向協議編程,你要傳值就必須保證繼承介面或父類來承諾協議。某些動態語言根本不用管,只要傳個東西進去,在運行過程中不管三七二十一直接拿來調方法試試,如果傳進去的東西又剛好帶了個叫這個名字的方法,就調用成功了,否則就拋個運行期錯誤。好玩的是,人們還給這種「根本不用管typing的玩法」起了個名字叫「Duck Typing」(「我的規矩就是沒規矩,這種沒規矩的規矩叫Duck規矩」)。允許這樣玩是不是一定好,也未必,得看場景。

真要問的話,應該問:「為什麼沒人給動態語言總結出一些針對動態語言的經典設計模式」(其實有,不過既然有,某些動態語言社區還總是拿靜態語言的設計模式來說事顯得好沒底氣)。

至於有些答案說 「設計模式是為了解決靜態語言的缺點」(有可能是被題目描述里最後那句帶偏了),那就見仁見智了。就一些套路而已,我可以說,有了這些套路我的世界更美好。但你硬要理解成「沒了這些套路你的世界就不美好」,我也不能說你全錯。就比如我一開汽車的人,學會平行泊車,我停車的選擇就會更多,你看看家裡的自行車硬要說我學平行泊車是為了克服汽車的缺點,你高興就好。


簡單來說,設計模式本來就是為了補足語言的缺陷而存在。

最初的23個設計模式基本都是應用在標準化前的C++里,其中很大一部分解決的都是其他語言里不存在的問題……比如factory method的應用場景基本就是虛構造函數,也就是傳一個類名string,構造出該類的對象,這是python天然支持的;還有command,大部分應用場景在python里一個lambda就搞定了。

還有一部分是可有可無的,比如singleton、prototype等。

設計模式不適用python,並不是因為python缺少什麼,而是因為python不缺少什麼


設計模式是存在的。

只是在python中,簡單到覺得那麼自然,好像不經設計一樣。

而且現在的django/flask的web開發,已經是框架經過高度模式化之後的結果,開發者只是填充而已。

在一些無框架可用的領域,你會發現還是存在大量分析設計架構的。

設計模式本質是總結一些常見問題場景的軟體設計方案。就如同機械設計裡面有凸輪、變速齒輪等各種固有方案一樣。


最主要原因是「函數第一性」。代碼寫多了就知道了,說再多沒用。


@Dane Brown 的奇葩言論:

問:設計模式的目的是什麼?

答:是為了解決企業開發中常見的問題。為了不再反覆的思考如何解決這些問題,所以GOF這幾個閑的無聊的大咖,弄出來設計模式。

同樣是解決問題,你可以選擇用類和介面解決,但並不只有你這一種方式,我也可以選擇通過添加語言特性解決。而且事實證明,語言特性比類和介面好用得多(請見C++的智能指針和Obj-C的ARC)。腦子聰明的人適應並探索新的特性,笨的人才會拿架構去補特性。

不是說其他語言不好,其他語言也需要有設計模式。

動態語言不需要這 23 種設計模式,它們早就把設計模式做成了語言特性。比如 Scala 的單例對象,Python 的 kwargs、函數第一性和內省,哪個不是設計模式的替代?

從前還是彙編的時候,有人總結了順序、分支和循環三種模式,到了現在,早就變成了 if、for、while 語句。現在你要是還想用 goto 實現這三種模式,不得被人罵死。時代是進步的,用動態語言的人看待你,就跟你看待濫用 goto 的原始人一樣。

=======================

本來我想說設計模式就是靜態語言的patch,但是還是想具體解釋一下。可能有如下情況:

1. 一些動態語言中沒有介面,而是靠對象的屬性來判斷。這時候不能硬搬。

2. 一些設計模式的介面僅僅是為了傳遞函數,動態語言中的函數是對象,因此不需要。

3. 動態語言的對象可以增減自身屬性,所以一些類似包裝器的模式也是沒必要的。

4. 一些動態語言已經內置了某個模式,比如 py 的關鍵字參數和構造器模式。


模式和語言無關!

模式和語言無關!

模式和語言無關!

模式,指的是對於一個特定問題場景(context)的一種解決方法(solution),既然抽象為問題,那麼自然就和具體語言沒有必然聯繫。

所以,別為此爭吵動態語言或者靜態語言誰優誰劣了

問題中應用的原文,說的也是become either "invisible or simpler" in dynamic language,原作者說的是「看不見」或者「更簡單」了,原作者也加上了引號,就我的理解,原作者可不是想引戰,只是想說在《設計模式》這本書中教科書式的模式的「解決方法」見得不多了,或者改變了形式。

因為動態語言有動態語言的特點,所以改變了模式的應用方式。

從來沒人敢說只有OO那套才是設計模式,畢竟,最早提出「模式」的還是一個建築設計師Christopher Alexander,只要是對一類問題解決方案的抽象,就是模式。

打個比方,在《設計模式》這本書中的觀察者模式(Observer Pattern),UML描述是這樣的。

上面是靜態語言實現的套路,為了支持不同的observer,就用繼承關係。

在動態語言JavaScript實現的RxJS中(實際上Rx有各種語言實現,這裡只是拿RxJS舉例子),因為Observer之間的差別只是next/error/complete函數的實現,所以觀察者模式簡化為下面這樣。

沒有了繼承,不是按照四人幫《設計模式》中的套路,但是誰又敢說RxJS應用的不是觀察者模式呢?

設計模式是不朽的,但是應用方式會是不斷變化的


不是「缺什麼」,是沒必要。本來靜態類型的一些設計模式,就是用來簡化靜態類型的寫法,而動態類型里實現那些東西本來就很簡單,自然不需要了。


不敢苟同。

即便寫了這麼多年JS,每次翻四人幫的書仍然有新的收穫。

如果你覺得設計模式在某程度上沒用,說明兩個問題:

  1. 你的系統還不足以複雜到要用設計模式的程度;
  2. 你的經驗還沒強到能熟練應用設計模式的程度。


你的理解

那麼可以認為,在動態語言中,很多設計模式將不太適用.

字典的理解

invisible - invisible adj.看不見的;不易為視線所見的,隱匿的;無形的(指銀行、旅遊等服務);不引人注目的

simpler - 簡單的( simple的比較級 );樸素的;易受騙的;自然的

不適用 - not applicable; inadequacy; inapplicability

你覺得你對還是字典對?


動態語言有自己的設計模式~,只是靜態語言的設計模式多數不適合動態語言。

如動態語言 Ruby 里流行的元編程,這「可以算得上是」 動態語言里的設計模式,可它並不適合於靜態語言。


個人愚見,二十三種設計模式是靜態語言在生產過程中的經驗總結,對於動態語言不適用,或者是沒有被明確的概念化是正常的,真正的指導思想是六大原則,動態語言同樣適用。


實名反對第一名的答案

別教壞小朋友。

問:設計模式的目的是什麼?

答:是為了解決企業開發中常見的問題。為了不再反覆的思考如何解決這些問題,所以GOF這幾個閑的無聊的大咖,弄出來設計模式。

問:為什麼動態語言沒有設計模式呢?

答:因為不需要啊,誰會用動態語言去開發企業級應用呢?

或者

用到了,但是你不知道。

問:你說的這麼言之確確的,是真的么?

答:和之前的回答有點衝突。因為js中的確是需要設計模式的,各位有空,可以看看jq,angular的源碼,裡面用到了很多設計模式!

綜上所述,不是什麼patch什麼的,而是因為根本用不到,或者你還沒有用到,或者你不知道而已。

again,設計模式解決的是通用問題。不分靜態語言或者動態語言的!


不是不適用,而是不適合將靜態語言中的實現照搬過來,比如發布訂閱模式,在js中因為函數也是對象可以當值傳遞,所以調用訂閱方法時不必傳訂閱者對象,只需要傳個函數就可以了。

另外有些模式的存在就為了彌補靜態語言的"缺陷",在動態語言中沒這個缺陷也就沒用武之地了

標準跟實現要分開,不存在偽oo思想,只存在偽oo語言


問題中提到的Design Patterns in Dynamic Programming 應該是這個:

http://norvig.com/design-patterns/design-patterns.pdf

很有些設計模式解決的問題,在動態語言中,已經是語言特性或者做成了語法糖了,所以並不需要顯式的寫那一套模式代碼了。


首先我們應該澄清設計模式這一概念的含義,才能解答你的困惑。

概念一,如果設計模式是指對抽象問題的解決方案的抽象的話,那麼模式千千萬萬,23種設計模式即不完整,也不獨立,也不原子化,無法在它之上構建所有的模式,也無法以它為原語來描述所有的模式,那麼我們單純學習這23種的意義是什麼?

概念二,如果設計模式是彌補靜態語言某些不方便的特性而總結的代碼套路,那麼在動態語言里這些特性已經內置,還需要繞遠路去學習23種設計模式的套路嗎,然後用語言已有的特性去實現套路再去實現已有的特性?繞不繞啊?這就像你已經熟悉走路了,在走路之前還需要刻意學習走路模式,走路的時候還要刻意去想走路的模式然後再走路,腦子抽了。

有人說動態語言里,設計模式是invisible or simpler,這是將設計模式置於概念一的解釋,但是從這種角度來看設計模式,它就沒什麼用,就比如你看到了鳥在飛,你發現了它扇動翅膀這一模式,你就能飛嗎?根本就沒有發現根本性原理以及提出解決方案啊。有什麼用?

個人觀點,23個設計模式沒有描述通用的問題的結構,也沒有給出問題的解決方案,也沒有描述代碼的通用結構,也沒有描述設計的通用結構。

23個設計模式的價值在於解決了靜態語言動態性不足的問題,而動態語言里這些代碼套路已然無用。

如果真有通用的問題和通用的解決方案,直接寫成lib或者框架或者做成語言特性即可,為何還要讓程序員去費勁的識別這些通用模式,然後千人千面的去設計代碼的模式,然後寫出千奇百怪的實現代碼,然後自豪的告訴別人,我這是設計模式!

(個人認為函數式和面向對象以及類型推導以及計算機科學裡的範疇論屬於概念一,23種設計模式屬於概念二)


靜態語言由於缺乏某些特性,當時經過實踐總結出了23種設計模式(當然實際上模式更多,只不過gof的太出名了)。這些模式就像是當時比較解決方案,遵循它們便於代碼復用。

也可以說,這些模式是對當時靜態語言「缺陷」的補丁。而現在很多設計模式被動態語言內化為語言特性了。例如 Python 的迭代器、裝飾器、資源管理器、生成器、文件作為天然的單子等等。


菜雞來回答下這個問題,如果有毛病歡迎大家在評論區指出。

給出兩個Github上設計模式的Python(動態語言),Java(靜態語言)實現

faif/python-patterns :這是Python版本的

iluwatar/java-design-patterns:這是Java版本的

以策略模式為例,https://github.com/faif/python-patterns/blob/master/behavioral/strategy.py 這一份Python版本能利用動態語言的特性很方便的處理,而在Java中,我們可能要對interface編寫不同的實現。

又比如說抽象工廠模式,Python的鴨子類型讓你完全不用弄出一堆神奇的借口、繼承關係。

順便附上網上找到的與此相關的問答Are there any design patterns that are unnecessary in dynamic languages like Python?

希望對您有所幫助0.0


這個問題本質上不是『動態語言中缺少了什麼』,而是『靜態語言中缺少了什麼』

多態在Js和Python中都是可以實現的。但作為動態語言,在大部分常見問題上,它們可以跳過多態,直接使用Duck Type解決問題。

注意原文中的"Invisible or simpler",這段話不是在說設計模式『不能』被應用到Python和類似的語言上——實際上你完全可以按照Java的方式寫Python,但我必須提醒你那會是非常讓人難忘的體驗——而是說,動態語言可以用更簡單的方式,甚至根本不需要任何額外的工作,就完成這些設計模式本來要做的事情。


先做幾個工程,然後估計你有答案了無論對錯。現在有的答案對的你也不知道對,錯的你也不知道錯,加班者恆加班不加班者恆不加班。


感覺題主理解錯原文了,人家並沒有說設計模式不適用,只是這些設計模式要麼默認生效要麼實現方式簡化了而已。


對於python,裝飾器語法對應裝飾器模式,迭代器對應迭代器模式,平時用的語法其實都是設計模式的精華啊


那些「設計模式」只對Java這種面向類的搞基語言有用,因為面向類的語言有大量的缺陷,以致於不少東西很難用面向類語言中的class表達,所以需要一些補丁和示例來補漏洞。

動態語言不需要,因為動態語言不需要class這種東西,即使有,也是像py/ruby那樣極度靈活。沒有面向類語言的缺陷,自然不需要那些補丁。

還有就是函數式,例如scheme/javascript/lua等,「設計模式」中很多根本用不到,因為光是一個lambda就可以解決一大半「設計模式」的問題。

「設計模式」是為面向class的語言提供的。很多「模式」要解決的問題在動態/函數式語言中根本就不是問題。

(至從c++11發布後,到現在c++17,寫代碼越來越爽。原本就不需要大搞模式,現在直接徹底滅掉模式,光是一個lambda就把以前一堆工廠啥玩意的垃圾代碼滅掉了。)


推薦閱讀:

python如何繪製一個橫坐標為字元串,縱坐標為數字的折線圖?
python 的 dict真的不會隨著key的增加而變慢嗎?
為什麼要學 Python?
有哪些值得推薦的Python學習網站?
有哪些值得推薦的 Python 開發工具?

TAG:JavaScript | 編程語言 | Python | Java | 設計模式 |