Summary:兩個類之間有雙向關聯,但其中一個類現在再也不須要另外一個類的特性。去除沒必要要的關聯。java
動機:程序員
雙向關聯頗有用,但你也必須爲它付出代價,那就是維護雙向鏈接、確保對象被正確建立和刪除而增長的複雜度。並且,因爲不少程序員並不習慣使用雙向關聯,它每每成爲錯誤之源。算法
大量的雙向鏈接也很容易形成「殭屍對象」:某個對象原本已經該死亡了,卻任然保留在系統中,由於對它的引用尚未徹底清除。數據庫
此外,雙向關聯也迫使兩個類之間有了依賴:對其中任何一個類的修改,均可能引起另外一個類的變化。若是這兩個類位於不一樣的包,這種依賴就是包與包之間的相依。過多的跨包依賴會形成緊耦合系統,使得任何一點小改動均可能形成許多沒法預知的後果。安全
只有在真正須要雙向關聯的時候,才應該使用它。若是發現雙向關聯再也不有存在的價值,就應該去掉其中沒必要要的一條關聯。函數
作法:性能
1.找出保存「你想去除的指針」的字段,檢查它的每個用戶,判斷是否能夠去除改指針測試
à不但要檢查直接訪問點,也要檢查調用這些直接訪問點的函數。this
à考慮有無可能不經過指針取得被引用對象。若是有可能,你就能夠對取值函數使用Substitute Algorithm,從而讓客戶在沒有指針的狀況下也可使用該取值函數spa
à對於使用該字段的全部函數,考慮將被引用對象做爲參數傳進去。
2.若是客戶使用了取值函數,先運用Self Encapsulate Field將待刪除字段自我封裝起來,而後使用Substitute Algorithm對付取值函數,令它再也不使用該字段,而後編譯、測試。
3.若是客戶未使用取值函數,那就直接修改待刪除字段的全部被引用點:改以其餘途徑得到該字段所保存的對象。每次修改後,編譯並測試。
4.若是已經沒有任何函數使用帶刪除字段,移除全部對該字段的更新邏輯,而後移除該字段
à 若是有許多地方對此字段賦值,先運用Self Encapsulate Field使這些地點改用同一個設值函數。編譯、測試。然後將這個設值函數的本體清空。再編譯、再測試。若是這些均可行,就能夠將此字段和其設值函數,連同對設值函數的全部調用,所有移除。
5.編譯,測試。
範例
本例從Change Unidirectional Association to Bidirectional留下的代碼開始進行,其中Customer和Order之間有雙向關聯:
class Order... Customer getCustomer(){ return _customer; } Void setCustomer(Customer arg){ if(_customer !=null){ _customer.friendOrders().remove(this); } _customer = arg; if(_customer !=null){ _customer.friendOrders().add(this); } private Customer _customer; } class Customer ... void addOrder(Order arg){ arg.setCustomer(this); } private Set _orders = new HashSet(); Set friendOrders(){ /** should only be used by Order*/ return _orders; }
後來咱們發現,除非先有Customer對象,不然不會存在Order對象。所以我想將從Order到Customer的鏈接移除掉。
對於本項重構來講,最困難的就是檢查可行性。若是我知道本項重構時安全的,那麼重構手法自身十分簡單。問題在因而否有任何代碼依賴_customer字段存在。若是確實有,那麼在刪除這個字段以後,必須提供替代品。
首先,須要研究全部讀取這個字段的函數,以及全部使用這些函數的函數。我能找到另外一條途徑來提供Customer對象嗎--這一般意味着將Customer對象做爲參數傳遞給用戶。下面是一個簡化例子:
class Order ... double getDiscountedPrice(){ return getGrossPrice() * (1 - _customer.getDiscount()); }
改變爲
class Order... double getDiscountedPrice(Customer customer){ return getGrossPrice() * (1 _ customer.getDiscount()); }
若是待改函數式被Customer對象調用的,那麼這樣的修改方案特別容易實施,由於Customer對象將本身做爲參數傳給函數很容易。因此下列代碼:
class Customer .. double getPriceFor(Order order){ Assert.isTrue(_orders.contains(order)); return order.getDiscountedPrice(); }
變成了:
class Customer ... double getPriceFor(Order order){ Assert.isTrue(_orders.contains(order)); return order.getDiscountedPrice(this); }
另外一種作法就是修改取值函數,使其在不使用_customer字段的前提下返回一個Customer對象。若是這行得通,就可使用Substitute Algorithm修改Order.getCustomer()函數算法。我有可能這樣修改代碼:
Customer getCustomer(){ Iterator iter = Customer.getInstances().iterator(); while(iter.hasNext()){ Customer each = (Customer) iter.next(); if(each.containsOrder(this)){ return each; } } return null; }
這段代碼比較慢,不過確實可行。並且在數據庫環境下,若是我須要使用數據庫查詢語句,這段代碼對系統性能的影響可能並不顯著。若是Order類中有些函數使用_customer字段,咱們能夠實施Self Encapsulate Field令它們轉而改用上述的getCustomer()函數。
若是咱們要保留上述的取值函數,那麼Order和Customer的關聯從接口上看雖然還是雙向的,但實現上已是單向關係了。隨人移除了反指針,但兩個類彼此之間的依賴關係仍然存在。
既然要替換取值函數,那麼咱們就專一地替換它,其餘部分留待之後處理。逐一修改取值函數的調用者,讓它們經過其餘來源取得Customer對象。每次修改後都編譯並測試。實際工做中這一過程每每至關快。若是這個過程讓咱們以爲很棘手很複雜,咱們能夠放棄本項重構。
一旦消除了_customer字段的全部讀取點。就能夠着手處理對此字段賦值的函數了。很簡單,只要把這些賦值動做所有移除,再把字段一併刪除就好了。因爲已經沒有任何代碼須要這個字段。因此刪掉它並不會帶來任何影響