(防扒小助手) 本人CSDN博客: https://blog.csdn.net/m0_61753302 本人博客園博客(同步CSDN): 何以牽塵 - 博客園 (cnblogs.com)https://www.cnblogs.com/kalesky/ 如果對你有用的話歡迎點贊關註喲! ...
(防扒小助手)
本人CSDN博客:
https://blog.csdn.net/m0_61753302
本人博客園博客(同步CSDN):
何以牽塵 - 博客園 (cnblogs.com)https://www.cnblogs.com/kalesky/
如果對你有用的話歡迎點贊關註喲!
目錄
3.2.5 任務5:投票活動Poll的實現類GeneralPollImpl
3.3.2 任務8:採用Strategy設計模式實現靈活的計票規則
3.3.3 任務9:採用Strategy設計模式實現靈活的遴選規則
3.3.5 任務11:採用Visitor設計模式實現功能擴展
3.5.3 聚餐點菜應用:取消權重設置、只計算“喜歡”的票數
1、實驗目標概述
本次實驗覆蓋課程第 2、3 章的內容,目標是編寫具有可復用性和可維護性
的軟體,主要使用以下軟體構造技術:
- 子類型、泛型、多態、重寫、重載
- 繼承、委派、CRP
- 語法驅動的編程、正則表達式
- 設計模式
本次實驗給定了多個具體應用,學生不是直接針對每個應用分別編程實現,而是通過 ADT 和泛型等抽象技術,開發一套可復用的 ADT 及其實現,充分考慮這些應用之間的相似性和差異性,使 ADT 有更大程度的復用(可復用性)和更容易面向各種變化(可維護性)。
2、實驗環境配置
2.1 實驗環境
Intellij IDEA 2022.1(Ultimate Edition)
2.2 GitHub Lab3倉庫的URL地址
略。
3、實驗過程
3.1 待開發的三個應用場景
3.1.1 應用場景
① 商業表決(BusinessVoting)
面向某個商業公司,其內部成員提出某個商業提案(例如“關於投資 xx 項目的提案”),公司董事會的各位董事對其進行實名錶決(支持、反對、棄權),各董事在表決中的權重取決於其所持有的公司股票的比例,根據該持股比例對投票結果進行計算,若“支持”票的比例超過2/3,則該提案通過,否則該提案不通過。
② 代表選舉(Election)
針對某次活動(例如哈工大學生代表大會),需要從一群候選人中選出若幹人,作為代表參加活動。在該選舉中,提前確定一部分候選人,投票人從已確定的候選人中選取,不可提名新的候選人。計劃選出的代表數量是提前確定的。投票人針對每個候選人匿名選擇“支持、反對、棄權”之一,但選擇“支持”的人數不能高於計劃選出的代表數量,否則為非法票。所有投票人的權重均相等。
③ 聚餐點菜(DinnerOrder)
一群人去餐館就餐,需要從該餐館提供的菜單中選擇若幹道菜,點菜的數量要大於等於就餐總人數,且小於總人數+5。每個人針對菜單上的每一道菜實名錶達自己的喜好(喜歡、不喜歡、無所謂),選擇這三個選項的數目無限制。不同人的身份不同,其偏好的影響力會有所不同(例如家庭聚餐時,老人的權重更大、子女的權重更小,見下表黃色部分)。所有人表達觀點之後,根據影響力加權計票(喜歡、不喜歡、無所謂分別得分2、0、1),取總得分最高的前k道菜。
3.1.2 共性需求
3.1.3 差異
3.2 ADT識別與設計
3.2.1 任務1:投票類型VoteType
(1)測試策略
根據VoteType中方法的spec,對checkLegality和getScoreByOption方法進行設計測試。
checkLegality():
通過對已有的投票類型和未出現的投票類型進行checkLegality的正確性測試。
getScoreByOption():
給定的投票選項的名稱,通過測試其getScoreByOption返回的值是否與給定值相等進行正確性的判斷。
(2)欄位和方法
① spec規約
<1> Rep Invariants
選項應該至少有2個,每個選項的長度不超過5,且不允許出現空格。
<2> Abstract Function
使用options實現的函數對應關係的映射。
<3> Safety from Rep Exposure
必要時使用防禦性拷貝防止表示泄露。
② 欄位:
<1> private Map<String, Integer> options = new HashMap<>();
key表示選項名字,value表示該選項所對應的權重。
<2> private String support;
用來記錄哪個選項表示支持。
③ 方法:
<1> private boolean checkRep()
檢查不變性:如果有選項名稱長度超過5或者少於兩個選項,則返回false,否則返回true。
<2> public VoteType()
預設使用“支持”(1)|“反對”(-1)|“棄權”(0)這三種投票選項。
<3> public VoteType(Map<String, Integer> origin)
傳入一個給定的Map類型參數,建立一個VoteType類型的對象。
<4> public VoteType(String regex)
根據滿足特定語法規則的字元串,創建一個投票類型對象。
<5> public boolean checkLegality(String option)
判斷一個投票選項是否合法(用於Poll中對選票的合法性檢查)。
<6> public int getScoreByOption
根據一個投票選項,得到其對應的分數。
<7> public String getSupport()
返回VoteType中的支持選項的選項名。
(3)測試結果
測試結果運行如下:
3.2.2 投票項VoteItem<C>
(1)測試策略
根據VoteItem中方法的spec,對getCandidate和getVoteValue方法進行設計測試。
getCandidate():
給定一個投票項,通過觀察調用getCandidate()返回值是否為給定的candidate。
getVoteValue():
給定一個投票項,通過觀察調用getVoteValue()返回值是否為給定的value。
(2)欄位和方法
① spec規約
<1> Rep Invariants
選項名稱不允許出現空格。
<2> Abstract Function
投票選項對候選人類型及String類型的映射
<3> Safety from Rep Exposure
欄位使用private,必要時使用防禦性拷貝防止表示泄露。
② 欄位
<1> private C candidate
本投票項所針對的候選對象
<2> private String value;
對候選對象的具體投票選項,例如“支持”、“反對”等
③ 方法
<1> private boolean checkRep()
檢查不變性:如果有選項名稱長度超過5或者出現空格,則返回false,否則返回true。
<2> public VoteItem(C candidate, String value)
創建一個投票項對象
<3> public String getVoteValue()
得到該投票選項的具體投票項
<4> public C getCandidate()
得到該投票選項所對應的候選人
(3)測試結果
測試結果運行如下:
3.2.3 任務3:選票Vote
(1)測試策略
根據VoteItem中方法的spec,對getCandidate和getVoteValue方法進行設計測試。
getCandidate():
給定一個投票項,通過觀察調用getCandidate()返回值是否為給定的candidate。
getVoteValue():
給定一個投票項,通過觀察調用getVoteValue()返回值是否為給定的value。
(2)欄位和方法
① spec規約
<1> Rep Invariants
合法的voteItem
<2> Abstract Function
對應到自己的成員變數
<3> Safety from Rep Exposure
rep均為private,採用防禦式拷貝
② 欄位
<1> static private int num
已經產生了多少票,用於為id計數
<2> private int id;
用於標記投票的序號
<3> private Set<VoteItem<C>> voteItems
一個投票人對所有候選對象的投票項集合
<4> private Calendar date
投票時間
③方法
<1>public Vote(Set<VoteItem<C>> voteItems)
創建一個選票對象
<2>public Set<VoteItem<C>> getVoteItems()
查詢該選票中包含的所有投票項
<3>public boolean candidateIncluded(C candidate)
一個特定候選人是否包含本選票中
(3)測試結果
測試結果運行如下:
3.2.4 任務4:投票活動Poll<C>的測試
(1)測試策略:
由於Poll<C>介面中的方法均為void類型,故採用getter方法對其操作結果進行觀察,比較與預期結果是否相同,通過此策略進行測試。
(2)測試方法:
① setInfo()
② addCandidates()
③ addVoters()
④ addVote()
3.2.5 任務5:投票活動Poll<C>的實現類GeneralPollImpl
(1)spec規約
<1> Rep Invariants
quantity為整數
<2> Abstract Function
映射到實際的一次投票活動
<3> Safety from Rep Exposure
用protected防止泄露和必要的防禦性拷貝
(2)欄位和方法
① rep
<1> 投票活動的名稱
protected String name;
<2> 投票活動發起的時間
protected Calendar date;
<3> 候選對象集合
protected List<C> candidates;
<4> 投票人集合,key為投票人,value為其在本次投票中所占權重
protected Map<Voter, Double> voters;
<5> 擬選出的候選對象最大數量
protected int quantity = Integer.MAX_VALUE;
<6> 本次投票擬採用的投票類型(合法選項及各自對應的分數)
protected VoteType voteType;
<7> 所有選票集合
protected Set<Vote<C>> votes = new HashSet<>();
<8> 計票結果,key為候選對象,value為其得分
protected Map<C, Double> statistics;
<9> 遴選結果,key為候選對象,value為其排序位次
protected Map<C, Double> results;
<10> 非法選票的集合
protected Set<Vote<C>> illegalVotes = new HashSet<>();
② 方法
<1> private boolean checkRep()
檢查不變數:quantity需要大於0
<2> public void setInfo(String name, Calendar date, VoteType type, int quantity)
設置投票活動的相關信息
<3> public void addVoters(Map<Voter, Double> voters)
向投票活動中添加voters及其對應的權重
<4> public void addCandidates(List<C> candidates)
向投票活動中添加candidates
<5> protected boolean isLegal(Vote<C> vote)
檢查投票是否合法
<6> public void addVote(Vote<C> vote)
想投票活動中添加選票
<7> protected boolean checkVotes(Set<Vote<C>> votes)
檢查票的合法性
<8> public void statistics(StatisticsStrategy<C> ss)
按規則計票
<9> public void selection(SelectionStrategy<C> ss)
按規則遴選
<10> public String result()
輸出遴選結果
<11> 額外構造的輔助方向(如getter方法)如下
public void accept(Visitor visitor);
public String getName();
public Calendar getDate();
public Set<Vote<C>> getVotes();
public Map<Voter, Double> getVoters();
public Set<Vote<C>> getIllegalVotes();
public List<C> getCandidates();
public int getQuantity();
public VoteType getVoteType();
public String toString();
(3)測試結果
測試結果如下:
3.2.6 任務6:投票活動Poll<C>的子類型
(1)BusinessVoting
需要重寫addVote和checkVotes方法
候選對象數量只能為1,必須為實名投票,在計算得票時還需考慮投票人所占權重。
① addVote方法重寫如下:
先檢查vote是否為實名投票,再檢查vote是否合法,若vote不合法或者voters中未包含該vote的投票人,則將該選票列入illegalVotes集合中。
② checkVotes方法重寫如下:
對於votes中的每一個vote,如果vote的voter不包含在該投票活動poll的voters中,則將該選票列入illegalVotes集合中。若在已投票的投票人集合votedVoter中包含了該voter,則將該投票人列入reVoters中,併在之後的計票活動中將重覆投票的voter的選票不計入在內。
(2)Election
需要檢查一個投票人投的支持票總數是否小於等於候選對象數量,該投票活動支持匿名投票。所有投票人的權重均相同。
① 增添一個新方法supportCount
用於返回一個選票內的贊成票數量。
② 重寫addVote方法:
對vote進行判斷,如果vote非法,則將其加入非法選票集合illegalVotes。 如果該投票人投的支持票數量大於候選人數量,也將其加入非法選票集合illegalVotes。
(3)DinnerOrder
需要重寫addVote和checkVotes方法
必須為實名投票,在計算得票時還需考慮投票人所占權重。
① addVote方法重寫如下:
先檢查vote是否為實名投票,再檢查vote是否合法,若vote不合法或者voters中未包含該vote的投票人,則將該選票列入illegalVotes集合中。
② checkVotes方法重寫如下:
對於votes中的每一個vote,如果vote的voter不包含在該投票活動poll的voters中,則將該選票列入illegalVotes集合中。若在已投票的投票人集合votedVoter中包含了該voter,則將該投票人列入reVoters中,併在之後的計票活動中將重覆投票的voter的選票不計入在內。
(4)測試結果
① BusinessVotingTest
Junit測試結果如下:
② ElectionTest
Junit測試結果如下:
③ DinnerOrderTest
Junit測試結果如下:
3.3 ADT行為的設計與實現
3.3.1 任務7:合法性檢查
GeneralPollImpl中將合法性檢查的行為抽取到一個單獨的方法isLegal之中進行合法性檢查:
對一張選票的各投票選項進行考察,若投票選項與候選人數目不相同,則返回非法值false。
之後對投票選項進行遍歷每一個投票,若所投票的對象不包含在本次投票的候選人之中,或者投票的類型與所投值不相符號,則返回非法值false。
在addVote方法中對isLegal方法進行調用即可:
並將isLegal判斷所對應的非法選票加入到非法選票的集合illegalVotes中。
3.3.2 任務8:採用Strategy設計模式實現靈活的計票規則
(1)BusinessStatistics計票規則
遍歷Vote<Proposal>類型的votes集合中的每一張選票vote。若vote在非法選票集合illegalVotes中,則不計入該選票。
由於BusinessVoting是實名投票,故需要獲取投票人的權重以便進行最終結果的計算。
對每一張選票vote的每一個投票選項VoteItem<Proposal>進行遍歷,在還沒進行遍歷之前先對議案的結果進行初始化,如果投票選項的值為正數,則表示支持,通過加權計入相應proposal的計票結果中。
(2)ElectionStatistics計票規則
遍歷Vote<Person>類型的votes集合中的每一張選票vote。若vote在非法選票集合illegalVotes中,則不計入該選票。
由於Election是匿名投票,所以不需要獲取投票人的權重等信息。
對每一張選票vote的每一個投票選項VoteItem<Proposal>進行遍歷,在還沒進行遍歷之前先對議案的結果進行初始化,如果投票選項的值為正數,則表示支持,相加計入相應person的計票結果中。
(3)DinnerStatistics計票規則
遍歷Vote<Dish>類型的votes集合中的每一張選票vote。若vote在非法選票集合illegalVotes中,則不計入該選票。
由於DinnerOrder是實名投票,故需要獲取投票人的權重以便進行最終結果的計算。
對每一張選票vote的每一個投票選項VoteItem<Dish>進行遍歷,在還沒進行遍歷之前先對議案的結果進行初始化,對所給的投票值通過加權計入相應dish的計票結果中。
3.3.3 任務9:採用Strategy設計模式實現靈活的遴選規則
(1)BusinessSelection遴選規則
初始化proposal的占比為0.0,本規則對Business進行了擴展,可同時提出多個proposal而不僅限於一個proposal,以適應未來任務的要求。
若對應proposal的計票結果超過了2/3,則將其放入result中傳出給結果。即遴選出最終方案。
(2)ElectionSelection遴選規則
首先將所有候選人放入TreeSet中利用TreeSet進行排序:
Set<Person> set = new TreeSet<>(new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
if(statistics.get(o1) > statistics.get(o2)) return -1;
else if(statistics.get(o1) < statistics.get(o2)) return 1;
return o1.getName().compareTo(o2.getName());
}
});
set.addAll(statistics.keySet());
Election要求根據支持票數量排序,前k個候選人當選;若有多個候選人的支持票數量相等而無法自然排出前k名,則僅有那些明確可進入前k名的人當選。
因此需要判斷第k名和第k+1名的分數是否相同,若得分不同,則取出這前k名作為最終結果傳遞給result輸出。若得分相同則需要從頭重新遍歷,取出前面與這第k名的分數不同的所有候選人Person,傳遞給result輸出。
double rank = 0.0, score = Integer.MIN_VALUE;
Iterator<Person> iterator = set.iterator();
for(int i = 0; i < Math.min(quantity, statistics.size()); i++) {
score = statistics.get(iterator.next());
}
if(!iterator.