Change Bidirectional Association to Unidirectional

Summary:两个类之间有双向关联,但其中一个类如今不再需要另一个类的特性。去除不必要的关联。

                                               Change Bidirectional Association to Unidirectional

动机:

  双向关联很有用,但你也必须为它付出代价,那就是维护双向连接、确保对象被正确创建和删除而增加的复杂度。而且,由于很多程序员并不习惯使用双向关联,它往往成为错误之源。

大量的双向连接也很容易造成“僵尸对象”:某个对象本来已经该死亡了,却任然保留在系统中,因为对它的引用还没有完全清除。

此外,双向关联也迫使两个类之间有了依赖:对其中任何一个类的修改,都可能引发另一个类的变化。如果这两个类位于不同的包,这种依赖就是包与包之间的相依。过多的跨包依赖会造成紧耦合系统,使得任何一点小改动都可能造成许多无法预知的后果。

只有在真正需要双向关联的时候,才应该使用它。如果发现双向关联不再有存在的价值,就应该去掉其中不必要的一条关联。

做法:

1.找出保存“你想去除的指针”的字段,检查它的每一个用户,判断是否可以去除改指针

à不但要检查直接访问点,也要检查调用这些直接访问点的函数。

à考虑有无可能不通过指针取得被引用对象。如果有可能,你就可以对取值函数使用Substitute Algorithm,从而让客户在没有指针的情况下也可以使用该取值函数

à对于使用该字段的所有函数,考虑将被引用对象作为参数传进去。

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

你可能感兴趣的:(Change Bidirectional Association to Unidirectional)