<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    zongxing

    沒有邁不過去的坎!

      BlogJava :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理 ::
      14 隨筆 :: 16 文章 :: 33 評(píng)論 :: 0 Trackbacks
    本文轉(zhuǎn)自:http://www.cnblogs.com/lane_cn/archive/2007/01/25/629731.html

    我使用OO技術(shù)第一次設(shè)計(jì)軟件的時(shí)候,犯了一個(gè)設(shè)計(jì)者所能犯的所有錯(cuò)誤。那是一個(gè)來自國外的外包項(xiàng)目,外方負(fù)責(zé)功能設(shè)計(jì),我們公司負(fù)責(zé)程序設(shè)計(jì)、編碼和測試。

    第一個(gè)重要的錯(cuò)誤是,我沒有認(rèn)真的把設(shè)計(jì)說明書看明白。功能點(diǎn)設(shè)計(jì)確實(shí)有一些問題,按照他們的設(shè)計(jì),一個(gè)重要的流程是無法實(shí)現(xiàn)的。于是我在沒有與投資方溝通的情況下,擅自改動(dòng)了設(shè)計(jì),把一個(gè)原本在Linux系統(tǒng)上開發(fā)的模塊改到了Windows系統(tǒng)上。結(jié)果流程確實(shí)是實(shí)現(xiàn)了,但是很不幸,根本不符合他們的需要,比起原先的設(shè)計(jì)差的更多。在詢問了這個(gè)流程的設(shè)計(jì)意圖之后,我也清楚了這一點(diǎn)。對(duì)方的工程師承認(rèn)了錯(cuò)誤,但是問題是:“為什么不早說啊,我們都跟領(lǐng)導(dǎo)講過了產(chǎn)品的構(gòu)架,也保證了交貨時(shí)間了,現(xiàn)在怎么去說啊?”。他們?cè)O(shè)計(jì)的是一個(gè)蘋果,而我造了一個(gè)桔子出來。最后和工程師商議的結(jié)果是:先把桔子改成設(shè)計(jì)書上的蘋果,按時(shí)交貨,然后再悄悄的改成他們真正需要的香蕉。的這時(shí)候距離交貨的時(shí)間已經(jīng)不足三天了,于是我每天加班工作到天明,把代碼逐行抽出來,用gcc編譯調(diào)試。好在大部分都是體力活,沒有什么技術(shù)含量,即使在深夜大腦半休眠的情況下仍然可以接著干。

    項(xiàng)目中出現(xiàn)的另外一個(gè)錯(cuò)誤是:我對(duì)工作量的估計(jì)非常的不準(zhǔn)確。在第一個(gè)階段的時(shí)候,按照功能設(shè)計(jì)說明書中的一個(gè)流程,我做了一個(gè)示例,用上了投資方規(guī)定的所有的技術(shù)。當(dāng)我打開瀏覽器,看到頁面上出現(xiàn)了數(shù)據(jù)庫里的“Tom,Jerry,王小帥”,就愉快的跑到走廊上去呼吸了一口新鮮空氣,然后樂觀的認(rèn)為:設(shè)計(jì)書都已經(jīng)寫好了,示例也做出來了,剩下的事情肯定就象砍瓜切菜一樣了。不就是把大家召集起來講講設(shè)計(jì)書,看看示例,然后撲上去開工,然后大功告成。我為每個(gè)畫面分配的編碼工作量是三個(gè)工作日。結(jié)果卻是,他們的設(shè)計(jì)并不完美,我的理解也并不正確,大家的思想也并不一致。于是我天天召集開會(huì),朝令夕改,不斷返工。最后算了一下,實(shí)際上寫完一個(gè)畫面用的時(shí)間在十個(gè)工作日以上。編碼占用了太多的時(shí)間,測試在匆忙中草草了事,質(zhì)量……能掩蓋的問題也就只好掩蓋一下了,性能更是無暇顧及了。

    還有一個(gè)方面的問題是出在技術(shù)上的,這方面是我本文要說的重點(diǎn)。按照投資方的方案,系統(tǒng)的主體部分需要使用J2EE框架,選擇的中間件是免費(fèi)的JBoss。再加上Tomcat作為Web服務(wù)器,Struts作為表示層的框架。他們對(duì)于這些東西的使用都是有明確目的,但是我并不了解這些技術(shù)。新手第一次進(jìn)行OO設(shè)計(jì),加上過多的新式技術(shù),于是出現(xiàn)了一大堆的問題。公司原本安排了一個(gè)牛人對(duì)我進(jìn)行指導(dǎo),他熟悉OO設(shè)計(jì),并且熟悉這些開源框架,曾熟讀Tomcat和Struts源代碼。可是他確實(shí)太忙,能指導(dǎo)我的時(shí)間非常有限。

    投資方發(fā)來設(shè)計(jì)書以后,很快就派來了兩個(gè)工程師對(duì)這個(gè)說明書進(jìn)行講解。這是一個(gè)功能設(shè)計(jì)說明書,包括一個(gè)數(shù)據(jù)庫設(shè)計(jì)說明書,和一個(gè)功能點(diǎn)設(shè)計(jì)說明。功能點(diǎn)說明里面敘述了每一個(gè)工作流程,畫面設(shè)計(jì)和數(shù)據(jù)流程。兩位工程師向我們簡單的說明了產(chǎn)品的構(gòu)想,然后花了一個(gè)多星期的時(shí)間十分詳細(xì)的說明了他們的設(shè)計(jì),包括數(shù)據(jù)表里每一個(gè)字段的含義,畫面上每一個(gè)控件的業(yè)務(wù)意義。除了這些功能性的需求以外,他們還有一些技術(shù)上的要求。

    為了減少客戶的擁有成本,他們不想將產(chǎn)品綁定在特定的數(shù)據(jù)庫和操作系統(tǒng)上,并且希望使用免費(fèi)的平臺(tái)。于是他們選擇了Java作為開發(fā)語言,并且使用了一系列免費(fèi)的平臺(tái)。選用的中間件是JBoss,使用Entity Bean作為數(shù)據(jù)庫訪問的方式。我們對(duì)Entity Bean的效率不放心,因?yàn)椴聹y他運(yùn)用了大量的反射技術(shù)。在經(jīng)過一段時(shí)間的技術(shù)調(diào)查之后,我決定不采用Entity Bean,而是自己寫出一大堆的Value Object,每個(gè)Value Object對(duì)應(yīng)一個(gè)數(shù)據(jù)庫表,Value Object里面只有一些setter和getter方法,只保存數(shù)據(jù),不做任何事情。Value Object的屬性與數(shù)據(jù)庫里面的字段一一對(duì)應(yīng)。與每個(gè)Value Object對(duì)應(yīng),做一個(gè)數(shù)據(jù)表的Gateway,負(fù)責(zé)把數(shù)據(jù)從數(shù)據(jù)庫里面查出來塞到這些Value Object里面,也負(fù)責(zé)把Value Object里面的數(shù)據(jù)塞回?cái)?shù)據(jù)庫。

    按照這樣的設(shè)計(jì),需要為每一個(gè)數(shù)據(jù)表寫一個(gè)Gateway和一個(gè)Value Object,這個(gè)數(shù)量是比較龐大的。因此我們做了一個(gè)自動(dòng)生成代碼的工具,到數(shù)據(jù)庫里面遍歷每一個(gè)數(shù)據(jù)表,然后遍歷表里面的每一個(gè)字段,把這些代碼自動(dòng)生成出來。

    這等于自己實(shí)現(xiàn)了一個(gè)ORM的機(jī)制。當(dāng)時(shí)我們做這些事情的時(shí)候,ORM還是一個(gè)很陌生的名詞,Hibernate這樣的ORM框架還沒聽說過。接著我們還是需要解決系統(tǒng)在多種數(shù)據(jù)庫上運(yùn)行的問題。Gateway是使用JDBC連接數(shù)據(jù)庫的,用SQL查詢和修改數(shù)據(jù)的。于是問題就是:要解決不同數(shù)據(jù)庫之間SQL的微小差別。我是這樣干的:我做了一個(gè)SqlParser接口,這個(gè)接口的作用是把ANSI SQL格式的查詢語句轉(zhuǎn)化成各種數(shù)據(jù)庫的查詢語句。當(dāng)然我沒必要做的很全面,只要支持我在項(xiàng)目中用到的查詢方式和數(shù)據(jù)類型就夠了。然后再開發(fā)幾個(gè)具體的Parser來轉(zhuǎn)換不同的數(shù)據(jù)庫SQL格式。

    到這個(gè)時(shí)候,數(shù)據(jù)庫里面的數(shù)據(jù)成功轉(zhuǎn)化成了程序里面的對(duì)象。非常好!按道理說,剩下的OO之路就該順理成章了。但是,很不幸,我不知道該怎樣用這些Value Object,接下來我就懷著困惑的心情把過程式的代碼嫁接在這個(gè)OO的基礎(chǔ)上了。

    我為每一個(gè)畫面設(shè)計(jì)出了一個(gè)Session Bean,在這個(gè)Session Bean里面封裝了畫面所關(guān)聯(lián)的一切業(yè)務(wù)流程,讓這個(gè)Session Bean調(diào)用一大堆Value Object開始干活。在Session Bean和頁面之間,我沒有讓他們直接調(diào)用,因?yàn)閾?jù)公司的牛人說:“頁面直接調(diào)用業(yè)務(wù)代碼不好,耦合性太強(qiáng)。”這句話沒錯(cuò),但是我對(duì)“業(yè)務(wù)代碼”的理解實(shí)在有問題,于是就硬生生的造出一個(gè)Helper來,阻擋在頁面和Session Bean中間,充當(dāng)了一個(gè)傳聲筒的角色。

    于是在開發(fā)中就出現(xiàn)了下面這副景象:每當(dāng)設(shè)計(jì)發(fā)生變更,我就要修改數(shù)據(jù)庫的設(shè)計(jì),用代碼生成工具重新生成Value Object,然后重新修改Session Bean里面的業(yè)務(wù)流程,按照新的參數(shù)和返回值修改Helper的代碼,最后修改頁面的調(diào)用代碼,修改頁面樣式。

    實(shí)際情況比我現(xiàn)在說起來復(fù)雜的多。比如Value Object的修改,程序規(guī)模越來越大以后,我為了避免出現(xiàn)內(nèi)存的大量占用和效率的下降,不得不把一些數(shù)據(jù)庫查詢的邏輯寫到了Gateway和Value Object里面,于是在發(fā)生變更的時(shí)候,我還要手工修改代碼生成工具生成的Gateway和Value Object。這樣的維護(hù)十分麻煩,這使我困惑OO到底有什么好處。我在這個(gè)項(xiàng)目中用OO方式解決了很多問題,而這些問題都是由OO本身造成的。

    另一個(gè)比較大的問題出在Struts上。投資方為系統(tǒng)設(shè)計(jì)了很靈活的界面,界面上的所有元素都是可以配置出來,包括位置、數(shù)據(jù)來源、讀寫屬性。并且操作員的權(quán)限可以精確到每一個(gè)查看、修改的動(dòng)作,可以控制每一個(gè)控件的讀寫操作。于是他們希望使用Struts。Struts框架的每一個(gè)Action恰好對(duì)應(yīng)一個(gè)操作,只需要自己定義Action和權(quán)限角色的關(guān)系,就可以實(shí)現(xiàn)行為的權(quán)限控制。但是我錯(cuò)誤的理解了Struts的用法,我為每一個(gè)頁面設(shè)計(jì)了一個(gè)Action,而不是為每一個(gè)行為設(shè)計(jì)一個(gè)Action,這樣根本就無法做到他們想要的權(quán)限控制方式。他們很快發(fā)現(xiàn)了我的問題,于是發(fā)來了一個(gè)說明書,向我介紹Struts的正確使用方式。說明書打印出來厚厚的一本,我翻了一天,終于知道了錯(cuò)在什么地方。但是一大半畫面已經(jīng)生米煮成熟飯,再加上我的Session Bean里面的流程又是按畫面來封裝的,于是只能改造小部分能改造的畫面,權(quán)限問題另找辦法解決了。

    下面就是這個(gè)系統(tǒng)的全貌,場面看上去還是蔚為壯觀的:

    系統(tǒng)經(jīng)歷過數(shù)次較大的修改,這個(gè)框架不但沒有減輕變更的壓力,反而使得變更困難加大了。到后來,因?yàn)闃I(yè)務(wù)流程的變更的越來越復(fù)雜,現(xiàn)有流程無法修改,只得用一些十分曲折的方式來實(shí)現(xiàn),運(yùn)行效率越來越低。由于結(jié)構(gòu)過于復(fù)雜,根本沒有辦法進(jìn)行性能上的優(yōu)化。為了平衡效率的延緩,不得不把越來越多的Value Object放在了內(nèi)存中緩存起來,這又造成了內(nèi)存占用的急劇增加。到后期調(diào)試程序的時(shí)候,服務(wù)器經(jīng)常出現(xiàn)“Out of memory”異常,各類對(duì)象龐大繁多,系統(tǒng)編譯部署一次需要10多分鐘。投資方原先是希望我們使用JUnit來進(jìn)行單元測試,但是這樣的流程代碼測試起來困難重重,要花費(fèi)太多的時(shí)間和人手,也只得作罷。此外他們?cè)O(shè)計(jì)的很多功能其實(shí)都沒有實(shí)現(xiàn),并且似乎以后也很難再實(shí)現(xiàn)了。設(shè)計(jì)中預(yù)想的很多優(yōu)秀特點(diǎn)在這樣框架中一一消失,大家無奈的接受一個(gè)失望的局面。

    在我離開公司兩年以后,這個(gè)系統(tǒng)仍然在持續(xù)開發(fā)中。新的模塊不斷的添加,框架上不斷添加新的功能點(diǎn)。有一次遇到仍然在公司工作的同事,他們說:“還是原來那個(gè)框架,前臺(tái)加上一個(gè)個(gè)的JSP,然后后臺(tái)加上一個(gè)個(gè)的Value Object,中間的Session Bean封裝越來越多的業(yè)務(wù)流程。”

    我的第一個(gè)OO系統(tǒng)的設(shè)計(jì),表面上使用了OO技術(shù),實(shí)際上分析設(shè)計(jì)還是過程主導(dǎo)的方式。設(shè)計(jì)的時(shí)候過多、過早、過深入的考慮了需要做哪些畫面,畫面上應(yīng)該有哪些功能點(diǎn),功能點(diǎn)的數(shù)據(jù)流程。再加上一個(gè)復(fù)雜的OO框架,名目繁多的對(duì)象,不僅無法做到快速的開發(fā),靈活的適應(yīng)需求的變化,反而使系統(tǒng)變得更加復(fù)雜,功能修改更加的麻煩了。

    在面條式代碼的時(shí)代,很多人用匯編代碼寫出了一個(gè)個(gè)優(yōu)秀的程序。他們利用一些工具,或者共同遵守一些特別的規(guī)約,采用一致的變量命名方式,規(guī)范的代碼注釋,可以使一個(gè)龐大的開發(fā)團(tuán)隊(duì)運(yùn)行的井井有條。人如果有了先進(jìn)的思想,工具在這些人的手中就可以發(fā)揮出超越時(shí)代的能量。而我設(shè)計(jì)的第一個(gè)OO系統(tǒng),恰好是一個(gè)相反的例子。

    實(shí)際上,面向?qū)ο蟮淖瞠?dú)特之處,在于他分析需求的方式。按照這樣的方式,不要過分的糾纏于程序的畫面、操作的過程,數(shù)據(jù)的流程,而是要更加深入的探索需求中的一些重要概念。下面,我們就通過一個(gè)實(shí)例看一看,怎樣去抓住需求中的這些重要概念,并且運(yùn)用OO方法把他融合到程序設(shè)計(jì)中。也看看OO技術(shù)是如何幫助開發(fā)人員控制程序的復(fù)雜度,讓大家工作的更加簡單、高效。

    我們來看看一個(gè)通信公司的賬務(wù)系統(tǒng)的開發(fā)情況。最開始,開發(fā)人員找到電信公司的職員詢問需求的情況。電信公司的職員是這樣說的:

    “賬務(wù)系統(tǒng)主要做這樣幾件事情:每個(gè)月1日凌晨按照用戶使用情況生成賬單,然后用預(yù)存沖銷這個(gè)賬單。還要受理用戶的繳費(fèi),繳費(fèi)后可以自動(dòng)沖銷欠費(fèi)的賬單,欠費(fèi)用戶繳清費(fèi)用之后要發(fā)指令到交換上,開啟他的服務(wù)。費(fèi)用繳清以后可以打印發(fā)票,發(fā)票就是下面這個(gè)樣子。”

    經(jīng)過一番調(diào)查,開發(fā)人員設(shè)計(jì)了下面幾個(gè)主要的流程:

    1、 出賬:根據(jù)一個(gè)月內(nèi)用戶的消費(fèi)情況生成賬單;

    2、 銷賬:沖銷用戶賬戶上的余額和賬單;

    3、 繳費(fèi):用戶向自己的賬戶上繳費(fèi),繳清欠費(fèi)后打印發(fā)票。

    弄清了流程,接著就設(shè)計(jì)用戶界面來實(shí)現(xiàn)這樣的流程。下面是其中一個(gè)數(shù)據(jù)查詢界面,分為兩個(gè)部分:上半部分是繳費(fèi)信息,記錄了用戶的繳費(fèi)歷史;下半部分是賬單信息,顯示賬單的費(fèi)用和銷賬情況。

    界面上的數(shù)據(jù)一眼看起來很復(fù)雜,其實(shí)結(jié)合出賬、繳費(fèi)、銷賬的流程講解一下,是比較容易理解的。下面簡單說明一下。

    繳費(fèi)的時(shí)候,在繳費(fèi)信息上添加一條記錄,記錄下繳費(fèi)金額。然后查找有沒有欠費(fèi)的賬單,如果有就做銷賬。沖抵欠費(fèi)的金額記錄在“欠費(fèi)金額”的位置。如果欠費(fèi)時(shí)間較長,就計(jì)算滯納金,記錄在“滯納金”的位置上。沖銷欠費(fèi)以后,剩余的金額記錄在“預(yù)存款”的位置上。“其他費(fèi)用”這個(gè)位置是預(yù)留的,目前沒有作用。

    每個(gè)月出賬的時(shí)候,在賬單信息里面加上一條記錄,記錄下賬單的應(yīng)收和優(yōu)惠,這兩部分相減就是賬單的總金額。然后檢查一下賬戶上有沒有余額,如果有就做銷賬。銷賬的時(shí)候,預(yù)存款沖銷的部分記錄在“預(yù)存劃撥”的位置,如果不足以沖抵欠費(fèi),賬單就暫時(shí)處于“未繳”狀態(tài)。等到下次繳費(fèi)的時(shí)候,沖銷的金額再記錄到“新交款”的位置。等到所有費(fèi)用繳清了,賬單狀態(tài)變成“已繳”。

    銷賬的流程就這樣融合在繳費(fèi)和出賬的過程中。

    看起來一切成功搞定了,最重要的幾個(gè)流程很明確了,剩下的事情無疑就像砍瓜切菜一樣。無非是繞著這幾個(gè)流程,設(shè)計(jì)出其他更多的流程。現(xiàn)在有個(gè)小問題:打印發(fā)票的時(shí)候,發(fā)票的右側(cè)需要有上次結(jié)余、本次實(shí)繳、本次話費(fèi)、本次結(jié)余這幾個(gè)金額。

    上次結(jié)余:上個(gè)月賬單銷賬后剩下來的金額,這個(gè)容易理解;

    本次結(jié)余:當(dāng)前的賬單銷賬后剩下的金額,這個(gè)也不難;

    本次話費(fèi):這是賬單的費(fèi)用,還是最后一次完全銷賬時(shí)的繳費(fèi),應(yīng)該用哪一個(gè)呢?

    本次繳費(fèi):這個(gè)和本次話費(fèi)有什么區(qū)別,他在哪里算出來?

    帶著問題,開發(fā)者去問電信公司的職員。開發(fā)者把他們?cè)O(shè)計(jì)的界面指點(diǎn)給用戶看,向他說明了自己的設(shè)計(jì)的這幾個(gè)流程,同時(shí)也說出了自己的疑問。用戶沒有直接回答這個(gè)疑問,卻提出了另一個(gè)問題:

    “繳費(fèi)打發(fā)票這個(gè)流程并不總是這樣的,繳費(fèi)以后不一定立刻要打印發(fā)票的。我們的用戶可以在銀行、超市這樣的地方繳話費(fèi),幾個(gè)月以后才來到我們這里打印發(fā)票。并且繳費(fèi)的時(shí)間和銷賬的時(shí)間可以相距很長的,可以先繳納一筆話費(fèi),后面幾個(gè)月的賬單都用這筆錢銷賬;也可以幾個(gè)月都不繳費(fèi),然后繳納一筆費(fèi)用沖銷這幾個(gè)賬單。你們?cè)O(shè)計(jì)的這個(gè)界面不能很好的體現(xiàn)用戶的繳費(fèi)和消費(fèi)情況,很難看出來某一次繳費(fèi)是在什么時(shí)候用完的。必須從第一次、或者最后一次繳費(fèi)余額推算這個(gè)歷史,太麻煩了。還有,‘預(yù)存劃撥’、‘新交款’這兩個(gè)概念我們以前從來沒有見過,對(duì)用戶解釋起來肯定是很麻煩的。”

    開發(fā)人員平靜了一下自己沮喪(或憤怒)的心情,仔細(xì)想一想,這樣的設(shè)計(jì)確實(shí)很不合理。如果一個(gè)會(huì)計(jì)記出這樣的賬本來,他肯定會(huì)被老板開除的。

    看起來流程要改,比先前設(shè)計(jì)的更加靈活,界面也要改。就好像原先蓋好的一棟房子忽然被捅了幾個(gè)窟窿,變得四處透風(fēng)了。還有,那四個(gè)數(shù)值到底應(yīng)該怎樣計(jì)算出來呢?我們先到走廊上去呼吸兩口新鮮空氣,然后再回來想想吧。

    現(xiàn)在,讓我們先忘記這幾個(gè)變化多端的流程,花一點(diǎn)時(shí)間想一想最基本的幾個(gè)概念吧。系統(tǒng)里面最顯而易見的一個(gè)概念是什么呢?沒錯(cuò),是賬戶(Account)。賬戶可以繳費(fèi)和消費(fèi)。每個(gè)月消費(fèi)的情況是記錄在一個(gè)賬單(Bill)里面的。賬戶和賬單之間是一對(duì)多的關(guān)系。此外,賬戶還有另一個(gè)重要的相關(guān)的概念:繳費(fèi)(Deposit)。賬戶和繳費(fèi)之間也是一對(duì)多的關(guān)系。在我們剛才的設(shè)計(jì)中,這些對(duì)象是這樣的:

    這個(gè)設(shè)計(jì)看來有些問題,使用了一些用戶聞所未聞的概念(預(yù)存劃撥,新交款)。并且他分離了繳費(fèi)和消費(fèi),表面上很清楚,實(shí)際上使賬單的查詢變得困難了。在實(shí)現(xiàn)一些功能的時(shí)候確實(shí)比較簡單(比如繳費(fèi)和銷賬),但是另一些功能變得很困難(比如打印發(fā)票)。問題到底在什么地方呢?

    涉及到賬務(wù)的行業(yè)有很多,最容易想到的也許就是銀行了。從銀行身上,我們是不是可以學(xué)到什么呢?下面是一個(gè)銀行的存折,這是一個(gè)委托收款的賬號(hào)。用戶在賬戶上定期存錢,然后他的消費(fèi)會(huì)自動(dòng)從這里扣除。這個(gè)情景和我們需要實(shí)現(xiàn)的需求很相似。可以觀察一下這個(gè)存折,存入和支取都是記錄在同一列上的,在支出或者存入的右側(cè)記錄當(dāng)時(shí)的結(jié)余。

    有兩次賬戶上的金額被扣除到0,這時(shí)候金額已經(jīng)被全部扣除了,但是消費(fèi)還沒有完全沖銷。等到再次存入以后,會(huì)繼續(xù)支取。這種記賬的方式就是最基本的流水賬,每一條存入和支出都要記錄為一條賬目(Entry)。程序的設(shè)計(jì)應(yīng)該是這樣:

    這個(gè)結(jié)構(gòu)看上去和剛才那個(gè)似乎沒有什么不同,其實(shí)差別是很大的。上面的那個(gè)Deposit只是繳費(fèi)記錄,這里的Entry是賬目,包括繳費(fèi)、扣費(fèi)、滯納金……所有的費(fèi)用。銷賬扣費(fèi)的過程不應(yīng)該記錄在賬單中,而是應(yīng)該以賬目的形式記錄下來。Account的代碼片段如下:

     

    public class Account
    {
        
    public Bill[] GetBills()
        {
            
    //按時(shí)間順序返回所有的賬單
        }
        
        
    public Bill GetBill(DateTime month)
        {
            
    //按照月份返回某個(gè)賬單
        }
        
        
    public Entry[] GetEntrees()
        {
            
    //按時(shí)間順序返回所有賬目
        }
        
        
    public void Pay(float money)
        {
            
    //繳費(fèi)
            
    //先添加一個(gè)賬目,然后沖銷欠費(fèi)的賬單
        }
        
        
    public Bill GenerateBill(DateTime month)
        {
            
    //出賬
            
    //先添加一個(gè)賬單,然后用余額沖銷這個(gè)賬單
        }
        
        
    public float GetBalance()
        {
            
    //返回賬戶的結(jié)余
            
    //每一條賬目的金額總和,就是賬戶的結(jié)余
        }
        
        
    public float GetDebt()
        {
            
    //返回賬戶的欠費(fèi)
            
    //每一個(gè)賬單的欠費(fèi)金額綜合,就是賬戶的欠費(fèi)
        }
    }

    Entry有很多種類型(存入、支取、滯納金、贈(zèng)送費(fèi)),可以考慮可以為每一種類型創(chuàng)建一個(gè)子類,就像這樣:

    搞成父子關(guān)系看起來很復(fù)雜、麻煩,并且目前也看不出將這些類型作為Entry的子類有哪些好處。所以我們決定不這樣做,只是簡單的把這幾種類型作為Entry的一個(gè)屬性。Entry的代碼片段如下:

    public class Entry
    {
        
    public DateTime GetTime()
        {
            
    //返回賬目發(fā)生的時(shí)間
        }
        
        
    public float GetValue()
        {
            
    //返回賬目的金額
        }
        
        
    public EntryType GetType()
        {
            
    //返回賬目的類型(存入、扣除、贈(zèng)送、滯納金)
        }
        
        
    public string GetLocation()
        {
            
    //返回賬目發(fā)生的營業(yè)廳
        }
        
        
    public Bill GetBill()
        {
            
    //如果這是一次扣除,這里要返回相關(guān)的賬單
        }
    }

    Entry是一個(gè)枚舉類型,代碼如下:

    public enum EntryType
    {
        Deposit 
    = 1,
        Withdrawal 
    = 2,
        Penalty 
    = 3,
        Present 
    = 4
    }

    下面的界面顯示的就是剛才那個(gè)賬戶的賬目。要顯示這個(gè)界面只需要調(diào)用Account的GetEntrees方法,得到所有的賬目,然后按時(shí)間順序顯示出來。這個(gè)界面上的消費(fèi)情況就明確多了,用戶很容易弄明白某個(gè)繳費(fèi)是在哪幾個(gè)月份被消費(fèi)掉的。

    并且,發(fā)票上的那幾個(gè)一直搞不明白的數(shù)值也有了答案。比如2005年6月份的發(fā)票,我們先看看2005年6月份銷賬的所有賬目(第六行、第八行),這兩次一共扣除73.66元,這個(gè)金額就是本次消費(fèi);兩次扣除之間存入200元,這個(gè)就是本次實(shí)繳;第五行的結(jié)余是17.66元,這就是上次結(jié)余;第八行上的結(jié)余是144元,這個(gè)就是本次結(jié)余。

    用戶檢查了這個(gè)設(shè)計(jì),覺得這樣的費(fèi)用顯示明確多了。盡管一些措辭不符合習(xí)慣的業(yè)務(wù)詞匯,但是他們的概念都是符合的。并且上次還有一個(gè)需求沒有說:有時(shí)候需要把多個(gè)月份的發(fā)票合在一起打印。按照這樣的賬目表達(dá)方式,合并的發(fā)票數(shù)值也比較容易搞清楚了。明確了這樣的對(duì)象關(guān)系,實(shí)現(xiàn)這個(gè)需求其實(shí)很容易。

    面向?qū)ο蟮脑O(shè)計(jì)就是要這樣,不要急于確定系統(tǒng)需要做哪些功能點(diǎn)和哪些界面,而是首先要深入的探索需求中出現(xiàn)的概念。在具體的流程不甚清楚的情況下,先把這些概念搞清楚,一個(gè)一個(gè)的開發(fā)出來。然后只要把這些做好的零件拿過來,千變?nèi)f化的流程其實(shí)就變得很簡單了,一番搭積木式的裝配就可以比較輕松的實(shí)現(xiàn)。

    另一個(gè)重要的類型也漸漸清晰的浮現(xiàn)在我們的眼前:賬單(Bill)。他的代碼片段如下:

    public class Bill
    {
        
    public DateTime GetBeginTime()
        {
            
    //返回賬單周期的起始時(shí)間
        }
        
        
    public DateTime GetEndTime()
        {
            
    //返回賬單周期的終止時(shí)間
        }
        
        
    public Fee GetFee()
        {
            
    //返回賬單的費(fèi)用
        }
        
        
    public float GetPenalty()
        {
            
    //返回賬單的滯納金
        }
        
        
    public void CaculatePenalty()
        {
            
    //計(jì)算賬單的滯納金
        }
        
        
    public float GetPaid()
        {
            
    //返回已支付金額
        }
        
        
    public float GetDebt()
        {
            
    //返回欠費(fèi)
            
    //賬單費(fèi)用加上滯納金,再減去支付金額,就是欠費(fèi)
            return GetFee().GetValue() + GetPanalty() - GetPaid();
        }
        
        
    public Entry GetEntrees()
        {
            
    //返回相關(guān)的存入和支取的賬目
        }
        
        
    public Bill Merge(Bill bill)
        {
            
    //合并兩個(gè)賬單,返回合并后的賬單
            
    //合并后的賬單可以打印在一張發(fā)票上
        }
    }

    Bill類有兩個(gè)與滯納金有關(guān)的方法,這使開發(fā)者想到了原先忽略的一個(gè)流程:計(jì)算滯納金。經(jīng)過與電信公司的確認(rèn),決定每個(gè)月進(jìn)行一次計(jì)算滯納金的工作。開發(fā)人員寫了一個(gè)腳本,先得到系統(tǒng)中所有的欠費(fèi)賬單,然后一一調(diào)用他們的CaculatePenalty方法。每個(gè)月將這個(gè)腳本執(zhí)行一次,就可以完成滯納金的計(jì)算工作。

    Bill對(duì)象中有賬戶的基本屬性和各級(jí)賬目的金額和銷賬的情況,要打印發(fā)票,只有這些數(shù)值是不夠的。還要涉及到上次結(jié)余、本次結(jié)余和本次實(shí)繳,這三個(gè)數(shù)值是需要從賬目中查到的。并且發(fā)票有嚴(yán)格的格式要求,也不需要顯示費(fèi)用的細(xì)節(jié),只要顯示一級(jí)和二級(jí)的費(fèi)用類就可以了。應(yīng)該把這些東西另外封裝成一個(gè)類:發(fā)票(Invoice):

    通信公司后來又提出了新的需求:有些賬號(hào)和銀行簽訂了托收協(xié)議,每個(gè)月通信公司打印出這些賬戶的托收單交給銀行,銀行從個(gè)人結(jié)算賬戶上扣除這筆錢,再把一個(gè)扣費(fèi)單交給通信公司。通信公司根據(jù)這個(gè)扣費(fèi)單沖銷用戶的欠費(fèi)。于是開發(fā)人員可以再做一個(gè)托收單(DeputyBill):

    賬單中的GetFee方法的返回值類型是Fee,F(xiàn)ee類型包含了費(fèi)用的名稱、金額和他包含的其他費(fèi)用。例如下面的情況:

    我們可以用這樣的一個(gè)類來表示費(fèi)用(Fee),一個(gè)費(fèi)用可以包含其他的費(fèi)用,他的金額是子費(fèi)用的金額和。代碼片段如下:

    public class Fee
    {
        
    private float valuee = 0;
        
        
    public string GetName()
        {
            
    //返回費(fèi)用的名稱
        }
        
        
    public bool HasChildren()
        {
            
    //該費(fèi)用類型是否有子類型
        }
        
        
    public Fee[] GetChildren()
        {
            
    //返回該費(fèi)用類型的子類型
        }
        
        
    public float GetValue()
        {
            
    //返回費(fèi)用的金額
            if (HasChildren())
            {
                
    float f = 0;
                Fee[] children 
    = GetChildren();
                
    for (int i = 0; i < children.Length; i ++)
                {
                    f 
    += children[i].GetValue();
                }
                
    return f;
            }
            
    else
            {
                
    return valuee;
            }
        }
    }

    現(xiàn)在開發(fā)者設(shè)計(jì)出了這么一堆類,構(gòu)成軟件系統(tǒng)的主要零件就這么制造出來了。下面要做的就是把這些零件串在一起,去實(shí)現(xiàn)需要的功能。OO設(shè)計(jì)的重點(diǎn)就是要找到這些零件。就像是設(shè)計(jì)一輛汽車,僅僅知道油路、電路、傳動(dòng)的各項(xiàng)流程是不夠的,重要的是知道造一輛汽車需要先制造哪些零件。要想正確的把這些零件設(shè)計(jì)出來不是一件容易的事情,很少有開發(fā)者一開始就了解系統(tǒng)的需求,設(shè)計(jì)出合理的對(duì)象關(guān)系。根本的原因在于領(lǐng)域知識(shí)的貧乏,開發(fā)者和用戶之間也缺乏必要的交流。很多人在軟件開發(fā)的過程中才漸漸意識(shí)到原來的設(shè)計(jì)中存在一些難受的地方,然后探索下去,才知道了正確的方式,這就是業(yè)務(wù)知識(shí)的一個(gè)突破。不幸的是,當(dāng)這個(gè)突破到來的時(shí)候,程序員經(jīng)常是已經(jīng)忙得熱火朝天,快把代碼寫完了。要把一切恢復(fù)到正常的軌道上,需要勇氣,時(shí)間,有遠(yuǎn)見的領(lǐng)導(dǎo)者,也需要有運(yùn)氣。

    posted on 2007-10-13 22:14 zongxing 閱讀(264) 評(píng)論(0)  編輯  收藏 所屬分類: 設(shè)計(jì)模式

    只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。


    網(wǎng)站導(dǎo)航:
     
    主站蜘蛛池模板: 一区二区在线免费视频| 亚洲高清国产拍精品青青草原| 亚洲人成人网站在线观看| 免费看内射乌克兰女| 国产又大又长又粗又硬的免费视频| 亚洲熟妇无码一区二区三区导航 | 亚洲熟妇无码一区二区三区导航| 免费无码黄动漫在线观看| 一二三区免费视频| 亚洲午夜精品一区二区| 最近的中文字幕大全免费版 | 国产三级在线观看免费| 亚洲国产成人久久77| 国产又黄又爽又大的免费视频 | 女人18毛片水最多免费观看| 亚洲国产AV一区二区三区四区 | 亚洲欧美乱色情图片| 国产公开免费人成视频| fc2免费人成在线| 亚洲精品福利视频| 日韩电影免费在线观看视频| 免费看黄网站在线看| 久久久久亚洲AV无码网站| 无码少妇一区二区浪潮免费| 另类专区另类专区亚洲| 亚洲成A人片在线观看无码不卡| 亚洲人成免费网站| 全部一级一级毛片免费看| 99亚洲精品高清一二区| 日本一道本高清免费| 嫩草影院在线播放www免费观看| 亚洲av无码国产综合专区 | 亚洲国产日韩在线一区| 亚洲精品高清一二区久久| 在线永久看片免费的视频| 欧洲美女大片免费播放器视频| 久久精品国产亚洲AV无码偷窥 | 亚洲免费福利视频| 黄+色+性+人免费| 亚洲精品美女视频| 亚洲人成网站免费播放|