如何開發一個可複用的軟體系統

來源:互聯網
上載者:User
如何建立一個可複用軟體系統

譯者序:本文是設計模式“Template Method”模板方法的一個延伸,將模板應用到了整個軟體開發產品。首先將軟體產品核心不變的商務邏輯部分抽象出來,對於在不同產品中的不同的部分,核心產品通過鉤子調用鉤子組件重的具體實現,這樣開發不同的系統時,只要更改鉤子組件的內容就實現了不同的產品。開發的關鍵就是抽象核心產品的功能。當然這種開發思想也是局限的,不是適合所有的開發項目(抽象所有項目的核心是沒有意義的),這種開發思路比較適合針對於一個領域的產品開發,同一個領域抽象出來的核心產品才有實際的使用價值。



建立可複用的組件是學習如何建立可複用程式的第一步――――by Mike Cahn

如果你連續的開發幾個軟體項目,將會發現自己編寫了許多重複功能的代碼,當你認識到這點,你肯定會產生複用代碼的想法。事實上很多Team Dev都建立了自己的程式碼程式庫和組建庫以方便的將他們應用到新的項目中。下面我將講述如何將複用提高到一個更高的層級:複用整個應用程式

如果你開發一個particular business函數或者specific vertical market,你就會發現客戶的需求之間存在著很多重複使用核心代碼的組件的重疊,但是需求之間的不同點卻要求你不得不為每一個客戶開發一個全新的系統

尤其不幸的是重寫會增加設計和開發時間還有更複雜的維護,例如如果你存在多個版本,你就會面對這樣的問題:傳播一個bug修複、產品增加功能要求為每一個客戶指定更改和測試,複用組建協助等!但是如果所有應用程式的核心邏輯是相同的,那為什麼不更好的複用這部分核心邏輯呢?

答案是你當然可以做到,開發一個核心產品,然後在不同的功能層之間你都可以使用它而沒有必要改變核心代碼

什麼是鉤子?

一個鉤子就是你設定客戶執行個體化代碼的地方,它是一個叢核心產品調用到定製組件的方法,一個客戶化層(鉤子組件)需要知道正在發生什麼的地方,或者需要修改核心產品行為的地方,你的核心產品都可以使用鉤子調用

典型的應用是:一個鉤子方法通過資料和鉤子組件當前的上下文關聯性進行調用,鉤子組件處理這個調用以返回正確的資訊

當設計核心產品和鉤子組件時,你要讓它們盡量保持松耦合關係,將來如果核心產品發生變化不會影響已經配置的鉤子組件。你也要必須保證在配置鉤子組件時,核心產品增加計劃範圍之外新的鉤子調用後 鉤子組件也能執行正確的事件

確定核心功能:

使用鉤子建立一個核心應用程式時,評定的第一步就是確定哪些東西將要作為核心來處理,如果核心產品設定了太少的功能,那麼你將不得不針對每一個項目重複執行通用的功能,這樣增加了很大的工作量!重複的代碼操作破壞了核心應用程式開發的目的。但是如果包括了不是所有客戶都有的通用功能將是更糟糕事情,所有開發項目在使用核心代碼時都會產生很多問題,最後你將不得不修改核心產品

核心產品後期增加可移植的功能比從中移除本來就不應該加入的功能要有益的多,不過這樣可能會產生過於保守的錯誤!能夠適合核心產品的功能效能夠作為一個可以共用的組件產生,當你做好準備時你也可以移植它

確定核心應用程式功能性(functionality)最關鍵的是定義個一個所有客戶都通用的程式流,讓我們來看一個例子,假設你正在開發三個系統,它們都有一個基於移動工作項目的工作流程,在這三個系統中使用者都可以通過執行“下一步”,從一個工作項目執行到下一個工作項目,下面是這三個系統的詳細不同點:

系統1:這個系統僅僅是移動工作項目到下一步,但是不執行任何操作

系統2:系統在一個授權(legacy)系統中記錄工作項目和操作步驟的詳細資料,同時寫入一個從授權(legacy)的系統到工作項目內部的引用關鍵字

系統3:系統在工作項目移動到下一步之前顯示一個用於使用者更新的複選框,如果使用者標誌的所有必須的操作沒有成功的完成時,系統撤銷操作“下一步”操作




圖表1顯示了功能化是如何在核心產品和三個不同的客戶化層之間劃分的,在這個例子中

客戶化層需要下面的能力:

使用和更新核心產品對象(在這個例子中指工作項目)

用使用者進行互動

改變核心產品的工作流程(比如撤銷當前操作)

串連其他應用程式或者組件

這些需求是很典型的,你要在你的核心產品的所有函數中考慮到

構建鉤子介面:

在鉤子組件內部設定的參數依賴上下文改變,要比維護一堆鉤子介面強的多,所以建立一個通用的鉤子介面是較好的解決方案. 使用變數集合或者參數數組適應你將加入的不同資料類型,在第一個參數中儲存鉤子的標誌符,鉤子組件工作的第一件事就是解釋這個標誌符然後調用相應的函數或者組件,下面的代碼通過使用select …..case 結構實現了這一功能:

列表1:核心產品調用鉤子組件(一個很小的鉤子組件)

'Constants would normally be defined in a module or class
Const NEXTSTEP_START = "NS-START"
Const NEXTSTEP_SELECTED = "NS-SELECTED"
Const HOOK_OK = "OK"
Const HOOK_CANCEL = "CANCEL"
'...

'User just selected Next Step operation
sHookResponse = mobjHookComponent.CallIn(NEXTSTEP_START, _
mobjCurrentItem, mobjCurrentUser, "")
...
'User just selected the Step to move the item to
sHookResponse = mobjHookComponent.CallIn(NEXTSTEP_SELECTED, _
mobjCurrentItem, mobjCurrentUser, sSelectedStep)

Hook component Code:
'Note: Hook constants file would need to be included

Function CallIn(ByVal sHookId as String, vParam1 as Variant, _
vParam2 as Variant, vParam3 as Variant) as String

Dim sReturnValue as String
Dim objWorkitem as clsWorkitem
Dim objUser as clsUser
Dim sSelectedStep as String

On Error Goto CallIn_Handler

Select Case sHookId

Case NEXTSTEP_START
'Processing to be done when NextStep is first invoked
Set objWorkitem = vParam1
Set objUser = vParam2
Call WriteLog( sHookId, "Item ID: " & _
objWorkitem.WorkitemID & ", User ID: " & _
objUser.UserID )
sHookId = HOOK_OK
Case NEXTSTEP_SELECTED
Set objWorkitem = vParam1
Set objUser = vParam2
sSelectedStep = vParam3
Call WriteLog( sHookId, "Item ID: " & _
objWorkitem.WorkitemID & ", User ID: " & _
objUser.UserID & ", Step: " & vParam3 )

'...do whatever processing is required...
sHookId = HOOK_OK

Case else
'Unknown hook, possibly introduced to product
' after this component was written.
'Just allow it, but do nothing.
Call WriteLog( sHookId, "Unrecognised Hook" )
sReturnValue = HOOK_OK
End Select

CallIn = sReturnValue

Exit Function

CallIn_Handler:
'Handle the error...

End Function

Sub WriteLog( sHookID as String, sText as String )
Dim sFullText as String

sFullText = Time$ & " " & sHookID & " " & sText

Debug.Print sFullText

'Log to file used mainly for testing
'and for diagnosing production systems
If IsFileLoggingOn(sHookID) then
'Output log info to text file...
End If
End Sub (to be continue )

你也可以使用你熟悉的com 實現這個功能,他們通過指派調用方法的技術很相似



你在發行新版本的核心產品時必須保證不需要重新編譯已經存在的鉤子組件,所以鉤子組件應該忽視所有沒有被公認的鉤子標誌符而且僅僅返回一個簡單的“成功”代碼(參考代碼清單的case else 部分),通過這個方法,鉤子組件只需為功能需求執行被認可的鉤子調用,如果日後需要為核心產品增加新的鉤子調用點時,既有的鉤子組件不需要改變任何行為就可以增加新的鉤子標誌符

在“下一步”的例子中,核心產品可能在幾個執行點調用鉤子,例如:當使用者請求下一步操作然後選擇步驟名稱再次請求,這兩個例子將會是:

Hook
Param1
Param2
Param3
Param4

Next step Started
"NS-START"
objUser
objWorkitem
""

Next step Step Selected
"NS-SELECTED"
objUser
objWorkitem
sStepName




這樣簡單的一個例子,三個變數(加上鉤子標識符)可能就已經足夠了,可是為了避免了將

來介面改變帶來的麻煩,你總是不得不增加更多的函數,列表1顯示了一個核心產品的簡單

代碼:一個鉤子組件,一個鉤子的調用執行個體

為了執行客戶三個複選框的功能,你應該從鉤子組件項目中添加一個複選框表單,並且通過

下面代碼調用:

Case NEXTSTEP_SELECTED

'...Cast variables and Call WriteLog()...

'Show the checklist
frmChecklist.Show vbModal
If frmCheckList.AllRequiredItemsTicked Then
sReturnValue = HOOK_OK
Else
MsgBox "You have not ticked all the " & _
"required items. " & _
"Next Step cannot continue", vbExclamation
Call WriteLog( sHookId, "Cancel sent back -- " & _
"all required items not ticked" )
sReturnValue = HOOK_CANCEL
End If
' ... more code
鉤子組件影響核心產品:

核心產品為可能發生變化的需求設定點,然後將對他們的控制委託給鉤子組件,接著響應鉤

子組件返回的狀態,

資料對象引用

當鉤子組件接收到一個對資料對象的引用時,核心產品需要捕獲鉤子組件引起了什麼變化,

如果你不想鉤子組件修改一個對象的參數,可以通過使用一個宣傳對象或者鎖對象,或者一

個在調用方法前設定部分特性的對象,如果你允許改變,對象可以驗證他們,或者記錄他們,

當方法返回時通過核心產品驗證他們,例如使用者需要用兩個鉤子更新一個有引用關鍵字的工

作項目

Case NEXTSTEP_SELECTED
'...cast variables and call WriteLog()...
sRefKey = GetMainframeKey(objWorkitem.WorkitemID)
objWorkitem.Reference = sRefKey
sReturnValue = HOOK_OK
然後核心產品可以查詢返回的工作項目對象:

mobjCurrentItem.ResetChangedFlag

sHookResponse = _
mobjHookComponent.CallIn(NEXTSTEP_SELECTED, _
mobjCurrentUser, mobjCurrentItem, sSelectedStep)

If mobjCurrentItem.Changed then
'Take appropriate action, such as validating
'and/or saving the changes...
End If
邏輯控制

鉤子組件可以返回影響核心產品邏輯的值,這種執行方法是很有限的,例如:可以指定

一組鉤子組件返回的標誌(取消當前操作,顯示標準對話方塊),否則你的核心產品和鉤子組

件就會產生緊耦合危險。通過預定義一組客戶之間不需要改表的標誌,你就可以為核心產品

捕獲任何可能返回的值

使用互動

在一個基於windows 表單的典型應用程式中,鉤子組件可以用vb表單發行,當使用者需要時

互動調用

對於web應用程式,核心產品和鉤子組件需要在伺服器上運行,所以vb 表單就不適用了,

不過你可以用下面的幾種方法 :

通過為核心產品返回一個url ,來調用一個新的window瀏覽器,將一個調用瀏覽視窗的

Url 返回給核心產品,所有從這個視窗的後繼請求都能被非核心調用。因為這個視窗是非模

式化的,所以使用者很清楚的在核心產品螢幕和鉤子視窗間進行嵌套

第一步操作的一個變數用一個鉤子的url內容臨時替換核心產品的瀏覽器,這是一個很

有效模式介面,通過螢幕的請求可以被定製的組件捕獲,當控制被返回到核心產品時,控

制可以儲存自己的螢幕

返回一個html 或者xml 給核心產品,可以合并成正在建立的頁面,這使得鉤子介面通

過使用核心產品而變得更好,然而核心產品不得不將請求從螢幕的鉤子部分轉化為相應的定

制組件

實踐:

二進位相容性確保了你的核心產品和鉤子組件總是使用相同的介面,可以在辦公室特定

的電腦上記錄你建立的核心產品,項目小組也經常在網站上建立鉤子組件,使任何電腦

都變得可用。這樣有時候可以引起二進位很難修複的問題,如果你這樣運行,要盡量同步

這兩個建立環境,盡量包含服務元件。如果還是不能正常工作,也可以放棄二進位相容性,

使用更緩慢的後期綁定作為最後的手段

測試:

鉤子組件提供很有用的調試和測試協助,就像示範過的那樣,你的基本鉤子組件分之應該要記錄所有的調用和參數,響應每個鉤子的典型方法是為每一個客戶定製代碼,然而即使是一個內部使用,編寫一個可配置的鉤子組件也是值得的。組件的行為不是硬式編碼,它的響應是通過list2設定檔控制的(使用xml 檔案是因為他可以包含一個可分級的資訊)。你已經編寫了一個可配置的鉤子組件,你為了測試不同的情形而不得不改變它,你將只需要修改這個設定檔。這樣的修改對開發人員是很容易的,對測試者也有很顯著的用處,他們都不用修改代碼。

你的可配置鉤子允許你可以為每一個鉤子標誌符指定不同的傳回值,通過為每一個基本使用者定義行為的例子精確到了一位元。我還添加了一個暫停設定和用於顯示的訊息框,這對類比延遲是很用的,

最後我還添加了一個解釋鉤子組件如何改變傳入參數的設定(請看代碼清單2)

發布:

如果沒有除了日誌記錄以外的功能,一定要記住你必須至少提交和核心產品一致的鉤子組件分支,因為你的核心產品需要能夠調用執行它

更多的參考:

這個鉤子概念是基於”模板”設計模式的,如果你沒有聽過這個設計模式,你應該讀“設計模式”這本書,他們假設你使用的是一個有完全繼承功能的語言,雖然這不是必要的。通過vb.net 我們可以實現,vb60通過一點點努力也可以實現這個目的如果你感覺這個文章有點難,你可以從Stamatakis 編寫的“vb設計模式”開始,雖然它沒有覆蓋所有的模式,但是他對於vb開發人員來說是一個很好的開始





聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.