代码重构——第一个实例

xiaoxiao2021-02-28  37

0 引言

随着“互联网+”的普及,互联网行业得到了快速蓬勃发展,使得现实生活中人们对互联网产品的需求逐渐增加,促使互联网产品功能越来越复杂,项目逐渐失去有效的管理。为满足项目功能上的需求,提高项目的扩展性和可维护性,必须在保证业务功能不变的情况下进行代码重构的开发行为。

重构意味着要改变代码,或者说是重新修改,或者重写代码。它并不是简单的重复工作,这样的重复工作也是没有多大意义的。他的意义就在于,在日常的开发中,通过理解业务需求,捋顺业务逻辑,把繁杂、冗余的代码尽量简化,减少模块之间的耦合。重构的目的是使软件更容易被理解和修改,并且重构不会改变软件可观察的行为,也就是说重构之后代码的功能和初始代码的功能完全一样,任何用户或者其它未参与重构行为的程序员,并不会意识到项目的更改。

1 实验内容

1.1 实验问题

本次实验是对《重构——改善既有代码的设计》一书中第一章节实例的模拟实现,实例是为影片出租店所用的租赁程序。该家影片租赁店具有三种不同类型的影片,包括:新片、儿童片和普通片。顾客到店选择多种影片租赁,该程序需实现以下功能:根据顾客选定的影片及顾客待租赁的天数,直接打印顾客账单,账单中包括:顾客租赁的每一个影片的费用、所有影片的总费用及当前用户的积分。

1.2 实验分析

根据该实例,设计出以下三个类:Movie、Customer和Rental。其中,Movie类是一个纯数据类,该类中包含:有影片类型的编号将三种不同的影片区分开来。再根据不同的影片类型调用Rental类,并记录下顾客租赁的租期。原始程序中,将计算价格和积分及输出打印价格积分全部在Customer类中实现。该类中包括顾客的姓名,顾客租赁影片的类型及租期,计算顾客租赁影片的价格及累计的积分。

最后,我们可以加入一个Main类,声明一个主函数,用来管理其余三个类。该程序则可以实现,店家输入顾客租赁的影片名、影片类型、租赁天数及顾客的姓名,程序输出打印当前顾客需缴纳的费用及累计积分。该程序UML类图及三个类函数之间的交互如下:

图1 初始代码类图

图2 初始代码中statement()函数交互

2 重构过程

2.1 重构背景

如果将该实例只是作为某一项目中的一小部分待实现的功能,我们再次观察这三个类,会发现存在“超类”的情况。在原始代码Customer类中实现了计算价格和输出打印两个功能,这不符合“一个类只能有一个引起该类变化的原因”的原则。

为方便商家使用,商家提出需要增加网页显示的功能,在html表单中显示顾客的消费情况。那么我们需要再次增加一个htmlprintf()函数,该函数需实现的功能与原statement()中的类似,我们很容易想到,直接复制statement()函数中的功能。但是,如果当影片的计费规则和分类规则不断地发生变化时,我们需要在两个函数中不停地更改规则,这就使得程序容易出现bug。

总之,该程序的扩展性和可维护性都不够好,为程序后期增加功能的方便,也为了其他程序员方便读懂该程序,我们需要对程序进行代码重构。

2.2 具体过程

2.2.1 关于影片价格计算

在Customer类中的statement()函数,实现的功能有:计算租赁影片的费用、顾客积分以及输出打印三个功能,很明显这不符合这相当于一个“超函数”。因此,我们增加一个计算租赁影片的费用的函数getTotalCharge(),在statement()函数中调用该函数。而计算价格函数,只涉及到影片租赁价格和租赁天数两个变量。我们在Rental类中,实现getCharge();而在Movie类中,我们利用Rental类传递的参数租赁天数,计算影片的价格。

由于不同类型的影片的租赁价格有所不同,最终,我们增加Price类、CHILDRENS_Price类、NEW_RELEASE_Price类和REGULAR_Price类。在Price类中加入计算价格的抽象函数abstract double getCharge(int daysRented),其中CHILDRENS_Price类、NEW_RELEASE_Price类和REGULAR_Price类继承Price类,并重写父类中的getCharge(int daysRented)函数,分别实现各自类型影片的租赁价格计算。

2.2.2 关于顾客积分计算

顾客积分的计算方式同不同类型影片租赁价格计算方式原理相同,我们将初始代码Customer类中的statement()函数中的积分功能提取出来,采用价格计算同样的方式去重构。最后,我们在Price类中增加int getFrequentRenterPoints(intdaysRented)函数,在其余三个子类中重写计算积分函数,实现不同类型影片计算顾客积分的功能。

2.2.3 整体完善

在重构的过程中,产生了一些不再使用的变量和接口,一些临时变量往往容易引发问题,它们会导致大量参数被传来传去,而其实完全没有这种必要。因此我们将一些不必要的变量和接口进行去除或者修改。

2.3 重构方法

2.3.1 Extract Method

当看到一个方法过长或者方法很难让人理解其意图的时候,这时候就可以用提取方法这种重构手法。在该实例中的statement()函数中,存在一个明显的switch逻辑结构,我们可以采用提取方法将其提取到独立函数中。

2.3.2 MoveMethod

明确函数所在类的位置是很重要的。这样可以避免类与其它类有太多耦合。也会让程序中类的内聚性变得更加牢固,让整个系统变得更加整洁。简单来说,如果在程序中,某个类的函数在使用的过程中,更多的是在和别的类进行交互,调用后者或者被后者调用,那么就要注意了,我们要去判断这个类是否真正适合它原来所在的类。

这套手法就是在该函数最常引用的新类中建立一个有着类似行为的新函数,让旧函数变成一个单纯的委托函数或者完全删掉。在该实例中,我们将statement()函数中计算价格的功能单独提取后,发现新的函数放错了位置,它与Customer类并无多大联系,我们将其搬移到Rental类中,并不会改变整个程序的功能。因此我们采用了搬移方法将计算租赁价格的函数搬移至Rental类中。

2.3.3 Strategy Pattern

策略模式是指对一系列的算法定义,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化。当多个类只区别在表现行为不同,可以用该模式,在运行时动态选择具体要执行的行为。需要在不同情况下使用不同的策略,或者策略还可能在未来用其它方式实现。在该实例中,将不同类型影片租赁价格的计算采用策略模式,以实现代码的重构。

3 重构效果展示与分析

3.1 重构前后程序结果

我设置了几组不同的测试用例,对重构前后的代码进行测试,得到了相同的结果,说明代码重构成功。

图3 程序运行结果展示

3.2 重构后程序的分析

运用策略模式后的主要类图如下

图4 策略模式类图

参考资料

[1](美)福勒.重构:改善既有代码的设计[M].人民邮电出版社, 2010-11-1:1-52

[2] https://www.aliyun.com/jiaocheng/531223.html

[3]https://blog.csdn.net/Nuan_Feng/article/details/70555952

[4]https://blog.csdn.net/u012401711/article/details/52463347

 源码https://download.csdn.net/download/qq_35616167/10468620

版权声明:本文为博主原创文章,转载请注明出处。https://blog.csdn.net/qq_35616167/article/details/80631430

 

转载请注明原文地址: https://www.6miu.com/read-2629162.html

最新回复(0)