System Software
圖5.3 包含中間件的層次結構
5.1.2 客戶機/服務器結構
讓我們先回顧一下早期的電話系統。貝爾(Alexander Graham Bell)于1876年申請了電話專利。那時期的電話必須一對一對地賣,用戶自己在兩個電話之間拉一根線。如果一個電話用戶想和其它幾個電話用戶通話,他必須拉n根單獨的線到每個人的房子里。于是在很短的時間內,城市里到處都是穿過房屋和樹木的混亂的電話線。很明顯,企圖把所有的電話完全互聯(如圖5.4(a)所示)是行不通的。
貝爾電話公司在1878年開辦了第一個交換局。公司為每個客戶架設一條線。打電話時,客戶搖動電話的曲柄使電話公司辦公室的鈴響起來,操作員聽到鈴聲以后根據要求將呼叫方和被呼叫方用跳線手工連接起來。這種集中交換式的模型如圖5.4(b)所示。很快地,貝爾系統的交換局就出現在各地。人們又要求能打城市間的長途電話,就出現了二級交換局,以后進一步發展為多個二級交換局。[Tanenbaum 1998]
交換局
5.4(a)完全互聯的電話系統 5.4(b)集中交換式的電話系統
如果將圖5.4(b)中的電話看成是客戶程序,將中心的交換局看成是服務程序,那么圖5.4(b)就是典型的客戶機/服務器結構。注意這里客戶機和服務器都是指軟件而不是指硬件(一臺計算機可以放多個客戶機和服務器軟件)。
客戶機/服務器結構存在兩個顯然的優點:
(1)以集中的方式高效率地管理通訊。前面講電話系統的故事就是要說明這一點。
(2)可以共享資源。比如在信息管理系統中,服務器將信息集中起來,任何客戶機都可以通過訪問服務器而獲得所需的信息。
客戶機和服務器之間的通訊以“請求——響應”的方式進行?蛻魴C先向服務器發起“請求”(Request),服務器再響應(Response)這個請求,如圖5.5所示。
請求
客戶機
服務器
響應
圖5.5 Client和Server之間的通訊以“請求——響應”的方式進行
采用“請求——響應”這種通訊方式的基本動機是為了解決“聚集”(Rendezvous)問題。為了理解這一個問題,設想一個人試圖在分離的機器上啟動兩個程序并讓它們進行通訊。還需記住,計算機的運行速度要比人的操作速度高出許多數量級。在他啟動第一個程序后,該程序開始執行并向對等程序發送消息。在幾個微秒內,它便發現對等程序還不存在,于是就發出一條錯誤消息,然后退出。此后,他啟動了第二個程序。不幸的是,當第二個程序開始執行時,它也找不到第一個程序(早已退出)。即使這兩個程序連續地重新試著通訊,但由于它們的執行速度那么高,以致于它們在同一瞬間聯系上的概率非常低。在客戶機/服務器結構中,服務器在啟動后必須(無限期地)等待客戶機的“請求”,因此就形成了“請求——響應”的通訊方式。
在Inte.net/Intranet領域,目前“瀏覽器—Web 服務器—數據庫服務器” 結構是一種非常流行的客戶機/服務器結構,如圖5.6所示。這種結構最大的優點是:客戶機統一采用瀏覽器,這不僅讓用戶使用方便,而且使得客戶機端不存在維護的問題。當然,軟件開發布和維護的工作不是自動消失了,而是轉移到了Web 服務器端。在Web 服務器端,程序員要用腳本語言編寫響應頁面。例如用Microsoft的ASP語言查詢數據庫服務器,將結果保存在Web 頁面中,再由瀏覽器顯示出來。
客戶機
數據庫
服務器
Web 服務器
ASP Engine
瀏覽器
HTTP 請求
查詢
HTTP 響應
圖5.6 “瀏覽器—Web 服務器—數據庫服務器”結構
5.2 模 塊 設 計
在設計好軟件的體系結構后,就已經在宏觀上明確了各個模塊應具有什么功能,應放在體系結構的哪個位置。我們習慣地從功能上劃分模塊,保持“功能獨立”是模塊化設計的基本原則。因為,“功能獨立”的模塊可以降低開發、測試、維護等階段的代價。但是“功能獨立”并不意味著模塊之間保持絕對的孤立。一個系統要完成某項任務,需要各個模塊相互配合才能實現,此時模塊之間就要進行信息交流。
比如手和腳是兩個“功能獨立”的模塊。沒有腳時,手照樣能干活。沒有手時,腳仍可以走路。但如果希望跑得快,那么邁左腳時一定要伸右臂甩左臂,邁右腳時則要伸左臂甩右臂。在設計一個模塊時不僅要考慮“這個模塊就該提供什么樣的功能”,還要考慮“這個模塊應該怎樣與其它模塊交流信息”。
本節將論述評價模塊設計優劣的三個特征因素:“信息隱藏”、“內聚與耦合”和“封閉——開放性”。
5.2.1 信息隱藏
在一節不和諧的課堂里,老師嘆氣道:“要是坐在后排聊天的同學能象中間打牌的同學那么安靜,就不會影響到前排睡覺的同學!
這個故事告訴我們,如果不想讓壞事傳播開來,就應該把壞事隱藏起來,“家丑不可外揚”就是這個道理。為了盡量避免某個模塊的行為去干擾同一系統中的其它模塊,在設計模塊時就要注意信息隱藏。應該讓模塊僅僅公開必須要讓外界知道的內容,而隱藏其它一切內容。
模塊的信息隱藏可以通過接口設計來實現。一個模塊僅提供有限個接口(Interface),執行模塊的功能或與模塊交流信息必須且只須通過調用公有接口來實現。如果模塊是一個C++對象,那么該模塊的公有接口就對應于對象的公有函數。如果模塊是一個COM對象,那么該模塊的公有接口就是COM對象的接口。一個COM對象可以有多個接口,而每個接口實質上是一些函數的集合。[Rogerson 1999]
美國也許是世界上丑聞最多的國家,因為它追求民主,不懂得“隱藏信息”。但美國又是軟件產業最發達的國家,模塊化設計的方法都是美國人倡導的,他們應該很懂得“隱藏信息”。真是前后矛盾,這些美國佬!
5.2.2 內聚與耦合
內聚(Cohesion)是一個模塊內部各成分之間相關聯程度的度量。耦合(Coupling)是模塊之間依賴程度的度量。內聚和耦合是密切相關的,與其它模塊存在強耦合的模塊通常意味著弱內聚,而強內聚的模塊通常意味著與其它模塊之間存在弱耦合。模塊設計追求強內聚,弱耦合。
一、內聚強度
內聚按強度從低到高有以下幾種類型:
(1)偶然內聚。如果一個模塊的各成分之間毫無關系,則稱為偶然內聚。
(2)邏輯內聚。幾個邏輯上相關的功能被放在同一模塊中,則稱為邏輯內聚。如一個模塊讀取各種不同類型外設的輸入。盡管邏輯內聚比偶然內聚合理一些,但邏輯內聚的模塊各成分在功能上并無關系,即使局部功能的修改有時也會影響全局,因此這類模塊的修改也比較困難。
(3)時間內聚。如果一個模塊完成的功能必須在同一時間內執行(如系統初始化),但這些功能只是因為時間因素關聯在一起,則稱為時間內聚。
(4)過程內聚。如果一個模塊內部的處理成分是相關的,而且這些處理必須以特定的次序執行,則稱為過程內聚。
(5)通信內聚。如果一個模塊的所有成分都操作同一數據集或生成同一數據集,則稱為通信內聚。
(6)順序內聚。如果一個模塊的各個成分和同一個功能密切相關,而且一個成分的輸出作為另一個成分的輸入,則稱為順序內聚。
(7)功能內聚。模塊的所有成分對于完成單一的功能都是必須的,則稱為功能內聚。
二、耦合強度
耦合的強度依賴于以下幾個因素:(1)一個模塊對另一個模塊的調用;(2)一個模塊向另一個模塊傳遞的數據量;(3)一個模塊施加到另一個模塊的控制的多少;(4)模塊之間接口的復雜程度。
耦合按從強到弱的順序可分為以下幾種類型:
(1)內容耦合。當一個模塊直接修改或操作另一個模塊的數據,或者直接轉入另一個模塊時,就發生了內容耦合。此時,被修改的模塊完全依賴于修改它的模塊。
(2)公共耦合。兩個以上的模塊共同引用一個全局數據項就稱為公共耦合。
(3)控制耦合。一個模塊在界面上傳遞一個信號(如開關值、標志量等)控制另一個模塊,接收信號的模塊的動作根據信號值進行調整,稱為控制耦合。
(4)標記耦合。模塊間通過參數傳遞復雜的內部數據結構,稱為標記耦合。此數據結構的變化將使相關的模塊發生變化。
(5)數據耦合。模塊間通過參數傳遞基本類型的數據,稱為數據耦合。
(6)非直接耦合。模塊間沒有信息傳遞時,屬于非直接耦合。
如果模塊間必須存在耦合,就盡量使用數據耦合,少用控制耦合,限制公共耦合的范圍,堅決避免使用內容耦合。
5.2.3 封閉——開放性
如果一個模塊可以作為一個獨立體被其它程序引用,則稱模塊具有封閉性。如果一個模塊可以被擴充,則稱模塊具有開放性。
從字面上看,讓模塊具有“封閉——開放性”是矛盾的,但這種特征在軟件開發過程中是客觀存在的。當著手一個新問題時,我們很難一次性解決問題。應該先縱觀問題的一些重要方面,同時作好以后補充的準備。因此讓模塊存在“開放性”并不是壞事情!胺忾]性”也是需要的,因為我們不能等到完全掌握解決問題的信息后再把程序做成別人能用的模塊。
模塊的“封閉——開放性”實際上對應于軟件質量因素中的可復用性和可擴充性。采用面向過程的方法進行程序設計,很難開發出既具有封閉性又具有開放性的模塊。采用面向對象設計方法可以較好地解決這個問題。
5.3 數據結構與算法設計
學會設計數據結構與算法,可以讓我們編寫出高效率的程序。也許有人要問,在計算機速度日新月異的今天,為什么還需要高效率的程序?
因為我們的雄心與能力是一起增長的,技術進步最快也快不過人們欲望的增長。計算速度和存儲容量上的革新僅僅提供了處理更復雜問題的有效工具,所以高效率的程序永遠不會過時。
設計高效率的程序是基于良好的數據結構與算法,而不是基于編程小技巧。大多數計算機科學系在設置課程時,都重視學習基本的軟件工程原理,以及數據結構與算法設計。
一般說來,數據結構與算法就是一類數據的表示及其相關的操作(這里算法不是指數值計算的算法)。從數據表示的觀點來看,存儲在數組中的一個有序整數表也是一種數據結構。算法是指對數據結構施加的一些操作,例如對一個線性表進行檢索、插入、刪除等操作。一個算法如果能在所要求的資源限制(Resource Constraints)范圍內將問題解決好,則稱這個算法是有效率(Efficient)的。例如一個資源限制可能是“用于存儲數據的內存有限”,或者“允許執行每個子任務所需的時間有限”。一個算法如果比其它已知算法所需要的資源都少,這個算法也被稱為是有效率的。算法的代價(Cost)是指消耗的資源量。一般說來,代價是由一個關鍵資源例如時間或空間來評估的。
毋庸置疑,人們編寫程序是為了解決問題。只有通過預先分析問題來確定必須達到的性能目標,才有希望挑選出正確的數據結構。有相當多的程序員忽視了這一分析過程,而直接選用某一個他們習慣使用的,但是與問題不相稱的數據結構,結果設計出一個低效率的程序。如果使用簡單的設計就能夠達到性能目標時,選用復雜的數據結構也是沒有道理的。
人們對常用的數據結構與算法的研究已經相當透徹,可以歸納出一些設計原則:
(1)每一種數據結構與算法都有其時間、空間的開銷和收益。當面臨一個新的設計問題時,設計者要徹底地掌握怎樣權衡時空開銷和算法有效性的方法。這就需要懂得算法分析的原理,而且還需要了解所使用的物理介質的特性(例如,數據存儲在磁盤上與存儲在內存中,就有不同的考慮)。
(2)與開銷和收益有關的是時間——空間的權衡。通?梢杂酶蟮臅r間開銷來換取空間的收益,反之亦然。時間——空間的權衡普遍地存在于軟件開發的各個階段中。
(3)程序員應該充分地了解一些常用的數據結構與算法,避免不必要的重復設計工作。
(4)數據結構與算法為應用服務。我們必須先了解應用的需求,再尋找或設計與實際應用相匹配的數據結構。[Shaffer 1998]
5.4 用 戶 界 面 設 計
某個人總有辦法讓自己保持心情愉快、信心十足。有一天,他向一名圍棋九段和一名乒乓球世界冠軍挑戰,結果他全勝了。因為他跟圍棋九段打乒乓球,跟乒乓球冠軍下圍棋。用戶界面的編程技術是人們熟悉得不得了的事,我決定講一講比較陌生的“用戶界面設計美學”。
文章來源于領測軟件測試網 http://www.kjueaiud.com/