閱讀359 返回首頁    go 阿裏雲 go 技術社區[雲棲]


《數據結構與抽象:Java語言描述(原書第4版)》一1.2 說明一個包

本節書摘來華章計算機《數據結構與抽象:Java語言描述(原書第4版)》一書中的第1章 ,第1.2節,[美]弗蘭克M.卡拉諾(Frank M. Carrano) 蒂莫西M.亨利(Timothy M. Henry) 著 羅得島大學  新英格蘭理工學院 辛運幃 饒一梅 譯 更多章節內容可以訪問雲棲社區“華章計算機”公眾號查看。
1.2 說明一個包
在用Java實現包之前,需要描述它的數據,並詳細說明對應於包行為的方法。我們將命名方法,選擇它們的參數,確定它們的返回值類型,並寫出注釋來充分描述它們對包數據的影響。當然,我們最終的目的是寫出每個方法的Java頭和注釋,但首先我們用偽代碼來描述方法,然後用統一建模語言(UML)進行表示。
CRC卡的第一個行為引出一個方法,該方法返回包中當前的項數。對應的方法沒有參數,它返回一個整數。使用偽代碼,我們有下列的描述:
image

可以使用UML將方法表示為:
image

且將這行添加到類圖中。
可以使用一個布爾值方法來測試包是否為空,同樣該方法沒有參數。用偽代碼及UML描述這個方法的規格說明
image


image

將這行添加到類圖中。

注:因為通過查看getCurrentSize是否返回0就能檢測包何時為空,所以並不真的需要操作isEmpty。但是它是所謂的**便利方法**(convenience method),所以很多集合都提供這樣一個操作。
現在想向包中添加給定的對象。可以將這個方法命名為add,並有一個表示新項的參數。可以寫出下列偽代碼:
image

我們可能試圖讓add作為void方法,但是會有一些情況,比如如果包滿則不能將新項添加到包中。這些情況下,我們該如何辦呢?

設計決策:當不能添加新項時,方法add將如何處理?
當add不能完成任務時,我們可采取下麵兩種選擇:
什麼也不做。不能添加其他的項,所以忽略這個項並且不改變包。
不改變包,但告訴客戶添加是不可能的。
第一個選擇簡單,但會讓客戶疑惑到底發生了什麼。當然,我們可以規定add的前置條件,即包必須不滿。這樣客戶要負責避免將新項添加到滿包中。
第二個選擇更好一些,且它也不難說明或實現。我們如何告訴客戶添加是否成功?標準Java接口Collection規定,如果添加沒有成功則發生異常。稍後我們再完成這個方法,並且使用另一種方式。顯示一條錯誤信息並不是好的選擇,因為你應該讓客戶決定所有的書麵輸出。因為添加操作或者成功或者不成功,所以我們可以讓方法add返回一個布爾值。

因此,可以用UML規範add方法:

其中T表image
示newEntry的數據類型。

自測題1 假定aBag表示一個有有限容量的空包。寫偽代碼,將用戶提供的字符串添加到包中,直到操作失敗。

有3個動作涉及從包中刪除項:刪除所有的項;刪除任意一項;刪除某個項。假定我們用偽代碼為這些方法命名並說明其參數,如下所示:
image

這些方法的返回類型是什麼?
方法clear可以是一個void方法:我們隻想要一個空包,不獲取它的任何內容。所以,在UML中方法寫為:
image

如果第一個remove方法從包中刪除一項,則該方法可以簡單地返回被刪除的對象。它的返回類型為T,這是包中項的數據類型。在UML中,我們有
image

現在,我們可以處理從返回null的空包中刪除對象了。
如果包中不含有某項,則第二個remove方法不能從包中刪除該項。可以讓方法返回一個布爾值,類似於add那樣,用它來表示成功與否。或者,方法可以返回被刪對象,或者,如果不能刪除這個對象則返回null。下麵是用UML表示的規格說明的兩種可能版本——我們必須二選一:
image

或者
image

如果anEntry等於包中的某項,則這個方法的第一個版本將刪除該項並返回真(true)。即使方法沒有返回被刪除的項,客戶也能有方法的參數anEntry,它等於被刪除的項。故我們選擇這個版本,它與接口Collection是一致的。

自測題2 在一個類內同時具有上麵描述的remove(anEntry)的兩個版本合法嗎?
解釋。
自測題3 在一個類內同時具有remove的兩個版本,一個不帶參數而另一個帶一個參數,這樣合法嗎?解釋。
自測題4 給出自測題1中創建的滿包aBag,寫偽代碼語句,刪除並顯示包中的所有字符串。

其他的動作並不改變包的內容。其中一個動作是計數包中給定對象的出現次數。我們先用偽代碼後用UML說明它,如下所示。
image

另一個方法測試包是否含有給定對象。使用偽代碼和UML給出的規格說明如下所示。
image

自測題5 給定自測題1中創建的滿包aBag,寫偽代碼語句,找出aBag中字符串"Hello"出現的次數,如果有的話。

最後,我們想看看包的內容。不是提供顯示包中項的方法,而是定義一個方法來返回保存這些項的數組。這樣,客戶可以按照自己的意願顯示部分或全部的項。下麵是最後這個方法的規格說明:
image

當方法返回一個數組時,它通常應該定義一個新的數組來返回。我們還將說明這個方法的細節。
當我們為包中的方法提供前麵那些規格說明時,使用UML符號來表示它們。圖1-2顯示了這些結果。

image


注意,CRC卡和UML並不反映所有的細節,例如我們在前麵的討論中提到過的假定和特殊情形。但是,在確定了這樣的條件後,你應該在每個方法的下麵說明該方法應有的動作。
應該寫下你的決策,想讓方法如何動作,就像我們寫在下表中的那樣。然後,可以將這些非形式化的描述放在說明方法的Java注釋中。

image
image

設計決策:當特殊條件出現時會怎樣?
作為類的設計者,必須要做出決定如何處理特殊條件,並將這些決策包含在規格說明中。ADT包的文檔應該反映這些決策和前麵討論的細節。
一般地,可以用幾種方式聲明特殊情形。你的方法可能

  • 假定無效的情形不能發生。這個假定並不像聽起來那麼幼稚。方法可以聲明一種假設(即前置條件),這是客戶必須遵守的限製。然後由客戶檢查在方法調用前這個前置條件是否滿足。例如,方法remove的前置條件可能是包為非空的。注意,客戶可以使用ADT包的其他方法,例如isEmpty和getCurrentSize,來輔助完成這個任務。隻要客戶遵守這個限製,無效的情形就不會發生。
  • 忽略無效情形。當給出無效數據時方法可能簡單到什麼也不做。但是什麼都不做會讓客戶不知道發生了什麼。
  • 猜測客戶的意圖。與前一個選擇一樣,這個選擇可能為客戶帶來麻煩。 返回一個表示問題的值。例如,如果客戶試圖從空包中remove一項時,remove方法應該返回null。返回的值必須是不在包中的值。
  • 返回一個布爾值,表示操作的成功或失敗。
  • 拋出一個異常。

    注:拋出異常經常是Java方法運行期間處理遇到的特殊事件的理想方法。方法可以簡單地報告問題而不決定要做什麼。異常能讓每個客戶根據自己的特殊情形按需處理。Java插曲2將介紹異常的基本機製。
    注:ADT規格說明的草稿經常忽視或忽略你確實需要考慮的情形。你可能為了簡化草稿而有意忽略這些。一旦寫好了規格說明中的大部分內容,就可以關注這些細節,而讓規格說明更完善。
    一個接口

隨著規格說明越來越詳細,也越發地影響到你對程序設計語言的選擇。最終,你可能為包的方法寫下Java的方法頭並將它們組織為一個Java接口,用它們來實現ADT的類。程序清單1-1中的Java接口含有ADT包的方法及描述它們行為的詳細注釋。回想一下,類接口不含有數據域、構造方法、私有方法或保護方法。
現在,包中的項將是同一個類的對象。例如,我們可以有字符串的包。為了容納類類型的項,包的方法中使用泛型數據類型(generic data type)T>來表示每個項。必須在接口名的後麵寫,來說明標識符T的含義。一旦客戶選擇了具體的數據類型,編譯程序將在T出現的所有地方使用那個數據類型。接在本章後麵的Java插曲1中,將討論如何使用泛型為ADT中的數據提供類型的靈活性。
當檢查接口時,注意前一段中提到的處理特殊情形時所做的決策。具體來說,對於add、remove及contains方法,它們每一個都返回一個值。因為我們的程序設計語言是Java,所以要注意,有一個remove方法返回一個指向項的引用,而不是項本身。
雖然不一定要在實現類之前寫接口,但這樣做能讓你以簡潔的方式記錄你的規格說明。然後可以將接口中的代碼用在具體類的框架中。有了接口還能為包提供數據類型,它不依賴於具體的類定義。接下來的兩章將開發包類的兩種不同的實現。針對接口所寫的代碼,能讓我們更易於將包的一種實現替換為另一種。
程序清單1-1 包類的Java接口
image
image

說明一個ADT並為它的操作寫了Java接口後,應該寫幾個使用ADT的Java語句。雖然還不能執行這些語句(畢竟我們沒寫實現BagInterface的類),但我們可以用它們來確認或者修改方法的設計決策及相關文檔。這樣,可以檢查規格說明的適應性及對它的理解。最好現在來修改ADT的設計或文檔,而不是等到寫完實現後再進行。認真做這件事的額外好處是,後麵可以使用這些相同的Java語句來測試你的實現。

自測題6 給定自測題1創建的包aBag,寫Java語句,顯示aBag中所有的字符串。不要改變aBag的內容。
程序設計技巧:在實現一個類之前寫測試程序
寫Java語句來測試一個類的方法,將有助於你完全理解方法的規格說明。很明顯,在能正確實現方法之前必須理解它。如果你也是類的設計者,那麼使用這個類可能有助於你對設計或對文檔進行理想的修改。如果在實現類之前做這些修改,將會節省時間。因為早晚都要寫一個程序來測試你的實現,所以為什麼不現在寫而獲益,而非要放到以後再寫呢?
注:雖然我們說過,包中的項屬於同一個類,但這些項也可能屬於因繼承關係而相關的類。例如,假定Bag是實現接口BagInterface的類。如果我們寫下麵的語句創建類C對象的包:

則aBag中可以包含類C的對象及C的任何子類的對象。
下一節看看使用包的兩個例子。後麵,可以用這些例子來測試你的實現。

最後更新:2017-06-26 15:33:19

  上一篇:go  使用Ecs OpenAPI正確模式
  下一篇:go  《自己動手寫Docker》—— 導讀