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字段的全部读取点。就能够着手处理对此字段赋值的函数了。很简单,只要把这些赋值动做所有移除,再把字段一并删除就好了。因为已经没有任何代码须要这个字段。因此删掉它并不会带来任何影响

相关文章
相关标签/搜索