稍微对重构有所了解的人应该都知道有一本书叫《重构:改善既有代码设计》,这本由Martin Fowler写的巨著开启了重构新时代!不过有点可惜的是,也许是因为作者写这本书的时间太早,早到当时C#还不知道在哪!所以里面原生的实例全都是用java写的,虽说C#和java比较类似,但毕竟还是有区别的,看起来终归不是很舒服,就连Martin Fowler本人在书中也说希望今后能有人写一本C#版以及其他各种语言版本的重构书籍!本人也真心希望.Net版本的重构资料越来越多,不然我们学.Net要写论文,一查资料大部分都是java的资料,非常蛋疼!下面这个例子呢也是本人准备写小论文时找到的,拿出来分享下,这个例子虽然简单,但是做完了还真让人感觉确实重构技术很有意思啊,要是能将这门技术灵活掌握,今后开发软件势必获益匪浅!
这个例子是一个基于.Net平台的winform程序,功能比较简单,就是用来根据人的身高、体重还有年龄计算一个人每天应该摄入多少卡路里值,其次还可以算出一个人的完美体重,附带也算出当前真实体重与完美体重的差距,各位如果看了,千万请把注意力集中在里面的重构过程上而不是程序本身!下面是窗体截图:
:
闲话不多说,我把重构的几个版本代码都整理了下:关键是计算卡路里那个按钮的事件处理代码中含有长方法坏味道,我的重构呢也是想解决这个味道
1 #region 第一版源代码 2 3 txtDistance.Text = ""; 4 txtIdeal.Text = ""; 5 txtRecommend.Text = ""; 6 7 double result; 8 if (!double.TryParse(txtWeight.Text.Trim(), out result)) 9 { 10 MessageBox.Show("请输入有效的体重值!"); 11 txtWeight.Select(); 12 return; 13 } 14 if (!double.TryParse(txtHeight.Text.Trim(), out result)) 15 { 16 MessageBox.Show("请输入有效的身高值!"); 17 txtHeight.Select(); 18 return; 19 } 20 if (!double.TryParse(txtAge.Text.Trim(), out result)) 21 { 22 MessageBox.Show("请输入合法的年龄值!"); 23 txtAge.Select(); 24 return; 25 } 26 if (!(Convert.ToDouble(txtHeight.Text.Trim()) >= 100)) 27 { 28 MessageBox.Show("您输入的身高超出计算范围,请重新输入!"); 29 txtHeight.Select(); 30 return; 31 } 32 33 34 35 if (radioButtonMale.Checked) 36 { 37 double weight = Convert.ToDouble(txtWeight.Text.Trim()); 38 double height = Convert.ToDouble(txtHeight.Text.Trim()); 39 double age = Convert.ToDouble(txtAge.Text.Trim()); 40 double calories = DailyCaloryRecommendForMale(weight,height,age); 41 txtRecommend.Text = Convert.ToString(calories); 42 txtIdeal.Text = IdealWeightForMale(height).ToString(); 43 } 44 else 45 { 46 double weight = Convert.ToDouble(txtWeight.Text.Trim()); 47 double height = Convert.ToDouble(txtHeight.Text.Trim()); 48 double age = Convert.ToDouble(txtAge.Text.Trim()); 49 double calories = DailyCaloryRecommendForFemale(weight,height,age); 50 txtRecommend.Text = Convert.ToString(calories); 51 txtIdeal.Text = IdealWeightForFemale(height).ToString(); 52 } 53 //包含四个方法DailyCaloryRecommendForMale、DailyCaloryRecommendForFemale、IdealWeightForMale、IdealWeightForFemale、DistanceFromIdealWeight 54 txtDistance.Text = DistanceFromIdealWeight(Convert.ToDouble(txtWeight.Text.Trim()), Convert.ToDouble(txtIdeal.Text.Trim())).ToString(); 55 56 #endregion
1 #region 第二版源代码 主要运用了Extract Method重构手法 2 3 ClearResult();//封装了第一版三个清空文本框内容的代码 4 if (!UserInputValid())//UserInputValid封装了对用户输入合法性验证的功能 5 { 6 return; 7 } 8 if (radioButtonMale.Checked) 9 { 10 double weight = Convert.ToDouble(txtWeight.Text.Trim()); 11 double height = Convert.ToDouble(txtHeight.Text.Trim()); 12 double age = Convert.ToDouble(txtAge.Text.Trim()); 13 double calories = DailyCaloryRecommendForMale(weight,height,age); 14 txtRecommend.Text = Convert.ToString(calories); 15 txtIdeal.Text = IdealWeightForMale(height).ToString(); 16 } 17 else 18 { 19 double weight = Convert.ToDouble(txtWeight.Text.Trim()); 20 double height = Convert.ToDouble(txtHeight.Text.Trim()); 21 double age = Convert.ToDouble(txtAge.Text.Trim()); 22 double calories = DailyCaloryRecommendForFemale(weight,height,age); 23 txtRecommend.Text = Convert.ToString(calories); 24 txtIdeal.Text = IdealWeightForFemale(height).ToString(); 25 } 26 txtDistance.Text = DistanceFromIdealWeight(Convert.ToDouble(txtWeight.Text.Trim()), Convert.ToDouble(txtIdeal.Text.Trim())).ToString(); 27 28 #endregion
1 #region 第三版源代码 运用了Extract Class、Move Method和Extract Method重构手法 2 3 ClearResult(); 4 if (!UserInputValid()) 5 { 6 return; 7 } 8 patient = new Patient(); 9 if (radioButtonMale.Checked) 10 { 11 patient.Gender = Gender.Male; 12 } 13 else 14 { 15 patient.Gender = Gender.Female; 16 } 17 patient.Height = Convert.ToDouble(txtHeight.Text.Trim()); 18 patient.Weight = Convert.ToDouble(txtWeight.Text.Trim()); 19 patient.Age = Convert.ToDouble(txtAge.Text.Trim()); 20 txtRecommend.Text = patient.DailyCaloryRecommend().ToString(); 21 txtIdeal.Text = patient.IdealWeight().ToString(); 22 txtDistance.Text = patient.DistanceFromIdealWeight().ToString(); 23 24 #endregion
1 #region 第四版源代码 运用了Extract Class和move method以及类的继承机制 2 3 ClearResult(); 4 if (!UserInputValid()) 5 { 6 return; 7 } 8 if (radioButtonMale.Checked) 9 { 10 patient = new MalePatient(); 11 } 12 else 13 { 14 patient = new FemalePatient(); 15 } 16 patient.Height = Convert.ToDouble(txtHeight.Text.Trim()); 17 patient.Weight = Convert.ToDouble(txtWeight.Text.Trim()); 18 patient.Age = Convert.ToDouble(txtAge.Text.Trim()); 19 20 txtRecommend.Text = patient.DailyCaloryRecommend().ToString(); 21 txtIdeal.Text = patient.IdealWeight().ToString(); 22 txtDistance.Text = patient.DistanceFromIdealWeight().ToString(); 23 24 #endregion
下面是经过重构提取的各种方法还有类
1 private void ClearResult() 2 { 3 txtDistance.Text = ""; 4 txtIdeal.Text = ""; 5 txtRecommend.Text = ""; 6 } 7 8 private bool UserInputValid() 9 { 10 double result; 11 if (!double.TryParse(txtWeight.Text.Trim(), out result)) 12 { 13 MessageBox.Show("请输入有效的体重值!"); 14 txtWeight.Select(); 15 return false; 16 } 17 if (!double.TryParse(txtHeight.Text.Trim(), out result)) 18 { 19 MessageBox.Show("请输入有效的身高值!"); 20 txtHeight.Select(); 21 return false; 22 } 23 if (!double.TryParse(txtAge.Text.Trim(), out result)) 24 { 25 MessageBox.Show("请输入合法的年龄值!"); 26 txtAge.Select(); 27 return false; 28 } 29 30 if (!(Convert.ToDouble(txtHeight.Text.Trim()) >= 100)) 31 { 32 MessageBox.Show("您输入的身高超出计算范围,请重新输入!"); 33 txtHeight.Select(); 34 return false; 35 } 36 return true; 37 } 38 39 private double DistanceFromIdealWeight(double weight, double idealWeight) 40 { 41 return weight - idealWeight; 42 } 43 44 private double DailyCaloryRecommendForMale(double weight, double height, double age) 45 { 46 return 66 + 6 * weight + 13 * height - 7 * age; 47 } 48 49 private double DailyCaloryRecommendForFemale(double weight, double height, double age) 50 { 51 return 655 + 4 * weight + 5 * height - 5 * age; 52 } 53 54 private double IdealWeightForMale(double height) 55 { 56 return 50 + (height - 100) * 2; 57 } 58 59 private double IdealWeightForFemale(double height) 60 { 61 return 45 + (height - 100) * 2; 62 }
1 public abstract class Patient 2 { 3 private string id; 4 private string firstName; 5 private string lastName; 6 private double height; 7 private double weight; 8 private double age; 9 10 //private Gender gender; 11 12 public string Id 13 { 14 get { return this.id; } 15 set { this.id = value; } 16 } 17 18 public string FirstName 19 { 20 get { return this.firstName; } 21 set { this.firstName = value; } 22 } 23 24 public string LastName 25 { 26 get { return this.lastName; } 27 set { this.lastName = value; } 28 } 29 30 public double Height 31 { 32 get { return this.height; } 33 set { this.height = value; } 34 } 35 36 public double Weight 37 { 38 get { return this.weight; } 39 set { this.weight = value; } 40 } 41 42 public double Age 43 { 44 get { return this.age; } 45 set { this.age = value; } 46 } 47 48 /* 49 public Gender Gender 50 { 51 get { return this.gender; } 52 set { this.gender = value; } 53 }*/ 54 55 56 public double DailyCaloryRecommendForMale() 57 { 58 return 66 + 6 * Weight + 13 * Height - 7 * Age; 59 } 60 61 public double DailyCaloryRecommendForFemale() 62 { 63 return 655 + 4 * Weight + 5 * Height - 5 * Age; 64 } 65 66 public double IdealWeightForMale() 67 { 68 return 50 + (Height - 100) * 2; 69 } 70 71 public double IdealWeightForFemale() 72 { 73 return 45 + (Height - 100) * 2; 74 } 75 76 public double DistanceFromIdealWeightForMale() 77 { 78 return Weight - IdealWeightForMale(); 79 } 80 81 public double DistanceFromIdealWeightForFemale() 82 { 83 return Weight - IdealWeightForFemale(); 84 } 85 86 public abstract double DistanceFromIdealWeight();//封装与具体性别相关的方法 87 88 89 public abstract double DailyCaloryRecommend();//封装与具体性别相关的方法 90 91 92 public abstract double IdealWeight();//封装与具体性别相关的方法 93 94 95 } 96 97 98 public class FemalePatient : Patient 99 { 100 public override double DailyCaloryRecommend() 101 { 102 return DailyCaloryRecommendForFemale(); 103 } 104 105 public override double IdealWeight() 106 { 107 return IdealWeightForFemale(); 108 } 109 110 public override double DistanceFromIdealWeight() 111 { 112 return DistanceFromIdealWeightForFemale(); 113 } 114 115 116 } 117 118 public class MalePatient : Patient 119 { 120 public override double DailyCaloryRecommend() 121 { 122 return DailyCaloryRecommendForMale(); 123 } 124 125 public override double IdealWeight() 126 { 127 return IdealWeightForMale(); 128 } 129 130 public override double DistanceFromIdealWeight() 131 { 132 return DistanceFromIdealWeightForMale(); 133 } 134 }
正是上述这些提取出来的方法和类以及方法的移动才实现了按钮事件处理方法的极大优化,这样的优化能够显著提高程序的可维护性!其实里面各个版本程序的演变都是通过一步步重构实现的,具体还有些细节我没有展示完全,不过大家感兴趣可以通过现有给的最终代码逆推!