在上篇博客《代码重构(一):函数重构规则(Swift版)》中,详细的介绍了函数的重构规则,其中主要包括:Extract Method, Inline Method, Inline Temp, Replace Temp with Query, Introduce Explaining Variable, Split Temporary Variable, Remove Assignments to Parameters, Replace Method with Method Object等。关于上述这些函数重构的规则更为详细的信息请参考上一篇博客,在此就不做过多的赘述了。
今天这篇博客主要介绍一下类的重构。在我们写代码时,有些类是不规范的,需要重构。在对类进行重构时,也是有一些章法可寻的,本篇博客就结合着相关示例,对类的重构进行相关的介绍。当然在本篇博客中使用的实例,还是延续上一篇文章的风格,仍然采用Swift语言进行编写。当然,还是那句话,重构的思想和手法与设计模式类似,都与具体语言实现无关。触类旁通,关键还是思想和手法。为了精简博文的篇幅,相关的测试用例就不往上粘贴了。当然,在你实现时,测试用例是必不可少的,因为测试用例可以在你重构时及时发现因为重构而产生的错误。言归正传,进入今天博客的主题。
一、Move Method----方法迁移
关于Move Method,首先谈论一下为什么要进行方法的迁移。原因很简单,就是当类中的方法不适合放在当前类中时,就应该为该方法寻找合适下家。那么怎样才可以称作是当前方法不适合在当前类中呢?一个类中的函数与另一个类有很多的交互,函数非常依赖于某个类。如果一个类有太多行为,或者与另一个类有太多合作而形成高度耦合。此时就应该将该方法搬移到其高度依赖的类中。
在给方法搬家时需要做的就是在方法的新家中创建一个方法,实现要搬移的功能,如果新创建的函数需要旧类中的数据,那么就创建一个委托对象来解决这个问题。说白了就是在另一个类中创建一个相同的功能的新函数,将旧函数变成一个单纯的委托函数,或者将旧函数完全移除。搬移后,我们可以再使用函数的重构规则对新组的函数进行重构。下方就通过一个实例来直观的感受一下Move Method。
1.代码实例
在下方截图中有两个类,一个Book类,另一个是BookCustomer类。在Book类中有两个属性,一个是bookCode:表示书的种类(NEW_BOOK,OLD_BOOK, CHIDREN_BOOK), 另一个属性就是书名bookName。在BookCustomer中有3个字段,name表示用户的名称,isVip表示用户是否是会员,books表示该用户所购买的书的集合。BookCustomer类中的charge()方法用来根据books数组来计算图书的总价格,并返回总价格。如果是VIP, 就在总价格的基础上打7折,普通用户打8折。下方截图就是其具体实现。
2.使用Move Method进行重构
首先我们对上述两个类进行分析,观察需要重构的地方。首先第一眼看代码时,较长的charge()函数会让我们看起来些微的不舒服,因为它太长了。再仔细分析,其中的Switch语句中的业务逻辑用的全是Book类的东西,和当前BookCustomer类没用什么关联。但是这个Switch语句是当前charge()函数的核心,也就是BookCustomer严重依赖Book类的地方。以此分析下去,我们就清楚的指定,该Switch语句块放错了地方,它应该放在Book类中。所以我们应该将这块代码进行搬移。
重构方法就是在Book类中创建一个charge()函数,将Switch语句块放入新的charge()函数中。然后在原来的charge()函数使用Switch语句时调用新的charge()方法。下方代码段是使用Move Method重构后的结果。
3.使用函数重构
在使用Move Method重构后,我们看出在BookCustomer类中的charge()函数是可以使用Extract Method和Replace Temp With Qurey进行重构的。关于这两个函数重构的规则的具体细节请参见《代码重构(一):函数重构规则(Swift版)》中的介绍。下方截图是对BookCustomer类中的charge()函数进行重构后的结果,如下所示:
二、Move Field----搬移字段
上一部分是搬移方法,Move Field(搬移字段)与Move Method适用场景类似。当在一个类中的某一个字段,被另一个类的对象频繁使用时,我们就应该考虑将这个字段的位置进行更改了。Move Field与Move Method的思想和做法差不多,再次对其的示例就省略了。举一反三,你可以类比着Move Method来使用Move Field规则。具体实现方式在此就不做过多的赘述了。
三、Extract Class----提炼类
Extract Class和Extract Method类似,Extract Method提取的是方法,而Extract Class提取的是类。一个类如果过于复杂,做了好多的事情,违背了“单一职责”的原则,所以需要将其可以独立的模块进行拆分,当然有可能由一个类拆分出多个类。当然,对类的细化也是为了减少代码的重复性,以及提高代码的复用性,便于代码的维护。下方将会通过一个实例,对类进行提炼。
1.重构前的代码
下方是我们将要进行重构的代码段。在Person类中有三个字段,常量name表示该Employee的名字,officeAreaCode表示Employee所在办公部门的区域代码。然后就是Employee类的构造函数了。Employee类比较简单。
2.使用Extract Class对Employee重构
接下来要做的就是使用Extract Class对Employee进行重构。因为上述Employee类设计的不好,因为Employee类可以再分。显然可以将区域号和电话号提取成一个TelePhoneNubmer类,在Employee中调用TelePhoneNubmer类。这样一来TelePhoneNubmer类就可以重复利用了,而且层次结构更为清晰。下方代码段就是对上述代码进行重构后的结果。具体如下所示:
四、Inline Class----类的内联化
又到了“物极必反”的时候了。Extract Method与Inline Method职责相反,Extract Class当然也就职责相反的原则。那就是接下来要介绍的类的内联化:Inline Class。如果过度使用Extract Class原则的话,会使得某些类过于简单并且调用该简单的类的地方极少。也就是说一个类根本不能称为一个类,所以我们可以通过Inline Class将过度抽象出来的类放到其他类中。
关于Inline Class的示例在此就不做过多的赘述了,因为与Extract Class原则相反,将第三部分中的示例倒着过一遍即为类的内联化的工作方式。
五、Hide Delegate----隐藏“委托关系”
隐藏类之间的“委托关系”这一原则用起来是非常不错的,它可以简化类调用委托者的方式。简单的说就是讲委托调用的链,封装成相应的方法,使其隐藏掉具体的调用细节,从而简化了调用方式。下方会根据具体事例和测试用例来介绍一下Hide Delegate。
1.重构前的案例
在下方代码片段中有两个类,这两个类互为依赖关系。Department中有People,该People对应的就是经理人。还有一个字段就是chargeCode,对应的是部门代码。而People类中有name--名字字段,department--所属部门字段。在People对象中可以委托department对象来获取经理的名字。
获取People对象所在部门经理的名字的测试用例如下所示。在下方测试用例中创建了一个经理和一个员工,并为员工和经理绑定关系。zeluLi.department.manager.name就是委托department对象来调用经理的名字,这样调用未免太长,所以有必要使用Hide Delegate原则对其进行优化。
2.使用Hide Delegate进行重构
使用Hide Delegate进行重构的方式是比较简单的,就是在People中封装一个方法,在方法中返回经理的对象即可,这样就隐藏掉了委托关系。具体实现方式如下截图所示:
添加上上面的函数后的调用方式如下:
Remove Middle Man(移除中间人)原则与Hide Delegate相反,就是没有必要将委托人进行隐藏,所以就使用Remove Middle Man原则将上面我们封装的获取委托人的方法进行移除。关于Remove Middle Man的范例就不做过多的赘述了。
六、Introduce Foreign Method----引入外加函数
这一点在开发中用的还是比较多的,有时候你在不想或者不能修改原类的情况下想为该类添加新的方法。在这种情况下就会使用到Introduce Foreign Method。在Swift语言中,使用Introduce Foreign Method原则特别简单,也就是在不改变类的情况下对类进行扩展也是特别简单的。因为Swift语言以及OC中有延展的功能,所以非常对此非常好实现的。下方的代码段就是对MyTest类使用extension为其扩展一个method2方法,具体如下所示。
今天的博客就先到这儿,后期还会继续更新关于重构的博客。本篇博客中的代码分享地址为:https://github.com/lizelu/CodeRefactoring-Swift