您的位置:首页 > 编程语言

《重构改善既有代码的设计》之重构列表--重新组织数据(三)

2012-12-18 11:02 351 查看

八、Change Bidirectional Association to Unidirectional(将双向关联改为单向关联)

两个类之间有双向关联,但其中一个类如今不再需要另一个类的特性。 去除不必要的关联。 动机 双向关联很有用,但你也必须为它付出代价,那就是维护双向连接、确保对象被正确创建和删除而增加的复杂度。而且,由于很多程序员并不习惯使用双向连接,它往往成为错误之源。 大量的双向连接也很容易造成“僵尸对象”:某个对象本来已经死亡了,却仍然保留在系统中,因为对它的引用还没有完全清除。 此外,双向关联一迫使两个类之间有了依赖:对其中一个类的任何修改,都可能引发另一个类的变化。如果这两个类位于不同的包,这种依赖就是包与包之间的相依。过多的跨包依赖会造就紧耦合系统,使得任何一点小小的改动都可能造成许多无法预知的后果。 只有在真正需要双向关联的时候,才应该用它。如果发现双向关联不再有存在价值,就应该去掉其中不必要的一条关联。 做法 1、找出保存“你想去除的指针”的字段,检查它的每一个用户,判断是否可以去除该指针。 ? 不但要检查直接访问点,也要检查调用这些直接访问点的函数。 ? 考虑有无可能不通过指针取得被引用对象。如果有可能,你就可以对取值函数使用Substitute Algorithm ,从而让客户在没有指针的情况下有可以使用该取值函数。 ? 对于使用该字段的所有函数,考虑将被引用对象作为参数传进去。 2、如果客户使用了取值函数,先运用Self Encapsulate Field 将待删除字段自我封装起来,然后使用Substitute Algorithm对付取值函数,令它不再使用该字段。然后编译、测试。 3、如果客户并未使用取值函数,那就直接修改待删除字段的所有被引用点:改以其他途径获得该字段所保存的对象。每次修改后,编译并测试。 4、如果已经没有任何函数使用待删除字段,移除所有对该字段的更新逻辑,然后移除该字段。 ? 如果有许多地方对此字段赋值,先运行Self Encapsulate Field使这些地点改用同一个设值函数。编译、测试。而后将这个设值函数的本体清空。再编译、测试。如果这些都可行,就可以将该字段和其设值函数,连同对设值函数的所有调用,全部移除。 5、再编译、测试。

九、Replace Magic Number with Symbolic Constant(以字面常量取代魔法数)

你有一个字面数值,带有特别含义。 创造一个常量,根据其意义为它命名,并将上述的字面数值替换为这个常量。 Double potentialEnergy(double mass ,double height){ Return mass * 9.81 * height; } 转换后: Static final double GRAVITATIONAL_CONSTANT = 9.81; Double potentialEnergy(double mass ,double height){ Return mass * GRAVITATIONAL_CONSTANT * height; } 动机 在计算科学中,魔法数(Magic Number)是历史最悠久的不良现象之一。所谓魔法数是指拥有特殊意义,却又不能明确表现出这种意义的数字。如果你需要在不同地点引用同一个逻辑数,魔法数会让你烦恼不已,因为一旦这些数发生变化,你就必须在程序中找到所有魔法数,并将它们全部修改一遍,这简直就是一场噩梦。就是你不需要修改,要准确指出每个魔法数的用途,也会让你破费脑筋。 许多语言允许你使用常量。常量不会造成任何性能开销,却可以大大提高代码的可读性。 进行本重构之前,你应该先寻找其他替换方案。你应该观察魔法数如果被使用,而后你往往会发现一种更好的使用方式。如果这个魔法数是个类型码,请考虑使用Replace Type Code with Class;如果这个魔法数代表一个数组的长度,请在遍历的时候使用Array.length()。 做法 1、声明一个常量,令其值为原本的魔法数值。 2、找出这个魔法数的所有引用点。 3、检查是否可以使用这个新声明的常量来替换该魔法数。如果可以,便以此常量替换之。 4、编译。 5、所有魔法数都被替换完毕后,编译并测试。此时整个程序应该运转正常,就像没有做任何修改一样。 ? 有个不错的测试办法:检查现在程序是否可以被你轻松的修改常量值(这可能意味着某些预期结果将有所改变,以配合这一新值。实际工作中并非总是可以进行这样的测试)。如果可行,这就是一个不错的手法。

十、Encapsulate Field(封装字段)

你的类中存在一个public字段。 将它声明为private,并提供相应的访问函数。 Public String _name; 转换后: Private String _name; Public String getName(){return _name;} Pubic void setName(String arg){_name = arg;} 动机 面向对象的首要原则之一就是封装,或者称为“数据隐藏”。按此原则,你绝不应该将数据声明为public,否则其他对象就有可能访问甚至修改这项数据,而拥有该数据的对象却毫无察觉。于是,数据和行为就被分开了----这可不是一件好事。 数据声明为public被看做是一种不好的做法,因为这样会降低程序的模块化程度。数据和使用该数据的行为如果集中在一起,一旦情况发生变化,代码的修改就会比较简单,因为需要修改的代码都集中于同一个地方,而不是星罗棋布地散落在整个程序中。 Encapsulate Field是封装过程的第一步。通过这项重构手法,你可以将数据隐藏起来,并提供相应的访问函数。但它毕竟只是第一步。如果一个类除了访问函数外不能提供其他行为,它终究只是一个哑巴类。这样的类并不能享受对象技术带来的好处。而你知道,浪费任何一个对象都是很不好的。实施Encapsulate Field之后,我会尝试找到用到新建访问函数的代码,看看是否可以通过简单的Move Method轻快地将它们移到新对象去。 做法 1、为public字段提供取值、设值函数。 2、找到这个类以外使用该字段的所有地点。如果客户指示读取该字段,就把引用替换为对取值函数的调用;如果客户修改了该字段值,就将此引用点替换为对设值函数的调用。 ? 如果这个对象是一个对象,而客户只不过是调用该对象的某个函数,那么无论该函数是否改变对象状态,都只能算是读取该字段。只有当客户为该字段赋值时,才能将其替换为设值函数。 3、每次修改后,编译并测试。 4、将字段的所有用户修改完毕后,把字段声明为private。 5、编译、测试。

十一、Replace Recode with Data Class(以数据类取代记录)

你需要面对传统编程环境的记录结果。 为该记录创建一个“哑”数据对象。 动机 记录型结构是许多编程环境的共同性质。有一些理由使它们被带进面向对象程序之中:你可能面对的是一个遗留程序,也可能需要通过一个传统API来与记录结构交流,或是处理从数据库读出的记录。这些时候你就有必要创建一个接口类,以便日后将某些字段和函数搬移到这个类之中。一个不太常见但非常令人注目的情况是:数组中的每个位置上的元素都有特定含义,这种情况下应该使用Replace Array with Object。 做法 1、新建一个类,表示这个记录。 2、对于记录中的每一项数据,在新建的类中建立对象的一个private字段,并提供相应的取值、设值函数。 现在,你拥有了一个“哑”数据对。这个对象现在还没有任何有用的行为,但是更进一步的重构会解决这个问题。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: