我們知道:一個軟件從無到有需要經過如下幾個階段:分析、設計、編程、調試、部署和運行。
編程階段我們通常使用Java/.NET這樣面向對象語言工具,可以帶來很多設計上的好處,但是也存在一個奇怪的現象:
很多程序員雖然在使用OO語言,但是卻在code非OO的代碼,最終導致系統性能降低或失敗,這個現象在Java語言尤其
顯得突出,難怪有些人就把問題歸結于Java語言本身,睡不著覺怪床歪,又為了面子問題,說自己轉向.NET,實際上是在 回避自己的問題和弱點。
那么,這些人的問題和弱點體現在什么地方呢?從上面軟件生產過程來看,每個階段都對前面有所依賴, 在編程階段出問題,追根溯源,問題無疑出在分析和設計階段,分析設計作為一個軟件產生的龍頭,有著映射實際需求世界 到計算機世界這樣一個拷貝任務,如何做到拷貝不走樣,是衡量映射方法好壞與否的主要判斷標準。
目前,將需求從客觀現實世界映射到計算機軟件世界主要有兩種方式:傳統數據庫分析設計和面向對象建模( object-oriented class model), 當前軟件主要潮流無疑是面向對象占據主流,雖然它可能不是唯一最好最簡單的解決方案,但是它是最普通,也是最恰當的。
也就是說:在分析設計階段,采取圍繞什么為核心(是對象還是數據表為核心)的分析方法決定了后面編碼階段的編程特點,如果以數據表為核心進行分析設計, 也就是根據需求首先得到數據表名和字段,然后培訓程序員學會SQL語句如何操作這些數據表,那么程序員為實現數據表的前后順序操作, 必然會將代碼寫成過程式的風格。
相反,如果分析設計首先根據需求得出對象模型(class Model),那么程序員使用對象語言,再加上框架輔助,就很順理成章走上OO編程風格。 至于OO代碼相比傳統過程編碼的好處不是本文重點,可參考J道(jdon.com)相關討論,擴展性和維護性好,開發越深入開發速度越快無疑是OO系統主要優點。
本文重點主要是比較OO建模和數據表建模兩者特點,這兩者我們已經發現屬于兩個不同方向,也就是說,屬于兩個完全不同的領域,在J道其他文章里我們 其實已經把這兩個領域上升為不同的學科,數據表建模屬于數學范疇思維;而OO建模屬于哲學思維。(用科學的思維方法指導軟件的設計開發 http://www.jdon.com/article/32520.html)
下面我們看看面向對象的Class Model和Database Model是如何來表達客觀世界的,也就是他們在表達需求上有些什么不同?
面向對象模型(Class Model)
類代表一個對象類型,類在代碼運行階段將被創建為一個個對象實例, 每個類由兩個部分組成:屬性和行為,屬性通常是一些數據狀態值,也就是說:類將數據封裝隱藏在自己內部了, 訪問這些數據屬性必須通過類公開的方法,或者接口。
別小看這樣一個小小包裝,卻決定了以后代碼的維護性和擴展性, 打個比喻,日常生活中我們經常用各種盒子和袋子包裝一些東西,這樣做就是為了方便這些東西的攜帶或儲藏,小到生活, 大到客觀世界每個地方,都是包裝分類的影子,無論大小公司都是一個封裝,行政部分單位劃分,倉庫物流更需要包裝, 我們從來不會因為嫌麻煩而不愿意引入一個似乎多余的盒子或袋子,那么有什么理由不在我們賴之生存的軟件中(靠編軟件吃飯) 引入封裝概念呢?
這里可以再深入想像一下:不愿意用盒子和袋子的攜帶東西大部分是一些急脾氣的毛頭小伙子,而偏偏這些小伙子又從事 軟件工作,看來軟件的非對象化是注定的,只是一個玩笑。
類的方法行為也有多種類型,如公開 私有等,我們可以設計一些方法為公開接口,而將另外一些行為隱藏起來, 這樣一個看似簡單靈活的選擇,卻能夠應付我們日后頻繁的修改,軟件不修改就不叫軟件,軟件修改了就崩潰是業務軟件, 專業的軟件是抗修改的,而且能夠極其方便快速地被修改。這些都依靠接口公開和隱藏這樣一個簡單魔術,具體各種魔術表演 可以參考GoF設計模式(http://www.jdon.com/designpatterns/index.htm)。
類的關系
我們不能只用一個一個單獨的類來表達客觀世界,因為客觀世界存在千絲萬縷的各種關系,在計算機領域無疑我們使用 類的關系來表達映射這些關系。這里我們只探討類在建模方法上的關系,而不是UML中類的通用關系。 類在建模上主要有如下幾個關系:
類與類關系經常是這樣:一個類包含一個類(構造性structural),或者借助另外一個類達到某個功能(功能性), 在對需求建模分析中,構造性的這種關系,也稱為關聯(Association)是我們關注重點,當然這種關系很顯然表達的是一種 靜態的結構,比如電腦包含屏幕,他們之間的關系就是一種關聯。
聚合(Aggregation)是一種表格式樣的關聯,表示一個類包含多項子類,這種關系是一種整體與部分的關系。 一個汽車有四個輪子,四個輪子是汽車的部分。
組成(Composition)是一種更強烈的聚合關系,一個對象實際是由其子對象組成,子對象也唯一屬于父對象。
繼承也是類建模中經常用到的關系,繼承可以將一些數據屬性抽象到父類中,避免重復,如入庫單和出庫單有 很多屬性是差不多的,唯一不動的就是入庫和出庫的行為,那么我們可以抽象一個庫單為父類,使用繼承關系分別 表達入庫單和出庫單。
在Evans DDD中,提到通過訪問聚合根來遍歷導航關聯對象,這樣做的好處很明顯保證了對象的從屬性,非常符合 我們日常生活邏輯,比如,你要得到盒子里面的東西,必須首先得到盒子,然后經過一些準備如打開盒子,才能得到 盒子里面的東西,假設一下,如果沒有這樣封裝導航關系,盒子和東西都是可以透明并行得到,你想得到東西就能夠 直接獲得,而不必經過打開盒子這一關,這樣的訪問方式首先怪誕,其次是不安全,如果盒子和東西放在數據表中,就會發生 這種情況。
數據庫模型(Database Model 傳統E-R模型 )
好了,下面我們談論關系數據表模型,以前我們樸素的分析設計都是根據需求直接建立數據表的方式來進行的,為什么稱為樸素, 是因為我們好像只有數據結構 算法方面的知識,也認為只有這樣做才叫做軟件。 那么既然這條路能夠走出來,我們看看這個領域是如何映射客觀世界的。
數據表由于技術提供龐大數據存儲和可靠的數據訪問,正在不斷從技術領域走向社會領域,很多不懂計算機的人 也知道需要建立數據庫來管理一些事務,但是不代表我們就必須圍繞數據庫的分析設計。
數據表是類似前面的“類”,也是一種表達客觀世界的基本單元,表有多列字段,表的字段是保存數據的,每個字段有數據類型。 注意,這里沒有數據的封裝和公開,表的字段是赤裸的,只要有數據庫訪問權限,任何人都可以訪問,沒有結構層次關系, 都是扁平并列的,如果你想在數據表字段之間試圖看出客觀世界中的層次和封裝,那就錯了,在拷貝不走樣這個條件下, 這個映射方法至少把這個信息拷貝丟了。
數據表也有一些行為,這些行為是基于實體的一些規則:
約束(Constraints) 能夠保證不同表字段之間的關系完整安全性,保證數據庫的數據安全。
觸發器(Triggers)提供了實體在修改 新增和刪除之前或之后的一些附加行為,
存儲過程(Database stored procedures)提供數據專有的腳本性語言,存儲過程象一個數學公式雖然具有抽象簡潔美學,但是這種簡潔是悶葫蘆美學,不是大眾美學,只有公式存儲過程發明者自己了解精通,別人無法插手,軟件不是科學,不是比誰智商高,科研水平高,軟件是人機工程,更講究集體,講究別人是否方便與你協同擴展軟件。
關系數據表的遍歷訪問是通過列字段遍歷或表join等方式實現,SQL語句是這樣標準語言, 只要會寫SQL語句,就能訪問那些失去層次,失去客觀世界特征的蒼白的數據,這樣的系統能夠多少真實 反映客觀需求,是有問號的?SQL語句是否方便修改,是否經得起頻繁修改而不出錯,都是有疑問的地方,是否 SQL語句越復雜,修改越快,或者另外一個程序員能夠很快修改不是自己寫的SQL語句,這些都是問題所在。
數據表關系
數據表的關系主要是通過外健或專門關聯表來表達的,這種關系雖然可以反映1:1或1:N這樣關系,但是無法 表達關系的性質,是緊密組成關系式的關聯,還是無關緊要的普通關系,正因為如此,使用數據表分析設計時, 我們會有蜘蛛網的關系表,這些關系由于在后期無法分辨性質,無法進行整理,增加了系統復雜性。
更重要的是:分析就是對一個可能陌生領域進行探尋,如果使用數據表的分析設計方法,那么我們實際就是 在陌生領域中尋找數據表這樣一個形式,那么有可能產生誤判斷,將一個實則是表達關系的東東誤認為是一個實體表, 因為關系表必然帶來關系,這樣,就必然產生蜘蛛網式的數據表模型,將簡單問題復雜化。
總結
要談方法,這個世界其實只存在兩種:一是將復雜問題簡單化的方法;一個是將簡單問題復雜化的方法。 你使用什么樣的方法,你就有什么樣的世界觀,就是什么樣的人,但是對于軟件這個領域,你只能選擇前者。
因為方法的不同,軟件路線也就存在下面幾個路線:完全面向對象類建模路線(J道網站和筆者一直致力于這種路線的推介); 一種是對象和關系數據庫混合型,還有一種就是過去的完全關系數據庫類型軟件(如Foxpro/VB/Delphi等)。需要參考完全面向對象類建模路線的源碼可見(http://www.jdon.com/jdonframework/app.htm#simple。)