面向架構程式設計

2022-02-21 02:58:24 字數 3948 閱讀 2621

在領域設計:聚合與聚合根一文中,提到了兩個導致設計與**脫節的情況:

領域設計:聚合與聚合根通過**購物的例子說明了「設計的表現力不足」的問題。本文將通過《敏捷軟體開發:原則、模式與實踐》中保齡球計分軟體的例子來說明「**未反映出軟體架構」的問題。

在開始之前,我們需要了解需求,這裡就是「保齡球的記分規則」:

從上面的規則,我們可以得到初步的設計:

物件初步關係如下:

}從**本身來看,實現足夠簡單,變數名、方法名取得都有意義,符合開發原則,有完整的單元測試。但是,**結構沒有體現出業務邏輯。

上面的**結構如下:

從這個類關係圖中,只能看出來有乙個遊戲(game)和這個遊戲的得分(scorer)!這是從程式設計的角度一步步推導出來的**,在推導的過程中可能是理所當然的,但是過了一段時間後,你再來看這段**的時候,可能就不記得這段**是幹嘛的了!

另一方面,當別人來接手這段**時,你是否是先告訴他業務邏輯,然後讓他看**?但是因為**結構與設計的脫離,導致了雖然已經理解了業務邏輯、**結構也很清晰,但是還是需要讀了原始碼才能清楚這段**具體是幹嘛的!這是否是增加了理解的難度?

原因就是這個結構關係沒有體現出業務邏輯!理想情況應該是在開發人員理解業務以後,從**結構就可以理解具體的實現!

在保齡球記分邏輯中,是有輪(frame)和投擲(throw)這兩個概念的,所以在**中需要保留這兩個類!

public class frame {}

public class throw {}

一局遊戲有十輪,所以在建立game時就初始化十個frame。同時,當前frame的計算,需要前乙個frame的得分,所以除了第乙個frame,其它frame都持有前乙個frame的引用,同時每個frame都知道自己是第幾局(roundnum)!

public class game 

} }}public class frame

public void setpreframe(frame preframe)

}

每一次投擲都會有擊倒數量,所以throw中需要有欄位表示擊倒數量,同時因為一次投擲後,數量是不可修改的,所以數量由建構函式傳入,只有get方法而沒有set方法:

public class throw 

public int getnum()

}

frame可以包括1到3次throw,而按照全中、補中、其它擊中的不同,記分方式也有所不同。如果完全按照這個邏輯編寫,**會相對複雜。因為需要根據擊倒方式的不同,判斷是否要獲取後兩次的投擲。我們是否可以做一些調整?我們實際上是要計算投擲的得分,那麼這個投擲屬於哪一輪,是不是就不是那麼重要了?也就是說,投擲和記分規則可以調整為下面這樣:

現在frame分數的計算就統一了!

public class frame 

return throwscore;

}}

最後,就是怎麼將乙個throw新增到frame中,按照上面的設計調整,一次throw可能既屬於當前輪,也屬於上一輪甚至上上輪!怎們樣來判斷呢?根據frame是全中、還是補中還是其它來判定,所以frame中需要有方法來判定自身是全中、補中還是其它!

public class frame 

private boolean isstrike()

}

一次throw新增到frame後,還要判斷這個frame是否已經結束,即:

public class frame 

return false; }

}

public class frame 

}

public class game 

private void add2preframe(throw athrow)

if (currentframeidx - 2 >= 0 && !framelist[currentframeidx - 2].isfinish())

}}

調整後的設計如下:

對應的類結構如下:

此結構與設計相符和,只要理解了業務邏輯,順著業務就可以梳理出**結構,即使不看原始碼,也能猜到**的邏輯!

《敏捷》中有效**行數為71行,上面的有效**為79行,多了8行**!但是從理解上來看的話,後者更易於理解!完整**見下文。

public class game 

} }public int score()

public void add(int pins)

private void add2preframe(throw athrow)

if (currentframeidx - 2 >= 0 && !framelist[currentframeidx - 2].isfinish())

} public int scoreforframe(int theframe)

}public class frame

public int score()

return throwscore;

} public int add(throw athrow)

public boolean isfinish()

return false;

} private boolean isspare()

private boolean isstrike()

public void setpreframe(frame preframe)

}public class throw

public int getnum()

}

本文通過《敏捷》中保齡球的例子,來說明了**不能體現設計的原因及提出一種保證**和設計相一致的方法。

設計本身就是一種取捨,沒有完全正確的方法,只有適合的方法。從**本身出發,能夠構建出符合編碼原則的**,但是可能和設計本身有出入,這可能會增加後續的理解難度,變相增加了修改**的難度;反之從設計觸發,能構建出和設計相匹配的**,但是可能**本身的易讀性、**量、符合編碼原則上會有所妥協。

個人認為,對於業務邏輯不複雜,但是計算邏輯很複雜的**,以按照**原則來編寫**為主,以按照業務邏輯編寫**邏輯為輔,以保證**的簡潔明瞭;而對於業務邏輯複雜,但是計算邏輯不複雜的**,以按照業務邏輯編寫**為主,以按照**原則編寫**為輔,以保證**結構與業務邏輯的直觀匹配。

以上內容僅為個人觀點,歡迎**!

SOA面向服務化程式設計架構 dubbo

dubbo 是阿里系的技術。並非 系的技術啦,系的分布式服務治理框架式hsf啦 只聞其聲,不能見其物。而dubbo是阿里開源的乙個soa服務治理解決方案,dubbo本身 整合了監控中心,註冊中心,負載集群.等等。和整體的框架還是很優雅滴呀!github位址 目前發布的版本是2.5.3,gihub上的...

《架構整潔之道》之物件導向程式設計

物件導向是封裝 繼承 多型三項的有機組成。通過採取封裝特性,我們可以把一組相關聯的資料和函式圈起來,使圈外面的 只能看見部分函式,資料則完全不可見。譬如,在實際應用中,類中的公共函式和私有成員變數就是這樣。繼承的主要作用是讓我們可以在某個作用域內對外部定義的某一組變數與函式進行覆蓋。多型是函式指標的...

SOA面向服務化程式設計架構 dubbo

dubbo 是阿里系的技術。並非 系的技術啦,系的分布式服務治理框架式hsf啦 只聞其聲,不能見其物。而dubbo是阿里開源的乙個soa服務治理解決方案,dubbo本身 整合了監控中心,註冊中心,負載集群.等等。和整體的框架還是很優雅滴呀!github位址 目前發布的版本是2.5.3,gihub上的...