打造堅如磐石的代碼:18種高內聚原則精解
高內聚原則是GRASP(General Responsibility Assignment Software Principles)中的一個重要原則,它強調一個類或模塊應該只包含與其功能緊密相關的操作和數據,從而提高代碼的可讀性和可維護性。
1. 高內聚設計圖
高內聚原則鼓勵開發者將相關的數據和處理數據的方法封裝在同一個類中,一個類或模塊只負責一項任務,使得每個類都成為其數據的“專家”。
在這里插入圖片描述
Order
:代表訂單,包含訂單編號、商品項列表、用戶、支付方式等屬性,以及添加商品項、處理支付等方法。OrderItem
:代表訂單中的商品項,包含產品、數量和價格屬性,以及計算總價的方法。Customer
:代表用戶,包含用戶編號、名字、電子郵件等屬性,以及獲取全名的方法。PaymentMethod
:代表支付方式,包含支付詳情屬性,以及驗證支付方法和處理支付的方法。Product
:代表產品,包含產品編號、名稱和價格屬性。OrderStatus
:一個枚舉,定義了訂單可能的狀態。
1. 高內聚解決的問題
此原則解決了數據和行為分離的問題,避免了在系統中不必要的數據傳遞和復雜性。
3. 高內聚特點
- 數據封裝:將數據和處理數據的方法封裝在同一個類中。
- 高內聚性:類具有高內聚性,每個類都專注于處理自己的數據。
- 低耦合度:類之間的耦合度降低,因為每個類都管理自己的數據。
4. 高內聚缺點
- 過度封裝:在某些情況下,可能會導致過度封裝,使得類過于復雜。
- 職責不明確:在一些復雜的系統中,職責的劃分可能不夠明確。
5. 高內聚使用場景
當系統中的類需要處理特定數據時,應考慮應用高內聚原則。
6. 高內聚案例
6.1 訂單管理系統案例
訂單管理系統處理訂單數據和相關業務邏輯。
實現前(違反高內聚原則):
1publicclassOrder{
2privateString orderId;
3privatedouble totalAmount;
4privateList<OrderItem> items;
5privateCustomer customer;
6privatePaymentMethod paymentMethod;
7
8// 僅包含基本的getter和setter方法
9}
10
11publicclassOrderService{
12privateList<Order> orders;
13
14publicvoidprocessOrderPayment(Order order){
15// 處理訂單支付邏輯
16// 可能包括驗證支付方法、計算總金額等
17}
18}
實現后(遵循高內聚原則):
1publicclassOrder{
2privateString orderId;
3privateList<OrderItem> items;
4privateCustomer customer;
5privatePaymentMethod paymentMethod;
6privateOrderStatus status;
7
8publicOrder(String orderId,Customer customer,PaymentMethod paymentMethod){
9this.orderId = orderId;
10this.customer = customer;
11this.paymentMethod = paymentMethod;
12this.status =OrderStatus.PENDING;
13this.items =newArrayList<>();
14}
15
16publicvoidaddItem(OrderItem item){
17 items.add(item);
18updateTotalAmount();
19}
20
21privatevoidupdateTotalAmount(){
22double total = items.stream().mapToDouble(OrderItem::getTotalPrice).sum();
23// 更新訂單總金額邏輯
24}
25
26publicvoidprocessPayment(){
27if(paymentMethod.isValid()){
28 paymentMethod.charge(getTotalAmount());
29 status =OrderStatus.COMPLETED;
30}else{
31 status =OrderStatus.FAILED;
32}
33}
34
35publicdoublegetTotalAmount(){
36return items.stream().mapToDouble(OrderItem::getTotalPrice).sum();
37}
38
39publicOrderStatusgetStatus(){
40return status;
41}
42
43// 其他與訂單相關的業務邏輯
44}
45
46publicclassOrderItem{
47privateProduct product;
48privateint quantity;
49privatedouble price;
50
51publicdoublegetTotalPrice(){
52return price * quantity;
53}
54
55// 其他與訂單項相關的業務邏輯
56}
57
58publicclassCustomer{
59privateString customerId;
60privateString name;
61privateString email;
62
63// 客戶相關業務邏輯
64}
65
66publicclassPaymentMethod{
67privateString paymentDetails;
68
69publicbooleanisValid(){
70// 驗證支付方法邏輯
71returntrue;
72}
73
74publicvoidcharge(double amount){
75// 處理支付邏輯
76}
77
78// 其他與支付方法相關的業務邏輯
79}
80
81publicclassOrderService{
82privateList<Order> orders;
83
84publicvoidplaceOrder(Order order){
85 orders.add(order);
86 order.processPayment();
87}
88
89publicvoidcancelOrder(Order order){
90 order.setStatus(OrderStatus.CANCELED);
91}
92}
93
94publicenumOrderStatus{
95PENDING,COMPLETED,CANCELED,FAILED
96}
7、高內聚實現方式
高內聚是軟件設計中追求的目標,它意味著一個模塊、類或函數集中于完成一個單一的任務或職責。
- 單一職責原則(Single Responsibility Principle, SRP)
一個類應該只有一個引起它變化的原因。這樣,當需求變化時,只有相關的類需要修改。
- 相關功能封裝
將相關的數據和行為封裝在同一個類中,使得類成為其數據的“專家”,從而提高內聚性。
- 最小化接口暴露
只暴露必要的操作和數據,減少類與外部的交互,使得類的內部操作更加緊密相關。
- 避免過長的類或方法
過長的類或方法往往承擔了過多的職責,難以維護。將它們拆分成更小的單元可以提高內聚性。
- 使用內聯方法(Inline Method)
如果一個方法只在一個類中被調用,可以考慮將其內聯,以減少方法間的不必要分離。
- 提煉類(Refactoring to Class)
如果一組相關數據和行為分散在多個類中,可以提煉出一個新類來封裝這些相關元素。
- 增強封裝性
通過限制對內部數據的直接訪問,提供公共接口來操作數據,可以增加類的內聚性。
- 利用組合而非繼承
組合可以提供更靈活的方式來組織代碼,避免繼承帶來的復雜性和緊密耦合。
- 模塊化設計
將系統分解為多個模塊,每個模塊負責系統的一個特定方面,減少了模塊間的依賴。
- 職責鏈模式(Chain of Responsibility Pattern)
通過創建一系列處理者對象來處理請求,每個處理者只處理它負責的請求類型,從而提高內聚性。
- 策略模式(Strategy Pattern)
定義一系列算法,并將每一個算法封裝起來,使它們可以互換,算法的變化不會影響到使用算法的客戶。
- 狀態模式(State Pattern)
允許一個對象在其內部狀態發生改變時改變其行為,從而將相關的變更邏輯封裝在類內部。
- 聚合關系(Aggregation)
使用聚合關系來表示整體與部分的關系,但部分可以獨立于整體存在,這有助于保持類的職責單一和內聚。
- 服務類(Utility Classes)
對于提供通用功能但與業務邏輯無關的類,可以將其設計為服務類,這些類通常包含靜態方法,用于執行特定的輔助任務。
- 領域驅動設計(Domain-Driven Design, DDD)
通過DDD的戰術設計模式,如實體(Entity)、值對象(Value Object)、聚合(Aggregate)、領域服務(Domain Service)等,來提高模型的內聚性。
- 模塊化編程(Modular Programming)
將程序分解為可重用的模塊,每個模塊包含一組相關的功能,減少模塊間的依賴。
- 依賴注入(Dependency Injection)
使用依賴注入來降低組件之間的耦合度,提高組件的內聚性,因為組件不再負責其依賴的創建和管理。
- 策略枚舉(Strategy Enums)
在某些語言中,可以使用枚舉來實現策略模式,每個枚舉實例代表一個策略,這樣可以將策略的變體封裝在一個小的、內聚的范圍內。
8、高內聚(High Cohesion)與充血模型(Rich Domain Model)區別:
高內聚
- 定義:高內聚是指一個模塊或類中的元素彼此之間功能相關的程度高,每個模塊或類負責一項任務或職責。
- 目的:高內聚的目的是確保代碼的可讀性、可維護性和可重用性。它通過集中相關功能來減少模塊間的交互。
- 應用:通常在設計單個類或模塊時使用,強調一個類應該只有一個改變的原因。
充血模型
- 定義:充血模型是一種軟件設計模式,其中領域對象不僅包含數據,還包含操作這些數據的行為和邏輯。
- 目的:充血模型旨在創建一個高度模塊化和可維護的系統,領域對象負責自己的行為和狀態,提高了系統的靈活性和可維護性。
- 應用:通常應用于整個系統的架構設計,強調領域對象的自足性和業務邏輯的封裝。
主要區別
- 設計層面:
- 高內聚原則更側重于單個類或模塊的設計,確保它們功能集中。
- 充血模型更側重于系統架構層面,強調整個系統的模塊化和領域對象的自足性。
- 設計哲學:
- 高內聚是一種設計原則,關注于減少類內部元素的多樣性,確保它們緊密相關。
- 充血模型是一種設計模式,關注于領域對象的完整性和自足性,確保它們能夠獨立地執行業務邏輯。
- 實現方式
- 高內聚通過確保類或模塊的功能緊密相關來實現。
- 充血模型通過將業務邏輯和狀態管理封裝在領域對象內部來實現。
- 目的
- 高內聚原則的目的是提高代碼的內聚性和可維護性。
- 充血模型的目的是創建一個高度模塊化和可維護的系統,使得領域對象能夠獨立地執行業務邏輯。
9. 參考開源框架
許多領域驅動設計(DDD)框架,如Axon Framework和Apache Isis,都遵循高內聚原則來組織代碼。
10. 總結
高內聚原則是GRASP原則中的一個重要組成部分,它通過將數據和行為封裝在同一個類中,提高了代碼的內聚性和可維護性。遵循高內聚原則有助于創建一個清晰、直觀的系統結構,簡化了數據管理。雖然在某些情況下可能需要權衡封裝的程度。