Change Unidirectional Association to Bidirectional

Summary:兩個類都須要使用對方特性,但其間只有一條單向鏈接。添加一個反向指針,並使修改函數可以同時更新兩條鏈接。java

                                               

動機:函數

  開發初期,你可能會在兩個類之間創建一條單向鏈接,使其中一個類能夠引用另外一個類。隨着時間推移,你可能發現被引用類須要獲得其引用者以便進行某些處理。也就是說它須要一個反向指針。但指針是一種單向鏈接,你不可能反向操做它。一般你能夠繞道而行,雖然會耗費一些計算時間,成本還算合理,而後你能夠在被引用類中創建一個函數專門負責此行爲。可是,有時候想繞過這個問題並不容易,此時就須要創建雙向引用關係,或稱爲反向指針。若是使用不當,反向指針很容易形成混亂;但只要你習慣了這種手法,它們其實並非太複雜。測試

「反向指針」手法有點棘手,因此在你可以自如運用以前,應該由相應的測試。一般我不花心思去測試訪問函數,由於普通訪問函數的風險沒有高到須要測試的地步,但本重構要求測試訪問函數,因此它是極少數須要添加測試的重構手法之一。this

本重構運用反向指針實現雙向關聯。其餘技術(例如鏈接對象)須要其餘重構手法。spa

作法:指針

1.在被引用類中增長一個字段,用以保存反向指針。code

2.決定由哪一個類引用端仍是被引用端控制關聯關係對象

3.在被控端創建一個輔助函數,其命名應該清楚指出它的有限用途。ci

4.若是既有的修改函數在控制端,讓它負責更新反向指針。開發

5.若是既有的修改函數在被控端,就在控制端創建一個控制函數,並讓既有的修改函數調用這個新建的控制函數。

範例:

 下面是一段簡單程序,其中有兩個類:表示「訂單」的Order和表示「客戶」的Customer。Order引用了Customer,Customer並無引用Order:

class Order ...
Customer getCustomer(){
    return _customer;
}
void setCustomer (Customer arg){
    _customer = arg;
}
Cusotmer _customer;

首先,要爲Customer添加一個字段。因爲一個客戶能夠擁有多份訂單,因此這個新增字段應該是個集合。咱們不但願同一份訂單在同一個集合中出現一次以上,因此這裏適合使用set:

class Customer{
    private Set _orders = new HashSet();

如今,咱們須要決定由哪個類負責控制關聯關係。我比較喜歡讓但各種來操控,由於這樣就能夠將全部處理關聯關係的邏輯集中安置於一地。我將按下列步驟作出這一決定。

 1.若是二者都是引用對象,而其間的關聯是「一對多」關係,那麼就由「擁有單一引用」的那一方承擔「控制着」角色。以本例而言,若是一個客戶可擁有多份訂單,那麼就有Order類來控制關聯關係。

2.若是某個對象是組成另外一個對象的部件,那麼由後者負責控制關聯關係。

3.若是二者都是引用對象,而其間的關聯是「多對多」關係,那麼隨便其中哪一個對象來控制關聯關係,都無所謂。

本例之中,因爲Order負責控制關聯關係,因此我必須爲Customer添加一個輔助函數,讓Order能夠直接訪問_orders集合。Order的修改過函數將使用這個輔助函數對指針兩端對象進行同步通知。咱們將這個輔助函數命名爲friendOrders(),

表示這個函數只能在這種特殊狀況下使用。此外,若是Order和Customer位在同一個包內,咱們能夠將friendOrders()聲明爲保內可見,使其可見度降到最低。但若是這兩個類再也不同一個包內,咱們就只好把friendOrders() 聲明爲public了。

class Customer ...
    set friendOrders(){
        /**should only be used by Order when modifying the association*/
        return _orders;
    }

如今,咱們要改變修改函數,令它同時更新反向指針:

class Order ...
    void setCustomer(Sustomer arg){
        if(_customer != null){
            _customer.friendOrders().remove(this);
        }
        _customer = arg;
        if(_customer !=null){
            _customer.friendOrders().add(this);
        }
    }

類之間的關聯關係是各式各樣的,所以修改函數的代碼也會隨之有所差別。若是_customer的值不多是null,那麼能夠拿掉上述的第一個null檢查,但任然須要檢查傳入參數是否爲null。不過基本形式老是相同的:先讓對方刪除指向你的指針,再將你的指針指向一個新的對象,最後讓那個心對象把它的指針指向你。

若是你但願在Customer中也能修改鏈接,就讓它調用控制函數:

class Customer...
    void addOrder(Order arg){
        arg.setCustomer(this);
    }

若是一份訂單也能夠對應多個客戶,那麼你多面臨的就是一個「多對多」狀況,重構後的函數多是下面這樣:

class Order ...//controlling methods
    void addCustomer(Customer arg){
        arg.friendOrders().add(this);
        _customers.add(arg);
    }
    void removeCustomer(Customer arg){
        arg.friendOrders().remove(arg);
        _customers.remove(arg);
    }
    
 class Customer...
     void addOrder(Order arg){
         arg.addCustomer(this);
     }
     void removeOrder(Order arg){
         arg.removeCustomer(this);
     }
相關文章
相關標籤/搜索