2021哈工大软件构造Lab3
2021年春季學(xué)期
計(jì)算學(xué)部《軟件構(gòu)造》課程
Lab 3實(shí)驗(yàn)報(bào)告
實(shí)驗(yàn)源碼:https://github.com/1190200817/SC_Lab3
目錄
1 實(shí)驗(yàn)?zāi)繕?biāo)概述··· 1
2 實(shí)驗(yàn)環(huán)境配置··· 1
3 實(shí)驗(yàn)過程··· 1
3.1 待開發(fā)的三個(gè)應(yīng)用場景··· 1
3.2 面向可復(fù)用性和可維護(hù)性的設(shè)計(jì):IntervalSet<L>· 2
3.2.1 IntervalSet<L>的共性操作··· 2
3.2.2 局部共性特征的設(shè)計(jì)方案··· 3
3.2.3 面向各應(yīng)用的IntervalSet子類型設(shè)計(jì)(個(gè)性化特征的設(shè)計(jì)方案)··· 4
3.3 面向可復(fù)用性和可維護(hù)性的設(shè)計(jì):MultiIntervalSet<L>· 7
3.3.1 MultiIntervalSet<L>的共性操作··· 7
3.3.2 局部共性特征的設(shè)計(jì)方案··· 7
3.3.3 面向各應(yīng)用的MultiIntervalSet子類型設(shè)計(jì)(個(gè)性化特征的設(shè)計(jì)方案)··· 9
3.4 面向復(fù)用的設(shè)計(jì):L· 11
3.5 可復(fù)用API設(shè)計(jì)··· 13
3.5.1 計(jì)算相似度··· 13
3.5.2 計(jì)算時(shí)間沖突比例··· 14
3.5.3 計(jì)算空閑時(shí)間比例··· 15
3.6 應(yīng)用設(shè)計(jì)與開發(fā)··· 15
3.6.1 排班管理系統(tǒng)··· 15
3.6.2 操作系統(tǒng)的進(jìn)程調(diào)度管理系統(tǒng)··· 17
3.6.3 課表管理系統(tǒng)··· 18
3.7 基于語法的數(shù)據(jù)讀入··· 18
3.8 應(yīng)對(duì)面臨的新變化··· 20
3.8.1 變化1· 20
3.8.2 變化2· 21
3.9 Git倉庫結(jié)構(gòu)··· 21
4 實(shí)驗(yàn)進(jìn)度記錄··· 22
5 實(shí)驗(yàn)過程中遇到的困難與解決途徑··· 23
6 實(shí)驗(yàn)過程中收獲的經(jīng)驗(yàn)、教訓(xùn)、感想··· 24
6.1 實(shí)驗(yàn)過程中收獲的經(jīng)驗(yàn)和教訓(xùn)··· 24
6.2 針對(duì)以下方面的感受··· 24
本次實(shí)驗(yàn)覆蓋課程第 2、3 章的內(nèi)容,目標(biāo)是編寫具有可復(fù)用性和可維護(hù)性的軟件,主要使用以下軟件構(gòu)造技術(shù):
本次實(shí)驗(yàn)給定了三個(gè)具體應(yīng)用(值班表管理、操作系統(tǒng)進(jìn)程調(diào)度管理、大學(xué)課表管理),學(xué)生不是直接針對(duì)每個(gè)應(yīng)用分別編程實(shí)現(xiàn),而是通過 ADT 和泛型等抽象技術(shù),開發(fā)一套可復(fù)用的 ADT 及其實(shí)現(xiàn),充分考慮這些應(yīng)用之間的相似性和差異性,使 ADT 有更大程度的復(fù)用(可復(fù)用性)和更容易面向各種變化(可維護(hù)性)。
Eclipse和Git在上一次實(shí)驗(yàn)中已經(jīng)配置好了。本次實(shí)驗(yàn)無需額外配置環(huán)境。
在這里給出你的GitHub Lab3倉庫的URL地址(Lab3-學(xué)號(hào))。
https://github.com/ComputerScienceHIT/HIT-Lab3-1190200817
請(qǐng)仔細(xì)對(duì)照實(shí)驗(yàn)手冊(cè),針對(duì)每一項(xiàng)任務(wù),在下面各節(jié)中記錄你的實(shí)驗(yàn)過程、闡述你的設(shè)計(jì)思路和問題求解思路,可輔之以示意圖或關(guān)鍵源代碼加以說明(但千萬不要把你的源代碼全部粘貼過來!)。
簡要介紹三個(gè)應(yīng)用:
①值班表管理,一個(gè)單位有 n 個(gè)員工,在某個(gè)時(shí)間段內(nèi)安排值班。每天只能安排一個(gè)員工且不能出現(xiàn)無人值班的情況;每個(gè)員工需要安排在連續(xù)的幾天內(nèi)。值班表內(nèi)需要記錄員工的名字、職位、手機(jī)號(hào)碼,以便于外界聯(lián)系值班員。
②操作系統(tǒng)進(jìn)程調(diào)度管理,進(jìn)程被調(diào)度在 CPU 上執(zhí)行,操作系統(tǒng)決定在各個(gè)時(shí)段內(nèi)執(zhí)行哪個(gè)進(jìn)程。操作系統(tǒng)可掛起某個(gè)正在執(zhí)行的進(jìn)程,在后續(xù)時(shí)刻可以恢復(fù)執(zhí)行被掛起的進(jìn)程。每個(gè)時(shí)間只能有一個(gè)進(jìn)程在執(zhí)行,其他進(jìn)程處于休眠狀態(tài);一個(gè)進(jìn)程的執(zhí)行被分為多個(gè)時(shí)間段;在特定時(shí)刻,CPU 可以“閑置”;調(diào)度無規(guī)律,可看作是隨機(jī)調(diào)度。
③大學(xué)課表管理:課程需要特定的教室和特定的教師。假設(shè)各周的課表都是完全一樣的,同樣的課程安排將以“周”為單位進(jìn)行周期性的重復(fù),直到學(xué)期結(jié)束;一門課程每周可以出現(xiàn) 1 次,也可以安排多次,且由同一位教師承擔(dān)并在同樣的教室進(jìn)行;允許課表中有空白時(shí)間段;同一個(gè)時(shí)間段內(nèi)可以安排不同的課程;一位教師也可以承擔(dān)課表中的多門課程。
相同之處:都包含了具有不同特征的“時(shí)間段集合”對(duì)象。每個(gè)時(shí)間段對(duì)應(yīng)一個(gè)對(duì)象標(biāo)簽。
不同之處:有三個(gè)維度上的差異。①是否允許時(shí)間軸上有空白。在應(yīng)用1中,不允許有空白;在應(yīng)用2和應(yīng)用3中,允許有空白。②是否允許不同的 interval 之間有重疊。在應(yīng)用1和應(yīng)用2中,不允許有重疊;在應(yīng)用3中,允許有重疊。③是否包含周期性的時(shí)間段。應(yīng)用 3 中以“一周”為單位重復(fù)某個(gè)課程,但應(yīng)用1和應(yīng)用2中不存在這種情況。
該節(jié)是本實(shí)驗(yàn)的核心部分。
這個(gè)ADT描述了一組在時(shí)間軸上分布的“時(shí)間段”,每個(gè)時(shí)間段附著一個(gè)特定的標(biāo)簽,且標(biāo)簽不重復(fù)。因此共性的方法包括:
由于IntervalSet是一對(duì)一的結(jié)構(gòu),即一個(gè)標(biāo)簽對(duì)應(yīng)一個(gè)時(shí)間段,因此可以使用Map作為內(nèi)部的數(shù)據(jù)結(jié)構(gòu),定義為:
private final Map<L, time> intervalMap = new HashMap<>();
由于時(shí)間段是兩個(gè)long型的整數(shù)構(gòu)成的,因此可以定義一個(gè)輔助類time表示一個(gè)時(shí)間段。其中start是時(shí)間段的開始,end是時(shí)間段的結(jié)束,同時(shí)要求start一定大于0且start一定比end小。這里time實(shí)現(xiàn)了Comparable接口是為了滿足下面MultiIntervalSet中的一些功能。
對(duì)于empty()方法,直接返回一個(gè)具體實(shí)現(xiàn)類。
對(duì)于insert()方法,首先判斷非法條件(start<0和start>=end),然后判斷Map中是否包含標(biāo)簽,若包含該標(biāo)簽,那只有在重復(fù)設(shè)置相同時(shí)間段時(shí)才合法,否則會(huì)拋出IntervalConflictException;若不包含該標(biāo)簽,就直接插入時(shí)間段。(注意,這里是允許不同標(biāo)簽之間存在Overlap的)
對(duì)于labels()方法,直接返回Map中所有的鍵構(gòu)成的集合即可。
對(duì)于remove()方法,判斷Map的鍵中是否存在該標(biāo)簽,若不存在,直接返回false;若存在,就在Map中刪除該標(biāo)簽,并返回true。
對(duì)于start()方法,判斷Map的鍵中是否存在該標(biāo)簽,若不存在,直接返回-1,否則可以得到標(biāo)簽對(duì)應(yīng)的時(shí)間段time,調(diào)用time的getStart()方法就可以得到時(shí)間段的開始時(shí)間并返回。
對(duì)于end()方法,同上,但要調(diào)用time的getEnd()方法得到時(shí)間段的結(jié)束時(shí)間并返回。
對(duì)于copy()方法,先新建一個(gè)空白的IntervalSet副本,然后遍歷Map。將遍歷得到的所有時(shí)間段和標(biāo)簽通過insert()方法插入到新的副本中,最后返回該副本。
?????? 此外,還需要重寫toString方法提供容易閱讀的信息。
一、使用decorator裝飾器的方法進(jìn)行是否允許不同的 interval 之間有重疊以及周期性的維度的個(gè)性特征的設(shè)計(jì)。向裝飾器中傳入待裝飾的IntervalSet,并在繼承該裝飾器的具體子類中實(shí)現(xiàn)相應(yīng)的個(gè)性化功能。如下圖,在裝飾器IntervalSetDecorator中有屬性intervalSet,是一個(gè)待裝飾的IntervalSet類,而IntervalSetDecorator中的所有方法都調(diào)用intervalSet的相應(yīng)方法。
①不允許重疊的IntervalSet實(shí)現(xiàn)如下:
首先NoOverlapIntervalSet繼承裝飾器IntervalSetDecorator,并實(shí)現(xiàn)IntervalSet接口。在NoOverlapIntervalSet中添加一個(gè)屬性intervalMap記錄添加的所有時(shí)間段。
在重寫的insert()方法中,首先判斷新添加的時(shí)間段是否與已添加的時(shí)間段發(fā)生重疊,如果有,直接拋出異常;否則就調(diào)用未重寫的父類的insert()方法進(jìn)行插入。
?????? ②周期性的IntervalSet實(shí)現(xiàn)如下:
首先PeriodicIntervalSet繼承裝飾器IntervalSetDecorator,并實(shí)現(xiàn)IntervalSet接口。在PeriodicIntervalSet中添加一個(gè)屬性period記錄時(shí)間周期。
在重寫的insert()方法中,將開始時(shí)間和結(jié)束時(shí)間對(duì)周期取模后再調(diào)用未重寫的父類的insert()方法進(jìn)行插入。
?????? 二、使用代理的方式進(jìn)行是否允許時(shí)間軸上有空白這個(gè)維度的個(gè)性特征的設(shè)計(jì)。由于不允許空白相當(dāng)于添加了新的方法,因此將這個(gè)特性抽象為一個(gè)接口NoBlankIntervalSet:
接口中包含三個(gè)方法:blankIntervals()返回所有的空白時(shí)間段集合;getStart-Time()返回總的開始時(shí)間;getEndTime()返回總的結(jié)束時(shí)間。
?????? 一個(gè)具體的不允許時(shí)間軸上有空白的IntervalSet只需要實(shí)現(xiàn)NoBlank-IntervalSet接口。具體實(shí)現(xiàn)類CommonNoBlankIntervalSet的實(shí)現(xiàn)如下:
屬性startTime是總的開始時(shí)間,endTime是總的結(jié)束時(shí)間。blankIntervals()的實(shí)現(xiàn)如圖,首先將空白時(shí)間段設(shè)置為整個(gè)時(shí)間段,然后遍歷所有的時(shí)間段,將這些時(shí)間段從空白時(shí)間段中去除,最后得到的就是所有的空白時(shí)間段。
由于要求必須使用 IntervalSet<L>作為其 rep 的一部分,因此選擇IntervalSet<L>組成的List作為rep。一個(gè)IntervalSet中只存儲(chǔ)某個(gè)標(biāo)簽對(duì)應(yīng)的一個(gè)時(shí)間段,如果一個(gè)標(biāo)簽對(duì)應(yīng)多個(gè)時(shí)間段,需要分散在不同的IntervalSet中。
對(duì)于empty()方法,直接返回一個(gè)具體實(shí)現(xiàn)類。
對(duì)于initial初始化方法,直接將傳入的IntervalSet的副本作為rep的一個(gè)元素。
對(duì)于insert()方法,首先判斷非法情況。之后遍歷IntervalSet列表得到標(biāo)簽對(duì)應(yīng)的所有時(shí)間段,判斷已存在的時(shí)間段和要增加的時(shí)間段是否存在重疊,如果重疊則拋出異常;若不重疊,就尋找某個(gè)不存在該標(biāo)簽的IntervalSet,將這個(gè)時(shí)間段插入該IntervalSet,如果列表中所有的IntervalSet都包含該標(biāo)簽,就需要新建一個(gè)空白的IntervaSet加入列表,再進(jìn)行插入。
對(duì)于labels()方法,可以直接返回第一個(gè)IntervalSet中的所有標(biāo)簽組成的集合。(因?yàn)椴迦霑r(shí)都是從頭開始遍歷的,因此不會(huì)存在某個(gè)標(biāo)簽出現(xiàn)在后面的IntervalSet而不在第一個(gè)IntervalSet中的情況)
對(duì)于remove()方法,直接對(duì)每個(gè)IntervalSet調(diào)用remove()方法即可。
對(duì)于intervals()方法,遍歷IntervalSet列表得到標(biāo)簽對(duì)應(yīng)的所有時(shí)間段,將時(shí)間段從小到大進(jìn)行排序(前面time的實(shí)現(xiàn)中進(jìn)行了說明)。然后將每個(gè)時(shí)間段以它的順序作為標(biāo)簽插入到一個(gè)IntervalSet中并返回。
此外,還需要重寫toString方法提供容易閱讀的信息。
與3.2.3同理,使用decorator裝飾器的方法進(jìn)行是否允許不同的interval之間有重疊以及周期性的維度的個(gè)性特征的設(shè)計(jì)。向裝飾器中傳入待裝飾的MultiIntervalSet,并在繼承該裝飾器的具體子類中實(shí)現(xiàn)相應(yīng)的個(gè)性化功能。如下圖,在裝飾器MultiIntervalSetDecorator中有屬性multiIntervalSet,是一個(gè)待裝飾的MultiIntervalSet類,而MultiIntervalSetDecorator中的所有方法都調(diào)用multiIntervalSet的相應(yīng)方法。
①不允許重疊的MultiIntervalSet實(shí)現(xiàn)如下:
首先NoOverlapMultiIntervalSet繼承裝飾器MultiIntervalSetDecorator,并實(shí)現(xiàn)MultiIntervalSet接口。
在重寫的insert()方法中,首先判斷新添加的時(shí)間段是否與已添加的時(shí)間段發(fā)生重疊,如果有,直接拋出異常;否則就調(diào)用未重寫的父類的insert()方法進(jìn)行插入。
②周期性的MultiIntervalSet實(shí)現(xiàn)如下:
首先PeriodicMultiIntervalSet繼承裝飾器MultiIntervalSetDecorator,并實(shí)現(xiàn)MultiIntervalSet接口。在PeriodicMultiIntervalSet中添加一個(gè)屬性period記錄時(shí)間周期。
在重寫的insert()方法中,將開始時(shí)間和結(jié)束時(shí)間對(duì)周期取模后再調(diào)用未重寫的父類的insert()方法進(jìn)行插入。
設(shè)計(jì)三個(gè)應(yīng)用的不同標(biāo)簽,分別為“員工”(Employee)、“進(jìn)程”(Process)、
“課程”(Course)。并且它們都是immutable類。
①對(duì)于Employee,具有的屬性為:姓名、職務(wù)、手機(jī)號(hào)碼。
除了相應(yīng)的get方法之外,還需要重寫equals()、hashCode()和toString()方法。equals()的判斷依據(jù)是:只有三個(gè)屬性均相同時(shí)才認(rèn)為是相同的。
②對(duì)于Course,具有的屬性為:課程 ID、課程名稱、教師名字、地點(diǎn)。
除了相應(yīng)的get方法之外,還需要重寫equals()、hashCode()和toString()方法。equals()的判斷依據(jù)是:只有四個(gè)屬性均相同時(shí)才認(rèn)為是相同的。
此外,Course類實(shí)現(xiàn)了Comparable接口,這是為了APP中顯示課程順序的合理性。
③對(duì)于Process,具有的屬性為:進(jìn)程 ID、進(jìn)程名稱、最短執(zhí)行時(shí)間、最長執(zhí)行時(shí)間。
除了相應(yīng)的get方法之外,還需要重寫equals()、hashCode()和toString()方法。equals()的判斷依據(jù)是:進(jìn)程ID相同時(shí)認(rèn)為是相同的。
對(duì)于兩個(gè)MultiIntervalSet:s1和s2,遍歷s1中的標(biāo)簽,查看s2中是否存在相同的標(biāo)簽,如果不存在,則對(duì)相似度沒有貢獻(xiàn);如果存在,那么這個(gè)標(biāo)簽在s1和s2中各有一個(gè)時(shí)間段的集合,計(jì)算這兩個(gè)時(shí)間段集合的重合長度,將所有的重合長度加在一起除以MultiIntervalSet的時(shí)間跨度就是兩個(gè)MultiIntervalSet的相似度。
對(duì)于IntervalSet來說,由于它是一個(gè)特殊的MultiIntervalSet,因此可以把它轉(zhuǎn)換成MultiIntervalSet后再調(diào)用針對(duì)MultiIntervalSet的計(jì)算時(shí)間沖突比例的函數(shù)。
對(duì)于MultiIntervalSet,維護(hù)一個(gè)沖突時(shí)間段的集合conflictTime,初始時(shí)為空。遍歷set中的標(biāo)簽,判斷該標(biāo)簽和其他標(biāo)簽的時(shí)間段是否存在重合,如果存在,就將沖突的時(shí)間段加入conflictTime。向conflictTime加入時(shí)間段也需要考慮重合的問題,集合中不能有重合的時(shí)間段,因此向conflictTime加入時(shí)間段時(shí)需要進(jìn)行適當(dāng)?shù)暮喜ⅰW詈骳onflictTime中的時(shí)間長度除以總的時(shí)間跨度就是時(shí)間沖突比例。
對(duì)于IntervalSet來說,由于它是一個(gè)特殊的MultiIntervalSet,因此可以把它轉(zhuǎn)換成MultiIntervalSet后再調(diào)用針對(duì)MultiIntervalSet的計(jì)算空閑時(shí)間比例的函數(shù)。
對(duì)于MultiIntervalSet,原理類似于3.2.3中的blankIntervals()方法。維護(hù)一個(gè)空閑時(shí)間段的集合freeTime,初始時(shí)為整個(gè)時(shí)間段。遍歷set中的所有時(shí)間段,并將其從freeTime中刪去。最后freeTime中的時(shí)間長度除以總的時(shí)間跨度就是空閑比例。
利用上述設(shè)計(jì)和實(shí)現(xiàn)的ADT,實(shí)現(xiàn)手冊(cè)里要求的各項(xiàng)功能。
使用DutyIntervalSet作為數(shù)據(jù)結(jié)構(gòu),同時(shí)維護(hù)一個(gè)Employee的集合可以存儲(chǔ)未被安排的員工。剛進(jìn)入時(shí)APP,會(huì)提示初始化一些信息,包括:排班開始日期、結(jié)束日期以及一組員工信息。初始化結(jié)束后,打印一個(gè)菜單,告訴用戶提供的一些功能。
根據(jù)用戶選擇的功能采取相應(yīng)的操作。其中選項(xiàng)1,2涉及對(duì)Employee集合的增刪;3對(duì)應(yīng)DutyIntervalSet的insert操作;4對(duì)應(yīng)DutyIntervalSet的remove操作;5對(duì)應(yīng)DutyIntervalSet的blankIntervals操作;7對(duì)應(yīng)DutyIntervalSet的blankIntervals操作以及DutyIntervalSet的labels、start、end操作;8的操作見3.7節(jié)。由于這些操作都是簡單地使用一些函數(shù),因此不再詳細(xì)敘述。這里只介紹“6:自動(dòng)編排”的實(shí)現(xiàn)方法:首先調(diào)用DutyIntervalSet的blankIntervals操作得到未被安排的時(shí)間段,然后遍歷Employee集合,找出未被安排的員工,將這些未被安排的時(shí)間段和員工一對(duì)一匹配起來,并調(diào)用insert方法插入到DutyIntervalSet中。
此外,APP還擁有很好的健壯性,能面對(duì)用戶各種非法的、不符合格式的輸入。舉例來說,針對(duì)添加員工的操作,存在各種非法的輸入情況,在APP中都得到了相應(yīng)的解決,并提示給用戶。
使用ProcessIntervalSet作為數(shù)據(jù)結(jié)構(gòu),同時(shí)維護(hù)一個(gè)的Map存儲(chǔ)進(jìn)程和已執(zhí)行時(shí)間的映射關(guān)系。進(jìn)入APP后,會(huì)打印一個(gè)菜單,告訴用戶提供的一些功能。
根據(jù)用戶選擇的功能采取相應(yīng)的操作。其中選項(xiàng)1,2涉及對(duì)Map的增刪;5,6對(duì)應(yīng)ProcessIntervalSet的intervals操作。由于這些操作都是簡單地使用一些函數(shù),因此不再詳細(xì)敘述。這里介紹3,4的實(shí)現(xiàn)方法,選項(xiàng)3:從時(shí)間點(diǎn)0開始進(jìn)入一個(gè)循環(huán),當(dāng)所有的進(jìn)程都被執(zhí)行完成后退出循環(huán)。在循環(huán)中,首先使用隨機(jī)數(shù)(random.nextBoolean)決定是否調(diào)度進(jìn)程,如果決定不調(diào)度進(jìn)程,則閑置一段隨機(jī)的時(shí)間(random.nextInt);如果決定調(diào)度進(jìn)程,則隨機(jī)選擇一個(gè)未完成的進(jìn)程(使用隨機(jī)數(shù)選擇進(jìn)程的序號(hào))并執(zhí)行一段隨機(jī)的時(shí)間(random.nextInt),執(zhí)行結(jié)束后,如果該進(jìn)程的總執(zhí)行時(shí)間已經(jīng)落到最短執(zhí)行時(shí)間和最長執(zhí)行時(shí)間的區(qū)間內(nèi),則該進(jìn)程被執(zhí)行完成,然后從這個(gè)時(shí)間點(diǎn)開始進(jìn)行下一輪的循環(huán)。選項(xiàng)4:和選項(xiàng)3唯一的不同在于進(jìn)程的選擇不是隨機(jī)的,而是選擇距離其最大執(zhí)行時(shí)間差距最小的進(jìn)程。
同樣地,APP擁有很好的健壯性,能面對(duì)用戶各種非法的、不符合格式的輸入。
使用CourseIntervalSet作為數(shù)據(jù)結(jié)構(gòu),同時(shí)維護(hù)一個(gè)Course的Map存儲(chǔ)未被安排的課程以及對(duì)應(yīng)的剩余學(xué)時(shí)數(shù)。剛進(jìn)入時(shí)APP,會(huì)提示初始化一些信息,包括:學(xué)期開始日期、總周數(shù)、以及一組課程信息。初始化結(jié)束后,打印一個(gè)菜單,告訴用戶提供的一些功能。
根據(jù)用戶選擇的功能采取相應(yīng)的操作。其中選項(xiàng)1,2涉及對(duì)Course映射的增刪;3對(duì)應(yīng)CourseIntervalSet的insert操作;4對(duì)應(yīng)CourseIntervalSet的remove操作;5對(duì)應(yīng)對(duì)Course映射的遍歷以及顯示;6,7對(duì)應(yīng)API中操作的使用;8對(duì)應(yīng)CourseIntervalSet的intervals操作。由于這些操作都是簡單地使用一些函數(shù),因此不再詳細(xì)敘述。
同樣地,APP擁有很好的健壯性,能面對(duì)用戶各種非法的、不符合格式的輸入。
首先對(duì)文件的格式進(jìn)行分析,文件總共包括三個(gè)部分,分別是Employee、Period和Roster,每個(gè)部分有自己獨(dú)特的格式。在假設(shè)沒有空格、縮進(jìn)和空行的情況下,可以分別設(shè)計(jì)識(shí)別每個(gè)部分的正則表達(dá)式。
Employee部分的正則表達(dá)式:
Period部分的正則表達(dá)式:
Roster部分的正則表達(dá)式:
對(duì)于整個(gè)文件,由于三個(gè)部分出現(xiàn)的順序是不定的,即共有六種情況,因此識(shí)別整個(gè)文件的正則表達(dá)式為:
首先利用這個(gè)正則表達(dá)式判斷文件的格式是否正確,如果正確就抽取出每個(gè)部分。
利用Employee內(nèi)部的格式抽取出員工信息:
利用Period的格式抽取出時(shí)間段信息:
利用Roster內(nèi)部的格式抽取出排班信息:
根據(jù)抽取出的信息,就可以構(gòu)造出一個(gè)排班表了,在構(gòu)造的同時(shí)判斷一些錯(cuò)誤,如:員工信息重復(fù)、員工未定義、時(shí)間重疊等。
在APP中,讀入用戶指定的文件,并去除所有的空格、縮進(jìn)、空行,之后調(diào)用Parser解析信息,在出現(xiàn)錯(cuò)誤時(shí)提示給用戶相應(yīng)錯(cuò)誤信息。
修改之前的DutyIntervalSet實(shí)現(xiàn)的是IntervalSet接口,但是本次變化要求每個(gè)標(biāo)簽可以對(duì)應(yīng)多個(gè)時(shí)間段,因此要求DutyIntervalSet實(shí)現(xiàn)MulitIntervalSet接口,同時(shí)還要保持不允許重疊的特征。因此只需要修改DutyIntervalSet繼承的裝飾器類型以及實(shí)現(xiàn)的接口類型即可。具體修改如圖:
修改前的代碼:
修改后的代碼:
由于DutyIntervalSet從實(shí)現(xiàn)IntervalSet接口變?yōu)閷?shí)現(xiàn)MulitIntervalSet接口,因此一些方法會(huì)發(fā)生變化,例如不再支持start()和end()方法,同時(shí)新增加了intervals()方法。因此,在DutyRosterApp需要修改相應(yīng)的實(shí)現(xiàn)方法。舉例來說,對(duì)于APP中的可視化排班信息的功能,需要遍歷DutyIntervalSet中的信息,之前的遍歷方式直接使用labels()方法:
但是,修改之后需要使用intervals()方法:
在其他地方也涉及這種變化,就不一一列舉了。同時(shí),需要修改測試代碼保持測試的正確性。
除修改測試代碼的變化,一共修改大約50行代碼,花費(fèi)時(shí)間較短,說明之前的設(shè)計(jì)較為合理,應(yīng)對(duì)變化的能力比較強(qiáng),可維護(hù)性很好。測試代碼的修改大約也在50行左右。
由于使用了裝飾器,只需要將原來傳入裝飾器的MultiIntervalSet改為不能重疊的NonOverlapMultiIntervalSet即可。如果不考慮測試代碼的話,真正修改的代碼只有一行!說明之前的設(shè)計(jì)較為合理,應(yīng)對(duì)變化的能力比較強(qiáng),可維護(hù)性很好。具體修改如圖:
修改前的代碼(傳入可重疊的MultiIntervalSet):
修改后的代碼(傳入不可重疊的NoOverlapMultiIntervalSet):
為了使得測試仍然保持正確,需要修改原來的測試代碼,總共修改的代碼量大約為50行,花費(fèi)時(shí)間較短。
請(qǐng)?jiān)谕瓿扇繉?shí)驗(yàn)要求之后,利用Git log指令或Git圖形化客戶端或GitHub上項(xiàng)目倉庫的Insight頁面,給出你的倉庫到目前為止的Object Graph,尤其是區(qū)分清楚change分支和master分支所指向的位置。
使用git log指令, 得到如下結(jié)果:
可以看出,Git倉庫到目前為止的Object Graph有如下形式:
請(qǐng)使用表格方式記錄你的進(jìn)度情況,以超過半小時(shí)的連續(xù)編程時(shí)間為一行。
每次結(jié)束編程時(shí),請(qǐng)向該表格中增加一行。不要事后胡亂填寫。
不要嫌煩,該表格可幫助你匯總你在每個(gè)任務(wù)上付出的時(shí)間和精力,發(fā)現(xiàn)自己不擅長的任務(wù),后續(xù)有意識(shí)的彌補(bǔ)。
| 日期 | 時(shí)間段 | 計(jì)劃任務(wù) | 實(shí)際完成情況 |
| 2021.6.28 | 12:30-14:30 | 設(shè)計(jì)IntervalSet接口并完成一個(gè)具體的實(shí)現(xiàn)類CommonIntervalSet,編寫測試 | 完成 |
| 2021.6.28 | 14:30-17:00 | 設(shè)計(jì)MultiIntervalSet接口并完成具體的實(shí)現(xiàn)類CommonMultiIntervalSet,編寫測試 | 完成 |
| 2021.6.28 | 17:30-18:30 | 設(shè)計(jì)IntervalSet和Common-MultiIntervalSet的裝飾器 | 完成 |
| 2021.6.28 | 18:30-19:30 | 在有無空白的維度上,設(shè)計(jì)NoBlankIntervalSet接口并完成具體實(shí)現(xiàn)類CommonNoBlankIntervalSet,編寫測試 | 完成 |
| 2021.6.28 | 19:30-21:00 | 在是否允許重疊的維度上,設(shè)計(jì)實(shí)現(xiàn)不允許重疊的裝飾類NoOverlapIntervalSet和NoOverlap-MultiIntervalSet,編寫測試 | 完成 |
| 2021.6.28 | 21:30-22:30 | 在周期性的維度上,設(shè)計(jì)實(shí)現(xiàn)周期性的裝飾類PeriodicIntervalSet和PeriodicMultiIntervalSet,編寫測試 | 完成 |
| 2021.6.29 | 9:00-10:00 | 實(shí)現(xiàn)Employee、Process、Course類,編寫測試 | 完成 |
| 2021.6.29 | 10:00-12:30 | 設(shè)計(jì)實(shí)現(xiàn)API,編寫測試 | 完成 |
| 2021.6.29 | 13:30-14:00 | 實(shí)現(xiàn)DutyIntervalSet、Process-IntervalSet和CourseIntervalSet,編寫測試 | 完成 |
| 2021.6.29 | 14:00-17:30 | 實(shí)現(xiàn)DutyRosterApp | 測試健壯性時(shí)發(fā)現(xiàn)很多不足,延期一小時(shí)完成 |
| 2021.6.29 | 19:00-22:00 | 實(shí)現(xiàn)CourseScheduleApp | 測試健壯性時(shí)發(fā)現(xiàn)很多不足,延期半小時(shí)完成 |
| 2021.6.30 | 9:00-12:00 | 實(shí)現(xiàn)ProcessScheduleApp | 完成 |
| 2021.6.30 | 16:30-17:30 | 學(xué)習(xí)正則表達(dá)式 | 完成 |
| 2021.6.30 | 18:30-20:30 | 設(shè)計(jì)實(shí)現(xiàn)解析器Parser,編寫測試 | 由于對(duì)正則表達(dá)式不太熟悉,延期半小時(shí)完成 |
| 2021.6.30 | 21:00-22:30 | 向DutyRosterApp中加入解析文件功能 | 完成 |
| 2021.7.1 | 9:00-10:00 | 完善整個(gè)項(xiàng)目的注釋(spec、AF、RI、safe from exposure、test strategy) | 完成 |
| 2021.7.1 | 10:30-11:30 | 修改代碼以面對(duì)新的變化 | 完成 |
| 遇到的難點(diǎn) | 解決途徑 |
| 對(duì)裝飾器不夠熟悉,不知如何編寫正確的裝飾器以實(shí)現(xiàn)功能。 | 在網(wǎng)上查看其它應(yīng)用使用裝飾器的方法并加以總結(jié),逐漸熟悉裝飾器的使用。 |
| 編寫APP時(shí)遇到很多的健壯性問題。 | 仔細(xì)分析每個(gè)步驟中用戶可能的所有輸入,針對(duì)任何非法情況都作出提示。雖然很耗費(fèi)時(shí)間,但效果很好。 |
| 不知如何設(shè)計(jì)正則表達(dá)式來抽取大量的文件信息。 | 在學(xué)習(xí)正則表達(dá)式語法的同時(shí)進(jìn)行一些小的測試,從小的正則表達(dá)式開始,逐漸累積成復(fù)雜的正則表達(dá)式,最終實(shí)現(xiàn)對(duì)文件的解析。 |
設(shè)計(jì)ADT時(shí)一定要考慮得全面且清楚,在最終決定實(shí)現(xiàn)方案后再編寫代碼,否則在后面的應(yīng)用實(shí)現(xiàn)中發(fā)現(xiàn)問題時(shí)只能再重新設(shè)計(jì)。同時(shí),在編寫APP時(shí),一定要事先考慮健壯性的問題,不然,在編寫完代碼之后進(jìn)行測試時(shí)會(huì)遇到很多的健壯性問題,這時(shí)再去修改就會(huì)使得代碼很臃腫,可讀性變差,出錯(cuò)的可能性更高。
面向ADT的編程需要從實(shí)際中進(jìn)行抽象,并進(jìn)行合理的設(shè)計(jì),有很好的可擴(kuò)展性和可復(fù)用性;而直接面向應(yīng)用場景編程只針對(duì)特定應(yīng)用,每次更換應(yīng)用場景時(shí)都要重新編程,擴(kuò)展性很差,只適合簡單的應(yīng)用場景。本實(shí)驗(yàn)中設(shè)計(jì)一個(gè)ADT就可以應(yīng)用到三個(gè)不同的場景,大大縮短了開發(fā)時(shí)間。
這些工作使得客戶端了解各方法的功能但無法得知內(nèi)部具體實(shí)現(xiàn),可以防止內(nèi)部變量被客戶端惡意修改,時(shí)刻檢查表示不變量,保證安全性。雖然這些工作有些麻煩,但卻是好的軟件必須具備的,因此我愿意在以后編程中堅(jiān)持這么做。同時(shí),在復(fù)雜的軟件開發(fā)過程中,好的注釋可以節(jié)省大量閱讀代碼的時(shí)間,使得開發(fā)時(shí)間大大降低。
由于API面向的場景是廣泛的,因此開發(fā)難度很大,但是一旦開發(fā)一個(gè)好的API,就可以在大量場合中應(yīng)用,大大提高代碼的復(fù)用性。
語法驅(qū)動(dòng)編程為實(shí)際應(yīng)用提供了很大的便利,用戶無需繁瑣地一行一行地輸入信息,而只需提供一個(gè)文件以及一定的語法規(guī)則即可。而對(duì)于應(yīng)用的開發(fā)者,只需根據(jù)語法規(guī)則編寫代碼,即使改變語法規(guī)則也可以很快地修改實(shí)現(xiàn)代碼,有很好的可維護(hù)性。
ADT的難度主要體現(xiàn)在抽象上。一個(gè)好的ADT既不能過于具體,也不能過于抽象。需要從大量應(yīng)用場景中尋找共性,抽象的程度也很難把握。對(duì)于這種情況,只能反復(fù)的推敲,比較不同設(shè)計(jì)方案的差異并選擇最好的ADT設(shè)計(jì)方案。
接口、抽象類、類的抽象程度一定是逐漸降低的,將所有應(yīng)用的共性抽象為接口,然后在抽象類以及類中添加新的特性。通過接口的組合可以形成新的接口,并可以具備不同接口中的抽象。類的繼承也可以增加更具體的新的特性。同時(shí),使用正確的設(shè)計(jì)模式可以使得代碼的可復(fù)用性和可維護(hù)性最大化。
難度適中,可以接受,但是工作量很大,尤其是APP的編寫要花費(fèi)很長時(shí)間。雖然給了三周時(shí)間,但是和其他課的實(shí)驗(yàn)、大作業(yè)、考試有重疊,總體上時(shí)間還是很緊。希望各課程的老師可以相互協(xié)調(diào)一下實(shí)驗(yàn)安排。
逐漸理解了軟件構(gòu)造過程中獨(dú)有的思路和方法,也逐漸適應(yīng)了與之前完全不同的編程過程。通過Lab3大量的代碼訓(xùn)練,自己的編程水平也有了極大的提高。
總結(jié)
以上是生活随笔為你收集整理的2021哈工大软件构造Lab3的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 全球与中国键槽拉刀市场深度研究分析报告
- 下一篇: 微信内网页分享,分享者能看到分享的图片(