Change Bidirectional Association to Unidirectional

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字段的全部讀取點。就能夠着手處理對此字段賦值的函數了。很簡單,只要把這些賦值動做所有移除,再把字段一併刪除就好了。因爲已經沒有任何代碼須要這個字段。因此刪掉它並不會帶來任何影響

相關文章
相關標籤/搜索