日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

麻省理工18年春软件构造课程阅读10“抽象数据类型”

發(fā)布時(shí)間:2023/12/10 编程问答 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 麻省理工18年春软件构造课程阅读10“抽象数据类型” 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

本文內(nèi)容來(lái)自MIT_6.031_sp18: Software Construction課程的Readings部分,采用CC BY-SA 4.0協(xié)議。

由于我們學(xué)校(哈工大)大二軟件構(gòu)造課程的大部分素材取自此,也是推薦的閱讀材料之一,于是打算做一些翻譯工作,自己學(xué)習(xí)的同時(shí)也能幫到一些懶得看英文的朋友。另外,該課程的閱讀資料中有許多練習(xí)題,但是沒(méi)有標(biāo)準(zhǔn)答案,所給出的“正確答案”均為譯者所寫,有錯(cuò)誤的地方還請(qǐng)指出。

(更新:從第10章開(kāi)始,只提供正確答案,不再翻譯錯(cuò)誤答案)




譯者:李秋豪 江家偉

審校:李秋豪

V1.0 Thu Mar 29 00:41:23 CST 2018


本次課程的目標(biāo)

  • 理解“抽象數(shù)據(jù)類型(ADT)”
  • 理解“表示獨(dú)立”

在這篇閱讀中,我們將會(huì)講解一個(gè)重要的概念——抽象數(shù)據(jù)類型,它會(huì)幫助我們將數(shù)據(jù)結(jié)構(gòu)的使用和數(shù)據(jù)結(jié)構(gòu)的具體實(shí)現(xiàn)分開(kāi)。

抽象數(shù)據(jù)類型解決了一個(gè)很危險(xiǎn)的問(wèn)題:使用者可能對(duì)類型的內(nèi)部表示做假設(shè)。我們?cè)诤竺鏁?huì)探討為什么這種假設(shè)是危險(xiǎn)的,以及如何避免它。我們也會(huì)討論操作符的分類和如何設(shè)計(jì)好的抽象數(shù)據(jù)類型。


Java中的訪問(wèn)控制

閱讀: Controlling Access to Members of a Class

閱讀小練習(xí)

閱讀以下代碼并回答問(wèn)題:

class Wallet {private int amount;public void loanTo(Wallet that) {// put all of this wallet's money into that wallet /*A*/ that.amount += this.amount; /*B*/ amount = 0;}public static void main(String[] args) { /*C*/ Wallet w = new Wallet(); /*D*/ w.amount = 100; /*E*/ w.loanTo(w);}}class Person {private Wallet w;public int getNetWorth() { /*F*/ return w.amount;}public boolean isBroke() { /*G*/ return Wallet.amount == 0;}}

假設(shè)程序在運(yùn)行 /*A*/ 語(yǔ)句后立即停止,上圖列出了此時(shí)的內(nèi)部狀態(tài),請(qǐng)問(wèn)各個(gè)數(shù)字所標(biāo)出的方框內(nèi)應(yīng)該填上什么?

1 -> w

2 -> that

3 -> loanTo

4 -> 200

Access control A

關(guān)于語(yǔ)句 /*A*/,以下哪一個(gè)說(shuō)法是正確的?

that.amount += this.amount;
  • [x] 在Java中允許對(duì)this.amount的索引

  • [x] 在Java中允許對(duì) that.amount 的索引

Access control B

關(guān)于語(yǔ)句 /*B*/,以下哪一個(gè)說(shuō)法是正確的?

amount = 0;
  • [x] 在Java中允許對(duì) amount 的索引

Access control C

關(guān)于語(yǔ)句 /*C*/,以下哪一個(gè)說(shuō)法是正確的?

Wallet w = new Wallet();
  • [x] 在Java中允許對(duì) Wallet() 構(gòu)造函數(shù)的調(diào)用

Access control D

關(guān)于語(yǔ)句 /*D*/,以下哪一個(gè)說(shuō)法是正確的?

w.amount = 100;
  • [x] 在Java中允許對(duì) w.amount 的訪問(wèn)

Access control E

關(guān)于語(yǔ)句 /*E*/ ,以下哪一個(gè)說(shuō)法是正確的?

w.loanTo(w);
  • [x] 在Java中允許對(duì) loanTo() 的調(diào)用
  • [x] 在這句代碼執(zhí)行之后,w指向的Wallet對(duì)象的金額將會(huì)是0

Access control F

關(guān)于語(yǔ)句 /*F*/,以下哪一個(gè)說(shuō)法是正確的?

return w.amount;
  • [x] 這里關(guān)于 w.amount 的索引不會(huì)被允許,因?yàn)?amount 是在另一個(gè)類中的私有區(qū)域

  • [x] 這個(gè)非法訪問(wèn)會(huì)被靜態(tài)捕捉

Access control G

關(guān)于語(yǔ)句 /*G*/,以下哪一個(gè)說(shuō)法是正確的?

return Wallet.amount == 0;
  • [x] 這里關(guān)于 Wallet.amount 的索引不會(huì)被允許,因?yàn)?amount 是一個(gè)私有地址
  • [x] 這里關(guān)于 Wallet.amount 的索引不會(huì)被允許,因?yàn)?amount 是一個(gè)實(shí)例變量
  • [x] 這個(gè)非法訪問(wèn)會(huì)被靜態(tài)捕捉


什么是抽象

抽象數(shù)據(jù)類型是軟件工程中一個(gè)普遍原則的實(shí)例,從它衍生出很多意思相近的名詞。這里列出了幾個(gè)能夠表達(dá)其中思想的詞:

  • 抽象: 忽略底層的細(xì)節(jié)而在高層思考
  • 模塊化:將系統(tǒng)分為一個(gè)模塊,每個(gè)模塊可以單獨(dú)的進(jìn)行設(shè)計(jì)、實(shí)現(xiàn)、測(cè)試、推倒,并且在剩下的開(kāi)發(fā)中進(jìn)行復(fù)用。
  • 封裝:在模塊的外部建立起一道“圍墻”,使它只對(duì)自己內(nèi)部的行為負(fù)責(zé),并且系統(tǒng)別處的bug不會(huì)影響到它內(nèi)部的正確性。
  • 信息隱藏:將模塊的實(shí)現(xiàn)細(xì)節(jié)隱藏,使未來(lái)更改模塊內(nèi)部時(shí)不必改變外部代碼。
  • 功能分離:一個(gè)模塊僅僅負(fù)責(zé)一個(gè)特性/功能,而不是將一個(gè)特性運(yùn)用在很多模塊上或一個(gè)模塊擁有很多特性。

作為一個(gè)軟件工程師,你應(yīng)該知道這些名詞,因?yàn)槟銜?huì)在以后的工作中經(jīng)常遇到它們。這些思想的本質(zhì)目的都是為了實(shí)現(xiàn)我們這門課的三個(gè)目標(biāo):遠(yuǎn)離bug、易于理解、可改動(dòng)。

事實(shí)上,我們?cè)谥暗恼n程中已經(jīng)碰到過(guò)這些思想,特別是在設(shè)計(jì)方法和規(guī)格說(shuō)明的時(shí)候:

  • 抽象:規(guī)格說(shuō)明使得使用者只需要弄懂規(guī)格說(shuō)明并遵守前置條件,而不是讓他們?nèi)ヅ讓拥拇a實(shí)現(xiàn)
  • 模塊化:單元測(cè)試和規(guī)格說(shuō)明都幫助了將方法模塊化
  • 封裝:方法中的局部變量都是被封裝的,因?yàn)樗麄儍H僅可以在方法內(nèi)部使用。與此相對(duì)的是全局變量和指向可變對(duì)象的別名,它們會(huì)對(duì)封裝帶來(lái)很大損害。
  • 信息隱藏:規(guī)格說(shuō)明就是一種信息隱藏,它使得實(shí)現(xiàn)者可以自由的更改實(shí)現(xiàn)代碼。
  • 功能分離:一個(gè)規(guī)格說(shuō)明應(yīng)該是邏輯明確的,即它不能有很多特性,而應(yīng)該完成好一個(gè)功能。

從今天的課程開(kāi)始,我們將跳出對(duì)方法的抽象,看看對(duì)數(shù)據(jù)的抽象。但是在我們描述數(shù)據(jù)抽象時(shí)方法也會(huì)扮演很重要的角色。

用戶定義類型

在早期的編程語(yǔ)言中,用戶只能自己定義方法,而所有的類型都是規(guī)定好的(例如整型、布爾型、字符串等等)。而現(xiàn)代編程語(yǔ)言允許用戶自己定義類型對(duì)數(shù)據(jù)進(jìn)行抽象,這是軟件開(kāi)發(fā)中的一個(gè)巨大進(jìn)步。

對(duì)數(shù)據(jù)進(jìn)行抽象的核心思想就是類型是通過(guò)其對(duì)應(yīng)的操作來(lái)區(qū)分的:一個(gè)整型就是你能對(duì)它進(jìn)行加法和乘法的東西;一個(gè)布爾型就是你能對(duì)它進(jìn)行取反的東西;一個(gè)字符串就是你能對(duì)它進(jìn)行鏈接或者取子字符串的東西,等等。在一定意義上,用戶在以前的編程語(yǔ)言上似乎已經(jīng)能夠定義自己的類型了,例如定義一個(gè)名叫Date的結(jié)構(gòu)體,里面用int表示天數(shù)和年份。但是真正使得抽象類型變得新穎不同的是對(duì)操作的強(qiáng)調(diào):用戶不用管這個(gè)類型里面的數(shù)據(jù)是怎么保存表示的,就好像是程序員不用管編譯器是怎么存儲(chǔ)整數(shù)一樣。起作用的只是類型對(duì)應(yīng)的操作。

和很多現(xiàn)代語(yǔ)言一樣,在Java中內(nèi)置類型和用戶定義類型之間的關(guān)系很模糊。例如在 java.lang中的類 Integer 和 Boolean 就是內(nèi)置的——Java標(biāo)準(zhǔn)中規(guī)定它們必須存在,但是它們的定義又是和用戶定義類型的方式一樣的。另外,Java中還保留了原始類型,它們不是類和對(duì)象,例如 int 和 boolean ,用戶無(wú)法對(duì)它們進(jìn)行繼承。

閱讀小練習(xí)

Abstract Data Types

思考抽象數(shù)據(jù)類型 Bool,它有如下操作:

true : Bool
false : Bool

and : Bool × Bool → Bool
or : Bool × Bool → Bool
not : Bool → Bool

頭兩個(gè)操作構(gòu)建了這個(gè)類型對(duì)應(yīng)的兩個(gè)值,后三個(gè)操作對(duì)應(yīng)邏輯操作 和、或、取非。

以下哪些選項(xiàng)可以是 Bool 具體的實(shí)現(xiàn)方法(并且滿足上面的操作符)?

  • [x] 一個(gè)比特位,1代表true,0代表false
  • [x] 一個(gè)int值,5代表true,8代表false
  • [x] 一個(gè)對(duì)String對(duì)象的索引,"false"代表true, "true" 代表false
  • [x] 一個(gè)int值,大于1的質(zhì)數(shù)代表true,其余的代表false


類型和操作的分類

對(duì)于類型,不管是內(nèi)置的還是用戶定義的,都可以被分為可改變不可變兩種。其中可改變類型的對(duì)象能夠被改變:它們提供了改變對(duì)象內(nèi)容的操作,這樣的操作執(zhí)行后可以改變其他對(duì)該對(duì)象操作的返回值。所以 Date 就是可改變的,因?yàn)槟憧梢酝ㄟ^(guò)調(diào)用setMonth操作改變 getMonth 操作的返回值。但 String 就是不可改變的,因?yàn)樗牟僮鞣际莿?chuàng)建一個(gè)新的 String 對(duì)象而不是改變現(xiàn)有的這個(gè)。有時(shí)候一個(gè)類型會(huì)提供兩種形式,一種是可改變的一種是不可改變的。例如 StringBuilder就是一種可改變的字符串類型。

而抽象類型的操作符大致分類:

  • 創(chuàng)建者creator:創(chuàng)建一個(gè)該類型的新對(duì)象。一個(gè)創(chuàng)建者可能會(huì)接受一個(gè)對(duì)象作為參數(shù),但是這個(gè)對(duì)象的類型不能是它創(chuàng)建對(duì)象對(duì)應(yīng)的類型。
  • 生產(chǎn)者producer:通過(guò)接受同類型的對(duì)象創(chuàng)建新的對(duì)象。例如, String類里面的 concat 方法就是一個(gè)生產(chǎn)者,它接受兩個(gè)字符串然后據(jù)此產(chǎn)生一個(gè)新的字符串。
  • 觀察者observer:接受一個(gè)同類型的對(duì)象然后返回一個(gè)不同類型的對(duì)象/值。例如List的 size 方法,它返回一個(gè) int。
  • 改造者mutator:改變對(duì)象的內(nèi)容,例如 List的 add 方法,它會(huì)在列表中添加一個(gè)元素。

我們可以將這種區(qū)別用映射來(lái)表示:

  • creator : t* → T
  • producer : T+, t* → T
  • observer : T+, t* → t
  • mutator : T+, t* → void | t | T

其中T代表抽象類型本身;t代表其他的類型;+代表這個(gè)參數(shù)可能出現(xiàn)一次或多次;*代表這個(gè)參數(shù)可能出現(xiàn)零次或多次。例如, String.concat() 這個(gè)接受兩個(gè)參數(shù)的生產(chǎn)者:

  • concat : String × String → String

有些觀察者不會(huì)接受其他類型的參數(shù),例如:

  • size : List → int

而有些則會(huì)接受很多參數(shù):

  • regionMatches : String × boolean × int × String × int × int → boolean

構(gòu)造者通常都是用構(gòu)造函數(shù)實(shí)現(xiàn)的,例如 new ArrayList() ,但是有的構(gòu)造體是靜態(tài)方法(類方法),例如 Arrays.asList()和 String.valueOf ,這樣的靜態(tài)方法也稱為工廠方法。

改造者通常沒(méi)有返回值(void)。一個(gè)沒(méi)有返回值的方法一定有副作用 ,因?yàn)椴蝗贿@個(gè)方法就沒(méi)有任何意義了。但是不是所有的改造者都沒(méi)有返回值。例如Set.add() 會(huì)返回一個(gè)布爾值用來(lái)提示這個(gè)集合是否被改變了。在Java圖形庫(kù)接口中,Component.add() 會(huì)將它自己這個(gè)對(duì)象返回,因此add()可以被連續(xù)鏈?zhǔn)秸{(diào)用。

抽象數(shù)據(jù)類型的例子

int 是Java中的原始整數(shù)類型,它是不可變類型,沒(méi)有改造者。

  • creators: 字面量 0, 1, 2, …
  • producers: 算術(shù)符 +, -, *, /
  • observers: 比較符號(hào) ==, !=, <, >
  • mutators: 無(wú)

List 是Java中的列表類型,它是可更改類型。另外,List也是一個(gè)接口,所以對(duì)于它的實(shí)現(xiàn)可以有很多類,例如 ArrayList 和 LinkedList.

  • creators: ArrayList 和 LinkedList 的構(gòu)造函數(shù), Collections.singletonList
  • producers: Collections.unmodifiableList
  • observers: size, get
  • mutators: add, remove, addAll, Collections.sort

String 是Java中的字符串類型,它是不可變類型。

  • creators: String 構(gòu)造函數(shù), valueOf 靜態(tài)方法(工廠方法)
  • producers: concat, substring, toUpperCase
  • observers: length, charAt
  • mutators: 無(wú)

這個(gè)分類告訴了我們一些有用的術(shù)語(yǔ),但它不是完美的。例如對(duì)于復(fù)雜的數(shù)據(jù)類型,有些操作可能既是生產(chǎn)者也是改造者。

閱讀小練習(xí)

Operations

下面都是我們從Java庫(kù)中選取的幾個(gè)抽象數(shù)據(jù)類型的操作,試著通過(guò)閱讀文檔將這些操作分類。

提示:注意類型本身是不是參數(shù)或者返回值,同時(shí)記住實(shí)例方法(沒(méi)有static關(guān)鍵詞的)有一個(gè)隱式的參數(shù)。

Integer.valueOf()

creator

BigInteger.mod()

producer

List.addAll()

mutator

String.toUpperCase()

producer

Set.contains()

observer

Map.keySet()

observer

BufferedReader.readLine()

mutator


抽象類型是通過(guò)它的操作定義的

這一節(jié)的重要思想就是抽象類型是通過(guò)它的操作定義的.

對(duì)于類型T來(lái)說(shuō),它的操作集合和規(guī)格說(shuō)明完全定義和構(gòu)造了它的特性。例如,當(dāng)我們談到List類型時(shí),我們并沒(méi)有特指一個(gè)數(shù)組或者鏈接鏈表,而是一系列模糊的值——哪些對(duì)象可以是List類型——滿足該類型的規(guī)格說(shuō)明和操作規(guī)定,例如 get(), size(), 等等。

上一段說(shuō)到的“模糊的值”是指我們不能去檢查數(shù)據(jù)具體是在類型中怎么存儲(chǔ)的,而是要通過(guò)特定的操作去處理。例如上圖中畫(huà)出的,通過(guò)規(guī)格說(shuō)明這道“防火墻”,我們將類型中具體的實(shí)現(xiàn)和這些實(shí)現(xiàn)共享的私有數(shù)據(jù)封裝起來(lái),而用戶只能看到和使用接口上的操作。


設(shè)計(jì)抽象類型

設(shè)計(jì)一個(gè)抽象類型包括選擇合適的操作以及它們對(duì)應(yīng)的行為,這里列出了幾個(gè)重要的規(guī)則。

設(shè)計(jì)少量,簡(jiǎn)單,可以組合實(shí)現(xiàn)強(qiáng)大功能的操作而非設(shè)計(jì)很多復(fù)雜的操作。

每個(gè)操作都應(yīng)該有一個(gè)被明確定義的目的,并且應(yīng)該設(shè)計(jì)為對(duì)不同的數(shù)據(jù)結(jié)構(gòu)有一致的行為,而不是針對(duì)某些特殊情況。例如,或許我們不應(yīng)該為L(zhǎng)ist類型添加一個(gè)sum操作。因?yàn)檫@雖然可能對(duì)想要操作一個(gè)整數(shù)列表的用戶有幫助,但是如果用戶想要操作一個(gè)字符串列表呢?或者一個(gè)嵌套的列表? 所有這些特殊情況都將會(huì)使得sum成為一個(gè)難以理解和使用的操作。

操作集合應(yīng)該充分地考慮到用戶的需求,也就是說(shuō),用戶可以用這個(gè)操作集合做他們可能想做的計(jì)算。一個(gè)較好測(cè)試方法是檢查抽象類型的每個(gè)屬性是否都能被操作集提取出來(lái)。例如,如果沒(méi)有g(shù)et操作,我們就不能提取列表中的元素。抽象類型的基本信息的提取也不應(yīng)該特別困難。例如,size方法對(duì)于List并不是必須的,因?yàn)槲覀兛梢杂胓et增序遍歷整個(gè)列表,直到get執(zhí)行失敗,但是這既不高效,也不方便。

抽象類型可以是通用的:例如,列表、集合,或者圖。或者它可以是適用于特定領(lǐng)域的:一個(gè)街道的地圖,一個(gè)員工數(shù)據(jù)庫(kù),一個(gè)電話簿等等。但是一個(gè)抽象類型不能兼有上述二者的特性。被設(shè)計(jì)用來(lái)代表一個(gè)紙牌序列的Deck類型不應(yīng)該有一個(gè)通用的add方法來(lái)向類型實(shí)例中添加任意對(duì)象,比如整型和字符串類型。反過(guò)來(lái)說(shuō),對(duì)于像dealCards這樣的只對(duì)特定領(lǐng)域(譯者注:紙牌游戲)有效的方法,把它加入List這樣的通用類型中也是沒(méi)有意義的。


表示獨(dú)立

特別地,一個(gè)好的抽象數(shù)據(jù)類型應(yīng)該是表示獨(dú)立的。這意味著它的使用和它的內(nèi)部表示(實(shí)際的數(shù)據(jù)結(jié)構(gòu)和實(shí)現(xiàn))無(wú)關(guān),所以內(nèi)部表示的改變將對(duì)外部的代碼沒(méi)有影響。例如,List就是表示獨(dú)立的——它的使用與它是用數(shù)組還是連接鏈表實(shí)現(xiàn)無(wú)關(guān)。

如果一個(gè)操作完全在規(guī)格說(shuō)明中定義了前置條件和后置條件,使用者就知道他應(yīng)該依賴什么,而你也可以安全的對(duì)內(nèi)部實(shí)現(xiàn)進(jìn)行更改(遵循規(guī)格說(shuō)明)。

例子: 字符串的不同表示

讓我們先來(lái)看看一個(gè)表示獨(dú)立的例子,然后想想它為什么很有用。下面的 MyString抽象類型是我們舉出的例子,雖然它遠(yuǎn)遠(yuǎn)沒(méi)有Java中的String操作多,規(guī)格說(shuō)明也有些不同,但是還是有解釋力的。下面是規(guī)格說(shuō)明:

/** MyString represents an immutable sequence of characters. */ public class MyString { Example of a creator operation ////** @param b a boolean value* @return string representation of b, either "true" or "false" */public static MyString valueOf(boolean b) { ... }Examples of observer operations ////** @return number of characters in this string */public int length() { ... }/** @param i character position (requires 0 <= i < string length)* @return character at position i */public char charAt(int i) { ... }Example of a producer operation /// /** Get the substring between start (inclusive) and end (exclusive).* @param start starting index* @param end ending index. Requires 0 <= start <= end <= string length.* @return string consisting of charAt(start)...charAt(end-1) */public MyString substring(int start, int end) { ... } }

使用者只需要/只能知道這個(gè)類型的公共方法和規(guī)格說(shuō)明。

現(xiàn)在讓我們看一個(gè)MyString簡(jiǎn)單的表示方法,僅僅使用一個(gè)字符數(shù)組,而且它的大小剛好是字符串的長(zhǎng)度,沒(méi)有多余的空間:

private char[] a;

如果使用這種表示方法,我們對(duì)操作的實(shí)現(xiàn)可能就是這樣的:

public static MyString valueOf(boolean b) {MyString s = new MyString();s.a = b ? new char[] { 't', 'r', 'u', 'e' } : new char[] { 'f', 'a', 'l', 's', 'e' };return s; }public int length() {return a.length; }public char charAt(int i) {return a[i]; }public MyString substring(int start, int end) {MyString that = new MyString();that.a = new char[end - start];System.arraycopy(this.a, start, that.a, 0, end - start);return that; }

這里想一個(gè)問(wèn)題:為什么 charAt 和 substring 不去檢查參量在合法的范圍內(nèi)?你認(rèn)為這種類型的對(duì)象對(duì)于非法的輸入會(huì)有什么反應(yīng)?

下面的快照?qǐng)D展示了在使用者進(jìn)行substring操作后的數(shù)據(jù)狀態(tài):

MyString s = MyString.valueOf(true); MyString t = s.substring(1,3);

這種實(shí)現(xiàn)有一個(gè)性能上的問(wèn)題,因?yàn)檫@個(gè)數(shù)據(jù)類型是不可變的,那么 substring 實(shí)際上沒(méi)有必要真正去復(fù)制子字符串到一個(gè)新的數(shù)組中。它可以僅僅指向原來(lái)的 MyString 字符數(shù)組,并且記錄當(dāng)前的起始位置和終止位置。

為了實(shí)現(xiàn)這種優(yōu)化,我們可以將內(nèi)部表示改為:

private char[] a; private int start; private int end;

通過(guò)這種新的表示方法,我們可以這樣實(shí)現(xiàn)操作:

public static MyString valueOf(boolean b) {MyString s = new MyString();s.a = b ? new char[] { 't', 'r', 'u', 'e' } : new char[] { 'f', 'a', 'l', 's', 'e' };s.start = 0;s.end = s.a.length;return s; }public int length() {return end - start; }public char charAt(int i) {return a[start + i]; }public MyString substring(int start, int end) {MyString that = new MyString();that.a = this.a;that.start = this.start + start;that.end = this.start + end;return that; }

現(xiàn)在進(jìn)行substring操作后的數(shù)據(jù)狀態(tài):

MyString s = MyString.valueOf(true); MyString t = s.substring(1,3);

因?yàn)?MyString的使用者只使用到了它的公共方法和規(guī)格說(shuō)明(沒(méi)有使用私有的存儲(chǔ)表示),我們可以“私底下”完成這種優(yōu)化而不用擔(dān)心影響使用者的代碼。這就是表示獨(dú)立的力量。

閱讀小練習(xí)

Representation 1

思考下面這個(gè)抽象類型:

/*** Represents a family that lives in a household together.* A family always has at least one person in it.* Families are mutable.*/ class Family {// the people in the family, sorted from oldest to youngest, with no duplicates.public List<Person> people;/*** @return a list containing all the members of the family, with no duplicates.*/public List<Person> getMembers() {return people;} }

下面是一個(gè)使用者的代碼:

void client1(Family f) {// get youngest person in the familyPerson baby = f.people.get(f.people.size()-1);... }

假設(shè)所有的代碼都能順利運(yùn)行( Family 和 client1)并通過(guò)測(cè)試。

現(xiàn)在 Family的數(shù)據(jù)表示從 List 變?yōu)榱?Set :

/*** Represents a family that lives in a household together.* A family always has at least one person in it.* Families are mutable.*/ class Family {// the people in the familypublic Set<Person> people;/*** @return a list containing all the members of the family, with no duplicates.*/public List<Person> getMembers() {return new ArrayList<>(people);} }

以下哪一個(gè)選項(xiàng)是在 Family 更改后對(duì) client1 的影響?

  • [x] client1 依賴于 Family的數(shù)據(jù)表示, 并且這種依賴會(huì)導(dǎo)致靜態(tài)錯(cuò)誤。

Representation 2

原始版本:

/*** Represents a family that lives in a* household together. A family always* has at least one person in it.* Families are mutable. */ class Family {// the people in the family,// sorted from oldest to youngest,// with no duplicates.public List<Person> people;/** @return a list containing all* the members of the family,* with no duplicates. */public List<Person> getMembers() {return people;} }

新版本:

/*** Represents a family that lives in a* household together. A family always* has at least one person in it.* Families are mutable. */ class Family {// the people in the familypublic Set<Person> people;/*** @return a list containing all* the members of the family,* with no duplicates. */public List<Person> getMembers() {return new ArrayList<>(people);} }

使用者 client2的代碼:

void client2(Family f) {// get size of the familyint familySize = f.people.size();... }

以下哪一個(gè)選項(xiàng)是新版本對(duì) client2 的影響?

  • [x] client2 依賴于 Family的表示,這種依賴不會(huì)被捕捉錯(cuò)誤但是會(huì)(幸運(yùn)地)得到正確答案。

Representation 3

原始版本:

/*** Represents a family that lives in a* household together. A family always* has at least one person in it.* Families are mutable. */ class Family {// the people in the family,// sorted from oldest to youngest,// with no duplicates.public List<Person> people;/** @return a list containing all* the members of the family,* with no duplicates. */public List<Person> getMembers() {return people;} }

新版本:

/*** Represents a family that lives in a* household together. A family always* has at least one person in it.* Families are mutable. */ class Family {// the people in the familypublic Set<Person> people;/*** @return a list containing all* the members of the family,* with no duplicates. */public List<Person> getMembers() {return new ArrayList<>(people);} }

使用者 client3的代碼:

void client3(Family f) {// get any person in the familyPerson anybody = f.getMembers().get(0);... }

以下哪一個(gè)選項(xiàng)是新版本對(duì) client3 的影響?

  • [x] client3 獨(dú)立于 Family的數(shù)據(jù)表示, 所以它依然能正確的工作

Representation 4

對(duì)于上面的Family數(shù)據(jù)類型,對(duì)每行/段判斷他是規(guī)格說(shuō)明(specification)還是數(shù)據(jù)表示(representation)還是具體實(shí)現(xiàn)(implementation)?

/*** Represents a family that lives in a household together.* A family always has at least one person in it.* Families are mutable.*/

--> 規(guī)格說(shuō)明

public class Family {

--> 規(guī)格說(shuō)明

// the people in the family, sorted from oldest to youngest, with no duplicates.

--> 數(shù)據(jù)表示

private List<Person> people;

--> 數(shù)據(jù)表示

/*** @return a list containing all the members of the family, with no duplicates.*/

--> 規(guī)格說(shuō)明

public List<Person> getMembers() {

--> 規(guī)格說(shuō)明

return people;

--> 具體實(shí)現(xiàn)


抽象數(shù)據(jù)類型在Java中的實(shí)現(xiàn)

讓我們總結(jié)一下我們?cè)谶@篇文章中討論過(guò)的主要思想以及使用JAVA語(yǔ)言特性實(shí)現(xiàn)它們的具體方法,這些思想對(duì)于使用任何語(yǔ)言編程一般都是適用的。重點(diǎn)在于有很多種方式來(lái)實(shí)現(xiàn),很重要的一點(diǎn)是:既要對(duì)大概念(比如構(gòu)造操作:creator operation)有較好的理解,也要理解它們不同的實(shí)現(xiàn)方式。

ADT conceptWays to do it in JavaExamples
Abstract data typeClassString
Interface + class(es)List and ArrayList
EnumDayOfWeek
Creator operationConstructorArrayList()
Static (factory) methodCollections.singletonList(), Arrays.asList()
ConstantBigInteger.ZERO
Observer operationInstance methodList.get()
Instance methodCollections.max()
Producer operationInstance methodString.trim()
Static methodCollections.unmodifiableList()
Mutator operationInstance methodList.add()
Static methodCollections.copy()
Representationprivate fields

這個(gè)表中有三項(xiàng)我們還沒(méi)有在之前的閱讀中講過(guò):

  • 使用接口來(lái)定義一個(gè)抽象數(shù)據(jù)類型。我們已經(jīng)看到 List 和 ArrayList 這些例子,并且我們將會(huì)在以后的閱讀中討論接口。
  • 使用枚舉類型(enum)定義一個(gè)抽象數(shù)據(jù)類型。枚舉對(duì)于有固定取值集合的ADTs(例如一周中有周一、周二等等)來(lái)說(shuō),是很理想的類型。我們將會(huì)在以后的閱讀中討論枚舉。
  • 用不變的對(duì)象作為構(gòu)造者操作。這種模式在不可變類型中很常見(jiàn),在不可變類型中,最簡(jiǎn)單或者空(emptiest譯者:喵喵喵?)的值僅僅是一個(gè)屬性為public的不變量,基于這個(gè)不變量,生產(chǎn)者被用來(lái)從中構(gòu)造更復(fù)雜的值。

  • 測(cè)試抽象數(shù)據(jù)類型

    當(dāng)我們測(cè)試一個(gè)抽象數(shù)據(jù)類型的時(shí)候,我們分別測(cè)試它的各個(gè)操作。而這些測(cè)試不可避免的要互相交互:我們只能通過(guò)觀察者來(lái)判斷其他的操作的測(cè)試是否成功,而測(cè)試觀察者的唯一方法是創(chuàng)建對(duì)象然后使用觀察者。

    下面是我們測(cè)試 MyString 類型時(shí)對(duì)輸入空間的一種可能劃分方案:

    // testing strategy for each operation of MyString: // // valueOf(): // true, false // length(): // string len = 0, 1, n // string = produced by valueOf(), produced by substring() // charAt(): // string len = 1, n // i = 0, middle, len-1 // string = produced by valueOf(), produced by substring() // substring(): // string len = 0, 1, n // start = 0, middle, len // end = 0, middle, len // end-start = 0, n // string = produced by valueOf(), produced by substring()

    現(xiàn)在我們?cè)囍脺y(cè)試用例覆蓋每一個(gè)分區(qū)。注意到 assertEquals 并不能直接應(yīng)用于 MyString對(duì)象,因?yàn)槲覀儧](méi)有在 MyString上定義判斷相等的操作,所以我們只能使用之前定義的 valueOf, length, charAt, 以及 substring,例如:

    @Test public void testValueOfTrue() {MyString s = MyString.valueOf(true);assertEquals(4, s.length());assertEquals('t', s.charAt(0));assertEquals('r', s.charAt(1));assertEquals('u', s.charAt(2));assertEquals('e', s.charAt(3)); }@Test public void testValueOfFalse() {MyString s = MyString.valueOf(false);assertEquals(5, s.length());assertEquals('f', s.charAt(0));assertEquals('a', s.charAt(1));assertEquals('l', s.charAt(2));assertEquals('s', s.charAt(3));assertEquals('e', s.charAt(4)); }@Test public void testEndSubstring() {MyString s = MyString.valueOf(true).substring(2, 4);assertEquals(2, s.length());assertEquals('u', s.charAt(0));assertEquals('e', s.charAt(1)); }@Test public void testMiddleSubstring() {MyString s = MyString.valueOf(false).substring(1, 2);assertEquals(1, s.length());assertEquals('a', s.charAt(0)); }@Test public void testSubstringIsWholeString() {MyString s = MyString.valueOf(false).substring(0, 5);assertEquals(5, s.length());assertEquals('f', s.charAt(0));assertEquals('a', s.charAt(1));assertEquals('l', s.charAt(2));assertEquals('s', s.charAt(3));assertEquals('e', s.charAt(4)); }@Test public void testSubstringOfEmptySubstring() {MyString s = MyString.valueOf(false).substring(1, 1).substring(0, 0);assertEquals(0, s.length()); }

    閱讀小練習(xí)

    Partition covering

    哪一個(gè)測(cè)試覆蓋了分區(qū)“charAt() 以及字符串長(zhǎng)度=1”?

    • [x] testMiddleSubstring

    哪一個(gè)測(cè)試覆蓋了分區(qū)“子字符串的子字符串”?

    • [x] testSubstringOfEmptySubstring

    哪一個(gè)測(cè)試覆蓋了分區(qū)“valueOf(true)”?

    • [x] testValueOfTrue

    • [x] testEndSubstring

    Unit testing an ADT

    testValueOfTrue測(cè)試的是哪一個(gè)“單元”?

    • [x] valueOf 操作
    • [x] length 操作
    • [x] charAt 操作


    總結(jié)

    • 抽象數(shù)據(jù)類型(ADT)是通過(guò)它們對(duì)應(yīng)的操作區(qū)分的。
    • 操作可以分類為創(chuàng)建者、生產(chǎn)者、觀察者、改造者。
    • ADT的標(biāo)識(shí)由它的操作集合和規(guī)格說(shuō)明組成。
    • 一個(gè)好的ADT應(yīng)該是簡(jiǎn)單,邏輯明確并且表示獨(dú)立的。
    • 對(duì)于ADT的測(cè)試應(yīng)該對(duì)每一個(gè)操作進(jìn)行測(cè)試,并同時(shí)利用到創(chuàng)建者、生產(chǎn)者、觀察者、改造者。

    T將本次閱讀的內(nèi)容和我們的三個(gè)目標(biāo)聯(lián)系起來(lái):

    • 遠(yuǎn)離bug. 一個(gè)好的ADT會(huì)在使用者和實(shí)現(xiàn)者之間建立“契約”,使用者知道應(yīng)該如何使用,而實(shí)現(xiàn)者有足夠的自由決定具體實(shí)現(xiàn)。
    • 易于理解. 一個(gè)好的ADT會(huì)將其內(nèi)部的代碼和信息隱藏起來(lái),而使用者只需要理解它的規(guī)格說(shuō)明和操作即可。
    • 可改動(dòng). 表示獨(dú)立使得實(shí)現(xiàn)者可以在不通知使用者的情況下對(duì)ADT內(nèi)部進(jìn)行改動(dòng)。

    轉(zhuǎn)載于:https://www.cnblogs.com/liqiuhao/p/8667447.html

    總結(jié)

    以上是生活随笔為你收集整理的麻省理工18年春软件构造课程阅读10“抽象数据类型”的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。