1. 程式人生 > 其它 >2022-OO-Unit1

2022-OO-Unit1

2022-OO-Unit1

mashiroly

1. 引子

​ 在供參考的部落格要求中,可以體會到課程組呼籲同學們“將關注點置於已完成的程式碼”,分析其結構與問題。本文當然不會缺失這一部分,但本文更願意將重點放在“設計”,縷清這一個月來認識“面向物件”的思路變化。

2. 設計

先簡述迭代過程。
hw1:在題目要求上增加可處理多層括號;
hw2:在題目要求上增加可處理三角函式巢狀因子;
hw3:更新自定義函式巢狀。

2.1 遞迴下降與統一表示

​ 遞進式的三次作業中,架構的整體思路是一致的,即歸納為“遞迴下降”與“統一表示”。hw1已經確定了思路,也因此沒有經歷大規模重構。因此,本小節不強調三次作業題目的區別,大部分直接對hw3的題目表述進行分析。
​ 對個人而言,這也產生了新的問題,眼高手低意味著第一週工作量極大。

2.1.1 遞迴下降:從形式化表述到結構化資料
flowchart TB A[Expr] --> B[Term] B -->C[Factor] C --- D[Num] C --- E[Pow] C --- F[ExprFactor] F -.ONE.-> A C --- G[Tri] C --- H[SelfFunc] C --- I[Sum] G -.TWO.-> C H -.THREE.-> C

​ 上圖為根據形式化表述簡化的表示式結構。根據形式化表述,Expr、Term與Factor天然存在層次關係與遞迴定義。我們根據上圖的方式建立句法分析類Lexer、解析類Parser,將表示式的解析劃分為三層,按字讀取、正則表示式輔助、順序地逐層解析、返回下一層的結果。

​ 在Factor一層,我們遇到了ExprFactor、Tri和SelfFunc三種涉及遞迴的Factor,具體實現用一句話概括,就是“左括號遞迴,右括號返回”。實現ExprFactor到Expr的遞迴,我們就可以處理多層括號巢狀。實現Tri和SelfFunc到Factor的遞迴(下一步往往是再次把解析任務交給ExprFactor),我們就可以處理這兩種函式的巢狀。

​ 這時,我們會遇到一個問題:現在我們的確能將表示式解析成樹狀結構了,但我們最終目的是對錶達式展開化簡——所謂“返回下一層的結果”,返回的是什麼呢?為了構建起樹狀結構,為了父節點能夠”看見“子節點,這個”結果“理應是物件本身,並把子節點物件加入父節點物件的屬性。但是,子節點物件所表示的資料

以何種形式交給它的父節點物件呢?另一種表述是,最底層的子節點物件(葉結點)究竟以什麼形式儲存資料呢?

​ 這個問題直接催生了“統一表示”的思路,並使得設計和實現緊密關聯。

2.1.2 統一表示:化程式設計師所見為程式之所見

​ 對於程式設計師而言,不論是4,還是(x+1),還是(sin((+-x**2-1)**3))**4-+-3*cos(0),甚至是不合法的++--+123,都是人腦可理解、可計算的“算式”,這是因為人腦完全理解“算式”的計算順序、計算規則,並能夠完全看見“算式”的全部資訊並同時利用。

​ 但對程式而言,句法分析類Lexer只能看見當前解析到的位置,物件只能看見物件本身,可以訪問子節點物件(因為已經包含在屬性中)。如果不能做好資料的管理,那麼所謂“物件協同”僅止於構建一棵樹。

​ 站在程式設計師的角度,既然不論何種“算式”都共用同一套計算規則,我們就應該建立一套完備的統一表示,讓各級物件的資料可以互動。統一表示成什麼因人而異,重要的是有其思想。在我的設計中,建立Basic類:

public class Basic {
    private BigInteger coef = BigInteger.ONE;
    private BigInteger pow = BigInteger.ZERO;
    private HashMap<Factor, BigInteger> sin = new HashMap<>();
    private HashMap<Factor, BigInteger> cos = new HashMap<>();
}

完備地將樹中處於葉結點Factor的資料表示為$$coefx**pow\Pi(\sin(Factor)a*cos(Factor)b)$$,例如:1,x**2,sin(x)**4.如需拓展其他函式,只需修改Basic類即可。

Basic類不是葉結點Factor(1+x)顯然需要兩個Basic物件才能表示,因此需要容器basics管理Basic物件,並將容器作為表示該資料的物件的屬性。現在,我們可以將2.1.1節圖中最下一排(從Num到Tri)全部建類,唯一的屬性是basics。結合抽象層次關係,我們讓這些類統一實現Factor介面,運用歸一化的思想,建立歸一化訪問,無差別引用下層資料。對於SelfFunc和Sum,實踐中感覺使用Factor介面比較困難,因此按照2.3.4節的方法當作Expr處理。

在我的設計中,basics是HashMap<Basic, BigInteger>,其中BigInteger是Basic作為一個因子整體的冪次。如(2*x)**2 -> <{2,1,…},2>

​ 我們終於解決了葉結點Factor如何儲存資料的問題,從最底層向上返回的物件終於可以將資料交給上層參與計算了。但統一表示還能更新一步:不論是Term還是Expr也能用basics的形式表示,因此將Expr和Term也實現Factor介面。顯然Expr和ExprFactor類是一致的,我們統一為Expr類。自此,任何一級合法表示式都能用basics表示,只需在類內填補構建、計算、優化的方法了。

2.2 搭建架構

classDiagram class Parser{ -lexer -funclist +Parser(String str, SelfFunclist funclist) +parseExpr() +parseTerm() +parseFactor() } class Lexer{ -cutToken -input -details -num -funcName +Lexer(String str) +reWriteINput +getCurToken() +getDetails() +getNum() +getFuncName() +next() } Parser <.. Lexer Parser <.. SelfFunclist class SelfFunclist{ -funcs +addFunc() +getFuncs() } SelfFunclist *-- SelfFunc class SelfFunc{ -funcList -name -expression -vars +define(String str) +setList() +reference() +getExpression() +getName() } class Sum{ -iter -cnt -start -end +Sum() +reference(String str) } class Factor{ <<interface>> +getBasics() +toString() +getExp() +setExp() +equals() } Factor<|..Num Num:-basics Num:-exp Num:+Num(BigInteger coef) Factor<|..Pow Pow:-basics Pow:-exp Pow:+Pow(BigInteger pow) Factor<|..Sin Sin:-basics Sin:-exp Sin:+Sin(Factor factor, BigInterger exp) Factor<|..Cos Cos:-basics Cos:-exp Cos:+Cos(Factor factor, BigInterger exp) Factor<|..Expr Expr:-basics Expr:-terms Expr:-exp Expr:+getTerms() Expr:+addTerm(Term term) Expr:+addBasics(Term term) Factor<|..Term Term:-basics Term:-factors Term:-exp Term:+addFactor(Factor factor) Term:multBasics(Factor factor) Term:Merge(HashMap basics) Basic ..>Factor Sum ..>Expr SelfFunc ..>Expr class Basic{ -coef -pow -cos -sin +Basic() +Basic(Basic) +multCoef() +addCoef() +addPow() +addSin() +addCos() +merges() +equals() +toString() }

​ 整體思路已經在2.1兩小節清晰表述。Parser類和Lexer類協作,提取當前字元(串)的資料,並將資料分配給對應的物件;所有實現Factor介面的類都具有屬性Basic,只有讀到葉結點時,才會呼叫Basic中的方法儲存資料並返回。Term的方法實現了Factor之間的乘法與冪次,合併同類項,保證交給Expr的Basic冪次為1 且已合併同類項(出於HashMap容器的要求)。Expr的方法實現了Term之間的加法及合併同類項。最後通過toString直接將頂層Expr的basics屬性輸出。

2.3 難點問題

2.3.1 連續符號

​ 連續符號對我而言是hw1的一道坎。最初採用符號計數器,但由於正則表示式的設計,遇到形如+-(...)無法處理。換用思路:遇到負號則遞迴,返回時取相反。實現過程中發現很難確定什麼時候返回,bug頻出。最終採用了直接改寫原串為+1*-1*(...)的形式,並控制pos回撥,順利解決。

2.3.2 冪次

​ 對冪次的處理可能是最糾結的一步:到底給Term處理還是給Expr處理?最終,由於冪次與乘法的相似,將其交給Term,保證返回給Expr時整體的exp一定是1,更體現物件的內聚,也算多了一種debug的方案。

2.3.3 解析自定義函式與求和函式

​ 由於感到困難(偷懶),並沒有對自定義函式與求和函式重新建樹處理,而是利用對括號建棧解析、字串替換得到新的Expr,重新建Parser、傳入函式表解析,這樣返回的仍是Expr,作為Factor,交給上級處理。

3. 度量分析

3.1 類的內聚與耦合

OCavg:平均操作複雜度。

OMax:最大操作複雜度。

WMC:加權操作複雜度。

class OCavg Ocmax WMC
expression.Basic 2.533333333333333 10.0 38.0
expression.Cos 1.3333333333333333 3.0 8.0
expression.Expr 1.5 4.0 15.0
expression.Num 1.3333333333333333 3.0 8.0
expression.Pow 1.3333333333333333 3.0 8.0
expression.SelfFunc 2.142857142857143 6.0 15.0
expression.SelfFuncList 1.0 1.0 3.0
expression.Sin 1.3333333333333333 3.0 8.0
expression.Sum 1.6666666666666667 3.0 5.0
expression.Term 2.4444444444444446 9.0 22.0
MainClass 1.5 2.0 3.0
parser.Lexer 2.8 11.0 28.0
parser.Parser 3.6 11.0 18.0
Total 184.0
Average 1.978494623655914 5.0 13.142857142857142

兩個解析類的複雜度較高,這是遞迴下降本身的因素。而資料類中Term複雜度極高,歸因為乘法和冪次依賴多次遍歷,未能找到更好的方案。

3.2 方法的複雜度

CogC:認知複雜度。

ev(G):基本圈複雜度。

iv(G):設計複雜度。

v(G):圈複雜度。

選擇複雜度最高的方法列出。

method CogC ev(G) iv(G) v(G)
expression.Basic.addCos(Factor, BigInteger) 9.0 4.0 5.0 5.0
expression.Basic.addSin(Factor, BigInteger) 9.0 4.0 5.0 5.0
expression.Basic.equals(Object) 4.0 3.0 5.0 7.0
expression.Term.Merge(HashMap, HashMap) 8.0 4.0 5.0 5.0
expression.SelfFunc.reference(String) 8.0 1.0 7.0 7.0
expression.Term.multBasics(Factor) 24.0 1.0 9.0 9.0
expression.Basic.toString() 18.0 2.0 10.0 10.0
parser.Lexer.next() 13.0 2.0 10.0 11.0
Total 146.0 117.0 172.0 191.0
Average 1.5698924731182795 1.2580645161290323 1.8494623655913978 2.053763440860215

就平均複雜度而言,各方法還算比較簡單。複雜度最高的不出意料仍是multBasics。此外,由於沒用單獨的優化類,優化完全通過toString內部的分支實現,分支複雜。

4. 測試與bug分析

​ 測試主要採用自動評測與手動構造邊界資料結合。由於沒用自己動手實現評測機,這裡描述構造和互測出的bug。

  1. 連續符號,如2.3.1所述;
  2. 自定義函式和求和函式”字串替換實現,遺漏了需要加括號的情況。

​ 總之,bug均出自較“面向過程”的步驟,所在模組複雜度均較高,企圖採用trick避免對結構的設計。在之後的作業中應直面問題,訓練的目的在“學會“而非”答題“。