九、重构6:使用“多态”取代条件表达式
经过前面八篇文章,五次的重构,对于这个充电宝计费项目的重构基本上已经完成。今天是这一系列的最后一篇文章,我们来讲讲如何对条件表达式进行重构,对于条件表达式的重构比较的一种重构方式就是利用类的多态性进行重构。接下来我们就要使用该规则对PowerBank类中的GetAmount()与GetFrequentRenterPoints()函数进行重构。
我们对PowerBank类中的GetAmount()方法中的Switch-Case结构观察时,我们发现,此处的代码完全可以使用类的多态来替代。具体实现方法就是将不同的价格计算方式提取到我们新创建的价格类中,每个地段的充电宝都有自己的价格类,而这些价格类都实现同一个接口,这样一来在PowerBank类中就可以使用多类来获取总金额与积分了。
1.首先我们来创建一个价格接口。将光标停在PowerBank类名上,单击鼠标右键,在弹出菜单中选择“快速操作和重构”,再弹出的快捷菜单中选择“提取接口”。如下图。
2.如上图,在“提取接口”对话框中,填写接口名称,接口文件名称,在“选择构成接口的公共成员”列表框中,勾选相应的方法名称,使用鼠标点击“确定”按钮,Visual Studio 2019会自动生成一个接口文件。我们对自动生成的接口文件进行修改。具体代码如下:
namespace LeasePowerBank { public interface IPrice { int GetPriceCode(); decimal GetAmount(int RentedTime); int GetFrequentRenterPoints(int RentedTime); } }
3.在“解决方案资源管理器”中,选中“LeasePowerBank”项目,然后单击鼠标右键,在弹出的快捷菜单中选择“添加—》类”。如下图。
4.在“添加新项”对话框中,选择类,在名称中输入“LowTraffic”,然后点击“添加”按钮。如下图。
5.在Visual Studio 2019的代码编辑器中,在LowTraffic类继承IPrice接口。在光标停在接口IPrice上,然后单击鼠标右键,在弹出的快捷菜单中选择“快捷操作和重构—》实现接口”。如下图。
6.Visual Studio 2019会在LowTraffic类中自动添加实现接口的相应代码,我们只需要进行修改就可。具体代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace LeasePowerBank { public class LowTraffic : IPrice { public decimal GetAmount(int RentedTime) { decimal amount = RentedTime; if (RentedTime > 12) { amount = 12; } return amount; } public int GetFrequentRenterPoints(int RentedTime) { decimal amount = GetAmount(RentedTime); int frequentRenterPoints = (int)Math.Ceiling(amount); return frequentRenterPoints; } public int GetPriceCode() { return PowerBank.LowTraffic; } } }
7. 重复上面的第3步到第6步,我们分别来实现MiddleTraffic与HighTraffic类。这两个类的代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace LeasePowerBank { public class MiddleTraffic:IPrice { public decimal GetAmount(int RentedTime) { decimal amount = RentedTime; amount = RentedTime * 3; if (RentedTime > 8) { amount = 24; } return amount; } public int GetFrequentRenterPoints(int RentedTime) { decimal amount = GetAmount(RentedTime); int frequentRenterPoints = (int)Math.Ceiling(amount); return frequentRenterPoints; } public int GetPriceCode() { return PowerBank.MiddleTraffic; } } } using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace LeasePowerBank { public class HighTraffic:IPrice { public decimal GetAmount(int RentedTime) { decimal amount = RentedTime; amount = RentedTime * 5; if (RentedTime > 10) { amount = 50; } return amount; } public int GetFrequentRenterPoints(int RentedTime) { decimal amount = GetAmount(RentedTime); int frequentRenterPoints = (int)Math.Ceiling(amount); return frequentRenterPoints; } public int GetPriceCode() { return PowerBank.HighTraffic; } } }
8. 在添加了上面的代码之一,我们的PowerBank也要进行相应的修改,来利用多类来实现不同地段充电宝的计费计算与积分计算。在PowerBank中添加一个IPrice声明的对象,我们会根据不同的priceCode来给price变量分配不同的对象。金额计算时,只要在GetAmount()中调用price的GetAmount()方法。积分计算类似。具体代码如下。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace LeasePowerBank { /// <summary> /// 充电宝类 /// </summary> public class PowerBank { //地段人流量种类 public static int LowTraffic = 0;//低人流量地段 public static int MiddleTraffic = 1;//中人流量地段 public static int HighTraffic = 2; //高人流量地段 public int PriceCode; //价格代码 public string Title;//充电宝名称 public PowerBank(string title, int priceCode) { SetPriceCode(priceCode); Title = title; } IPrice price = null; private void SetPriceCode(int priceCode) { PriceCode = priceCode; switch (priceCode) { case 0: price = new LowTraffic(); break; case 1: price = new MiddleTraffic(); break; case 2: price = new HighTraffic(); break; default: break; } } /// <summary> /// 根据消费金额,充电宝所处地段,进行积分计算 /// </summary> /// <param name="RentedTime">租赁时间</param> /// <returns></returns> public int GetFrequentRenterPoints(int RentedTime) { return price.GetFrequentRenterPoints(RentedTime); } /// <summary> /// 根据充电宝订单,计算总金额 /// </summary> /// <param name="RentedTime">租赁时间</param> /// <returns></returns> public decimal GetAmount(int RentedTime) { return price.GetAmount(RentedTime); } } }
9. 我们的测试用例依然不变。在每次重构后我们都需要调用上述的测试用例来检查重构是否产生了副作用。现在我们的类间的依赖关系没怎么发生变化,只是相应类中的方法有些变化。在Visual Studio 2019的菜单栏上找到“测试-->运行所有测试”菜单项。或者在“测试资源管理器中”选择 “在视图中运行所有测试”按钮, 运行测试用例。监测重构的结果是否正确。如下图。
经过上面的重构,我们的这个项目也差不多了。重构就是这样,一步步的来,不要着急,每重构一次总是要向着好的方向发展。如果你从一始就是项目代码直接重构到重构6的代码,似乎有些困难。经过上面这些重构1至重构6这一步一步的进行重构,实际上也是挺简单的。