最近兩個星期,我使用 plantuml (貝爾實驗室出品了一個超級繪圖工具 graphviz, 這是一個包裝版)把我的繪圖項目做了一次全面的接口和類的可視化。使用了很多設計模式,包括:橋接、裝飾器、生成器、抽象工廠。繪制完后,圖像是很美的,接口之間的交互和參數(shù)定義清晰優(yōu)雅。很漂亮!
然并卵!
這個項目在開發(fā)之處已經(jīng)違反了我的一些感覺,對于程序設計的感覺。從我對數(shù)據(jù)庫和服務器的多年經(jīng)驗,使用基于數(shù)據(jù)表和數(shù)據(jù)解釋的抽象結構,你總能獲得最簡單易用可擴展的軟件結構。
不過,這個繪圖項目真的很復雜,涉及了很多的多態(tài)和關聯(lián)。比如,在一個長的列表中存儲種類不同的圖形,這些圖形存儲的繪圖數(shù)據(jù)和相關信息都不同,我需要把這些數(shù)據(jù)視做同一種類型,然后迭代它們,選出需要的一個并且使用它的相關信息。所以,我嘗試使用學術界的設計模式來解決其中的問題。
當項目變得很龐大的時候,我意識到設計模式屁都不是。諸如橋接、裝飾器以及其他,都是建立在一種假設,假設你的父組件和子組件總是可以忽略對方的細節(jié),而可以統(tǒng)一的處理它們。比如,面包有奶油味、抹茶味、水果味,面包又有低檔材料、高檔材料,那么你可以把味道和材料分為兩個不同的接口,然后各自抽象,并且組合這兩個接口生成更豐富的面包,比如低檔材料的抹茶味面包。但是,真實的編程世界中,這樣的理想狀態(tài)非常少。在真實的編程世界中,面包還想要更多的東西,比如奶油味的有糖,抹茶味的沒有糖,有糖的面包放在左邊柜臺上,沒有糖的面包放在右邊柜臺上。看到了吧,復雜度升級了,柜臺跟面包有沒有糖是綁定的。這意味著,如果你想像前面那樣抽象兩個接口---味道和材料,那你現(xiàn)在必須考慮柜臺。因為低檔材料的抹茶味面包是沒有糖的,放在右邊柜臺。現(xiàn)在,你不得不抽象出味道和柜臺的關系。在上面的接口之上再增加一層。每當你的需求復雜一點,這種層就會升級。比如,紅糖面包和白糖面包。
總之,就算設計模式避免了類繼承的爆炸,但是也避免不了抽象層級的復雜。
所以,我覺得我又不會編程了。于是,我盡可能的重新思考這些設計,并且重新在網(wǎng)絡上搜尋曾經(jīng)支持我的設計論調:面向數(shù)據(jù)結構編程而不是對象。如果不是為了這個繪圖項目,我絕對不會冒險再一次使用設計模式和面向對象。
我當然搜到了一大堆 Linus 排斥面向對象和 C++ Java 的話語,從感覺上,這些就是我面臨設計困難時候的感覺。我曾經(jīng)無數(shù)次這樣解決我的程序設計。
git的設計其實非常的簡單,它的數(shù)據(jù)結構很穩(wěn)定,并且有豐富的文檔描述。事實上,我非常的贊同應該圍繞我們的數(shù)據(jù)結構來設計代碼,而不是依據(jù)其它的,我認為這也是git之所以成功的原因之一。[...] 依我的觀點,好程序員和爛程序員之間的差別就在于他們認為是代碼更重要還是數(shù)據(jù)結構更重要。
在龐大的項目中,人們對不是自己開發(fā)的模塊并不了解,能快速理解其他模塊中函數(shù)的確切含義才能提高開發(fā)效率。而C++引入的各種抽象則使代碼非常依賴上下文,想理解一段代碼,需要看多得多的上下文。
面向對象語言以對象為核心,加一些相關聯(lián)的方法,簡直是囈語。重要的東西應該是數(shù)據(jù)結構,對象本身有啥重要?真正有意思的,是在不同類型的不同對象交互而且有鎖規(guī)則的時候。但是,即使是這時候,封裝什么“對象接口”也絕對錯誤,因為不再是單一對象的問題了。
有趣的是,這里有一篇另外一位前輩的很早的文字,推在 Google+ 上,來自 Unix 核心創(chuàng)建者之一 Rob Pike:
原文鏈接
A few years ago I saw this page: http://www.csis.pace.edu/~bergin/patterns/ppoop.htmlLocal discussion focused on figuring out whether this was a joke or not. For a while, we felt it had to be even though we knew it wasn't. Today I'm willing to admit the authors believe what is written there. They are sincere.
But... I'd call myself a hacker, at least in their terminology, yet my solution isn't there. Just search a small table! No objects required. Trivial design, easy to extend, and cleaner than anything they present. Their "hacker solution" is clumsy and verbose. Everything else on this page seems either crazy or willfully obtuse. The lesson drawn at the end feels like misguided epistemology, not technological insight.
It has become clear that OO zealots are afraid of data. They prefer statements or constructors to initialized tables. They won't write table-driven tests. Why is this? What mindset makes a multilevel type hierarchy with layered abstractions better than searching a three-line table? I once heard someone say he felt his job was to remove all while loops from everyone's code, replacing them with object stuff. Wat?
But there's good news. The era of hierarchy-driven, keyword-heavy, colored-ribbons-in-your-textook orthodoxy seems past its peak. More people are talking about composition being a better design principle than inheritance. And there are even some willing to point at the naked emperor; see http://prog21.dadgum.com/156.html for example. There are others. Or perhaps it's just that the old guard is reasserting itself.
Object-oriented programming, whose essence is nothing more than programming using data with associated behaviors, is a powerful idea. It truly is. But it's not always the best idea. And it is not well served by the epistemology heaped upon it.
Sometimes data is just data and functions are just functions.
--- Rob Pike (One of the Unix creators (Ken Thompson, Dennis M. Ritche, and Rob Pike))
幾年前我看到了這個網(wǎng)頁: http://www.csis.pace.edu/~bergin/patterns/ppoop.html
我真的不知道這篇文章到底是不是在搞笑。讀了一下,我雖然很想說這不是一篇搞笑的文章,但是,拜托,它根本就是。讓我來跟你們講講他們在搞笑什么吧。
e...按照他們的話語,我應該稱自己為 hacker (黑客),不管我不關心這些。Hello! 你只需要一個小的不能再小的 table ! 根本不需要什么對象。樸素平凡,容易擴展,容易清除,(比起他們的那種設計)多 TM 簡單。他們的 “hacker solution” 真的是又蠢又笨。他們寫出來的那堆東西到處透漏著瘋狂和愚蠢。他們缺乏技術認知。
很顯然,OO 的狂熱者們害怕數(shù)據(jù)。他們喜歡用語句或者構造器來初始化 tables 。他們根本不寫 table-driven 的測試。Why is this? 得有多大的心才會選擇用多級并且多層的類抽象,而不去用一個小小的三行 table ? 我曾經(jīng)聽說有人用各種 OO 的東西替換掉 while 循環(huán)。
不過好消息是,hierarchy-driven, keyword-heavy, colored-ribbons-in-your-textook orthodoxy 這些東東快到頭了。更多的人選擇組合而不是繼承。有些人已經(jīng)重新開始認識 OO。
面向對象編程語言,其本意是使用數(shù)據(jù)和相關的行為進行編程,這是一個很好的想法。事實確實如此。但是,這個想法并不總是最好的 idea。 這個想法并沒有完全的認知編程的世界。
Sometimes data is just data and functions are just functions.
--- Rob Pike (Unix 創(chuàng)建者之一的 (Ken Thompson, Dennis M. Ritche, and Rob Pike))
沒錯,我們需要的就是數(shù)據(jù)的抽象和數(shù)據(jù)的解釋器。用表來存儲你需要的各個數(shù)據(jù),對于多態(tài),C 語言中簡單直接干凈:union。使用這么一個簡單的結構,你能存儲各種不同的類型,而且你只需要存儲他們的指針,這意味著你不會浪費多少內存,同時你能獲得相同內存段但是數(shù)據(jù)不同的抽象。
然后,使用一個鏈表或者數(shù)組,把這個 union 裝進去,遍歷,cast,然后使用你需要的特定數(shù)據(jù)。
很多語言都有 union 的變體,現(xiàn)代語言中的泛型就是 union 的一種語法糖,但是你往往忘記了這種結構的真正價值和用意。仔細體會下這個全新的設計:
enum ShapeKind {
skLINE, skPORT, skBOARD
}
class Shape {
kind: ShapeKind
value: Line | Port | Board
contains(x: number, y: number): boolean
}
class ShapeContainer {
shapes: Array<Shape>
search(x: number, y: number): [ShapeKind, Shape]
}
type
ShapeKind = enum
skLINE, skPORT, skBOARD
Shape = ref object
case kind: ShapeKind
of skLINE:
line: Line
of skPORT:
port: Port
of skBOARD:
board: Board
contains: (x: number, y: number): bool
ShapeContainer = object
shapes: seq[Shape]
proc search(c: ShapeContainer, x: number, y: number): tuple[kind: ShapeKind, shape: Shape]