本節書摘來華章計算機《資料結構與抽象:java語言描述(原書第4版)》一書中的第1章 ,第1.2節,[美]弗蘭克m.卡拉諾(frank m. carrano) 蒂莫西m.亨利(timothy m. henry) 著 羅得島大學 新英格蘭理工學院 辛運帏 饒一梅 譯 更多章節内容可以通路雲栖社群“華章計算機”公衆号檢視。
1.2 說明一個包
在用java實作包之前,需要描述它的資料,并詳細說明對應于包行為的方法。我們将命名方法,選擇它們的參數,确定它們的傳回值類型,并寫出注釋來充分描述它們對包資料的影響。當然,我們最終的目的是寫出每個方法的java頭和注釋,但首先我們用僞代碼來描述方法,然後用統一模組化語言(uml)進行表示。
crc卡的第一個行為引出一個方法,該方法傳回包中目前的項數。對應的方法沒有參數,它傳回一個整數。使用僞代碼,我們有下列的描述:

可以使用uml将方法表示為:
且将這行添加到類圖中。
可以使用一個布爾值方法來測試包是否為空,同樣該方法沒有參數。用僞代碼及uml描述這個方法的規格說明
及
将這行添加到類圖中。
注:因為通過檢視getcurrentsize是否傳回0就能檢測包何時為空,是以并不真的需要操作isempty。但是它是所謂的便利方法(convenience method),是以很多集合都提供這樣一個操作。 現在想向包中添加給定的對象。可以将這個方法命名為add,并有一個表示新項的參數。可以寫出下列僞代碼:
我們可能試圖讓add作為void方法,但是會有一些情況,比如如果包滿則不能将新項添加到包中。這些情況下,我們該如何辦呢?
設計決策:當不能添加新項時,方法add将如何處理?
當add不能完成任務時,我們可采取下面兩種選擇:
什麼也不做。不能添加其他的項,是以忽略這個項并且不改變包。
不改變包,但告訴客戶添加是不可能的。
第一個選擇簡單,但會讓客戶疑惑到底發生了什麼。當然,我們可以規定add的前置條件,即包必須不滿。這樣客戶要負責避免将新項添加到滿包中。
第二個選擇更好一些,且它也不難說明或實作。我們如何告訴客戶添加是否成功?标準java接口collection規定,如果添加沒有成功則發生異常。稍後我們再完成這個方法,并且使用另一種方式。顯示一條錯誤資訊并不是好的選擇,因為你應該讓客戶決定所有的書面輸出。因為添加操作或者成功或者不成功,是以我們可以讓方法add傳回一個布爾值。
是以,可以用uml規範add方法:
其中t表
示newentry的資料類型。
自測題1 假定abag表示一個有有限容量的空包。寫僞代碼,将使用者提供的字元串添加到包中,直到操作失敗。
有3個動作涉及從包中删除項:删除所有的項;删除任意一項;删除某個項。假定我們用僞代碼為這些方法命名并說明其參數,如下所示:
這些方法的傳回類型是什麼?
方法clear可以是一個void方法:我們隻想要一個空包,不擷取它的任何内容。是以,在uml中方法寫為:
如果第一個remove方法從包中删除一項,則該方法可以簡單地傳回被删除的對象。它的傳回類型為t,這是包中項的資料類型。在uml中,我們有
現在,我們可以處理從傳回null的空包中删除對象了。
如果包中不含有某項,則第二個remove方法不能從包中删除該項。可以讓方法傳回一個布爾值,類似于add那樣,用它來表示成功與否。或者,方法可以傳回被删對象,或者,如果不能删除這個對象則傳回null。下面是用uml表示的規格說明的兩種可能版本——我們必須二選一:
或者
如果anentry等于包中的某項,則這個方法的第一個版本将删除該項并傳回真(true)。即使方法沒有傳回被删除的項,客戶也能有方法的參數anentry,它等于被删除的項。故我們選擇這個版本,它與接口collection是一緻的。
自測題2 在一個類内同時具有上面描述的remove(anentry)的兩個版本合法嗎?
解釋。
自測題3 在一個類内同時具有remove的兩個版本,一個不帶參數而另一個帶一個參數,這樣合法嗎?解釋。
自測題4 給出自測題1中建立的滿包abag,寫僞代碼語句,删除并顯示包中的所有字元串。
其他的動作并不改變包的内容。其中一個動作是計數包中給定對象的出現次數。我們先用僞代碼後用uml說明它,如下所示。
另一個方法測試包是否含有給定對象。使用僞代碼和uml給出的規格說明如下所示。
自測題5 給定自測題1中建立的滿包abag,寫僞代碼語句,找出abag中字元串"hello"出現的次數,如果有的話。
最後,我們想看看包的内容。不是提供顯示包中項的方法,而是定義一個方法來傳回儲存這些項的數組。這樣,客戶可以按照自己的意願顯示部分或全部的項。下面是最後這個方法的規格說明:
當方法傳回一個數組時,它通常應該定義一個新的數組來傳回。我們還将說明這個方法的細節。
當我們為包中的方法提供前面那些規格說明時,使用uml符号來表示它們。圖1-2顯示了這些結果。
注意,crc卡和uml并不反映所有的細節,例如我們在前面的讨論中提到過的假定和特殊情形。但是,在确定了這樣的條件後,你應該在每個方法的下面說明該方法應有的動作。
應該寫下你的決策,想讓方法如何動作,就像我們寫在下表中的那樣。然後,可以将這些非形式化的描述放在說明方法的java注釋中。
設計決策:當特殊條件出現時會怎樣?
作為類的設計者,必須要做出決定如何處理特殊條件,并将這些決策包含在規格說明中。adt包的文檔應該反映這些決策和前面讨論的細節。
一般地,可以用幾種方式聲明特殊情形。你的方法可能
假定無效的情形不能發生。這個假定并不像聽起來那麼幼稚。方法可以聲明一種假設(即前置條件),這是客戶必須遵守的限制。然後由客戶檢查在方法調用前這個前置條件是否滿足。例如,方法remove的前置條件可能是包為非空的。注意,客戶可以使用adt包的其他方法,例如isempty和getcurrentsize,來輔助完成這個任務。隻要客戶遵守這個限制,無效的情形就不會發生。
忽略無效情形。當給出無效資料時方法可能簡單到什麼也不做。但是什麼都不做會讓客戶不知道發生了什麼。
猜測客戶的意圖。與前一個選擇一樣,這個選擇可能為客戶帶來麻煩。
傳回一個表示問題的值。例如,如果客戶試圖從空包中remove一項時,remove方法應該傳回null。傳回的值必須是不在包中的值。
傳回一個布爾值,表示操作的成功或失敗。
抛出一個異常。
注:抛出異常經常是java方法運作期間處理遇到的特殊事件的理想方法。方法可以簡單地報告問題而不決定要做什麼。異常能讓每個客戶根據自己的特殊情形按需處理。java插曲2将介紹異常的基本機制。
一個接口
随着規格說明越來越詳細,也越發地影響到你對程式設計語言的選擇。最終,你可能為包的方法寫下java的方法頭并将它們組織為一個java接口,用它們來實作adt的類。程式清單1-1中的java接口含有adt包的方法及描述它們行為的詳細注釋。回想一下,類接口不含有資料域、構造方法、私有方法或保護方法。
現在,包中的項将是同一個類的對象。例如,我們可以有字元串的包。為了容納類類型的項,包的方法中使用泛型資料類型(generic data type)t>來表示每個項。必須在接口名的後面寫,來說明辨別符t的含義。一旦客戶選擇了具體的資料類型,編譯程式将在t出現的所有地方使用那個資料類型。接在本章後面的java插曲1中,将讨論如何使用泛型為adt中的資料提供類型的靈活性。
當檢查接口時,注意前一段中提到的處理特殊情形時所做的決策。具體來說,對于add、remove及contains方法,它們每一個都傳回一個值。因為我們的程式設計語言是java,是以要注意,有一個remove方法傳回一個指向項的引用,而不是項本身。
雖然不一定要在實作類之前寫接口,但這樣做能讓你以簡潔的方式記錄你的規格說明。然後可以将接口中的代碼用在具體類的架構中。有了接口還能為包提供資料類型,它不依賴于具體的類定義。接下來的兩章将開發包類的兩種不同的實作。針對接口所寫的代碼,能讓我們更易于将包的一種實作替換為另一種。
程式清單1-1 包類的java接口
說明一個adt并為它的操作寫了java接口後,應該寫幾個使用adt的java語句。雖然還不能執行這些語句(畢竟我們沒寫實作baginterface的類),但我們可以用它們來确認或者修改方法的設計決策及相關文檔。這樣,可以檢查規格說明的适應性及對它的了解。最好現在來修改adt的設計或文檔,而不是等到寫完實作後再進行。認真做這件事的額外好處是,後面可以使用這些相同的java語句來測試你的實作。
自測題6 給定自測題1建立的包abag,寫java語句,顯示abag中所有的字元串。不要改變abag的内容。
寫java語句來測試一個類的方法,将有助于你完全了解方法的規格說明。很明顯,在能正确實作方法之前必須了解它。如果你也是類的設計者,那麼使用這個類可能有助于你對設計或對文檔進行理想的修改。如果在實作類之前做這些修改,将會節省時間。因為早晚都要寫一個程式來測試你的實作,是以為什麼不現在寫而獲益,而非要放到以後再寫呢?
則abag中可以包含類c的對象及c的任何子類的對象。
下一節看看使用包的兩個例子。後面,可以用這些例子來測試你的實作。