代码重构(五):继承关系重构规则
陆陆续续的发表了多篇关于重构的文章了,还是那句话,重构是一个项目迭代开发中必 不可少的一个阶段。其实重构伴随着你的项目的整个阶段。在前几篇关于重构的文章中我们谈到了函数的重构、类的重构、数据的重构以及条件表达式的重构,那么 今天咱们就来聊聊继承关系的重构。当然还是延续前几篇博客的风格,我们在博客中的代码实例依然使用Swift语言来实现,当然还是那句话,使用什么语言无 所谓,关键是看重构的场景以及重构的思想。
“重构”不仅仅可以改善你既有的代码设计,还可以改变你组织代码的思路,使你的程序在设计之初就趋于合理化,利于程序的扩充。重构往往伴随着设计模 式的使用,在重构系列的博客结束后,我想系统的给大家分享一下关于设计模式的东西。当然是结合着各种实例。所谓一名Coder,重构和设计模式是必须涉猎 的部分,因为这两者可以让你写出更漂亮的代码,当然要想真正的掌握设计模式以及各种重构手法,还得结合不同的实例来进行实践。理论固然重要,但是要想将理 论的东西变成你自己的,还必须将理论付诸实践。废话少说,进入今天的主题。
一.Pull Up Field (字段上移) & Pull Down Field (字段下移)
字段上移与字段下移是相对的,也是我们之前所说的“凡事都有其两面性”,我们要辩证的去看待。我们只对Pull Up Field (字段上移) 这个规则做讨论,那么关于Pull Down Field (字段下移)我们不做过多的讨论,因为这两条规则是相反的,理解一条后,把这条规则反过来就是我们要理解的另一条规则。这样说起来,还是比“举一反三”要容易的多。
下方这个实例是为了解释“字段上移”所实现的一个Demo。当然Demo看上去不仅简单而且是有些夸张的,不过说明字段上移这个规则是完全足够了的。比如我们有一个父类为MySuperClass,我们有一个子类SubClass1,而在SubClass1中有一个字段父类是没有的。因为后期需求迭代或者需求变更,我们需要再创建一个SubClass1的兄弟类,就是下方的SubClass2。在SubClass2中与SubClass1中存在相同的字段,那就是var a = 0。
在上述情况下,就需要使用到我们的“字段上移”的规则。也就是说将子类中相同的字段移到父类中。在该实例中就是讲var a = 0 移到父类中。重构后的代码如下所示:
而将“Pull Down Field (字段下移)”正好与上面的情况相反。也就是父类中有某些字段,但是这些字段只有在少数子类中使用到,在这种情况下我们需要将这个字段移到相应的子类中即可。除了Pull Up Field (字段上移) & Pull Down Field (字段下移) 这两个规则外,Pull Up Method (将函数上移) 和 Pull Down Method (将函数下移)这两个规则与上述情况类似。就是将上面的字段改成函数,有时候不仅字段会出现上述情况,函数也会出现上述情况,需要我们进行移动。因为使用场景类似,再次就不做过多的赘述了。
二、Extract Subclass (提炼子类)
这种情况下用的还是比较多的,当类中的某些方法只有在特定的类的实例中才会使用到,此时我们就需要提炼出一个子类,将该方法放到相应的子类中。这样一来我们的每个类的职责更为单一,这也就是我们常说的“单一职责”。
在下方示例中,CustomerBook是一个图书消费者的类。其中customeCharge()方法是普通用户计算消费金额所需的方法,而vipCharge()方法是VIP用户调用的方法,在内部vipCharge()需要调用customeCharege()方法。但是对外部而言,vipCharge()方法只有VIP用户才会用到,在这种情况下我们就需要使用“Extract Subclass (提炼子类)”规则对VIP进行提炼。
具体做法是我们需要提炼出一个子类,也就是说将VIP用户作为普通用户的子类,然后将只有VIP用户才调用的方法放到我们的VIP子类中。这样一来层次更加明确,每个类的职责更为单一。上述示例重构后的结果如下所示。
与“提炼子类”规则相对应的是“Collapse Hierarchy (折叠继承关系)”。一句话来概括:就是当你的父类与子类差别不大时,我们就可以将子类与父类进行合并。将上面的示例翻转就是“Collapse Hierarchy (折叠继承关系)”规则的示例,再次就不做过多的赘述了。
三、Form Template Method (构造模板函数)
Form Template Method (构造模板函数)这一规则还是比较实用的。先说模板,“模板”其实就是框架,没有具体的实现细节,只有固定不变的步骤,可以说模板不关心具体的细节。举个栗子🌰,像前段时间比较火的“秘密花园”,那些没有颜色的线条就是模板,如果一些人获取的是同一本秘密花园,那么说明每个人所获取的模板是相同的。但是每个人对每块的区域所图的颜色又有差异,这就是实现细节的不同。
言归正传,当两个兄弟类中的两个函数中的实现步骤大致一直,但是具体细节不同。在这种情况下,我们就可以将大体的步骤提取成模板,放到父类中,而具体细节由各自的子类来实现。具体实现请看下方的类,在Subclass1和Subclass2中的calculate()方法中的大体步骤是相同的,就是对两个值相加,然后返回这两个值的和。但是具体细节不同,可以看出两个相加值的具体计算方式不同。
在上述情况下我们就可以使用“Form Template Method (构造模板函数)”规则将相同的计算流程进行提取,也就是构造我们的模板函数。将模板函数放到两个类的父类中,然后在相应的子类中只给出实现细节即可。下方代码段是重构后的代码,父类中多出的方法就是我们提取的模板函数,而子类中只给出相应的实现细节即可。
四、以委托取代继承(Replace Inheritance with Delegation)
有时候我们为一些类创建子类后,发现子类只使用了父类的部分方法,而且没有继承或者部分继承了父类的数据。在这种情况下我们就可以将这种继承关系修 改成委托的关系。具体做法就是修改这种继承关系,在原有子类中添加父类的对象字段,在子类中创建相应的方法,在方法中使用委托对象来调用原始父类中相应的 方法。
下方示例是我们假想出来的,但是说明该规则是绰绰有余了。我们假设SubClass01类中只会用到SuperClass01中的display()方法,而没有继承父类中的数据。在下方示例中是继承关系,在这种情况下我们需要将其转换成委托关系。
下方是我们重构后的代码,在下方代码中我们去除了之前的继承关系。并在子类中创建了一个之前父类的代理对象,并且创建了一个相应的方法,在该新建的方法中通过代理对象来调用相应的方法。具体如下所示。
上述规则与以继承取代委托(Replace Delegation with Inheritance)原则相对于,使用情况与上述相反,再次就不做过多的赘述了。
几天博客就先到这儿,内容比较简单,但是还是比较重要的。