重构——一个小例子
菜鸟区域,老鸟绕路!
原代码,这是一个可以借阅影片的小程序,你可以想象成某个大型系统,我想代码应该都能很容易看懂:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web; namespace Reconsitution
{
public class Movie
{
/// <summary>
/// 常规的
/// </summary>
public const int Regular = 0;
/// <summary>
/// 新发布
/// </summary>
public const int NewRelease = 1;
/// <summary>
/// 儿童版
/// </summary>
public const int Childrens = 2; public string Title { get; private set; }
public int PriceCode { get; set; } public Movie(string title, int priceCode)
{
Title = title;
PriceCode = priceCode;
}
} public class Rental
{
public Movie @Movie { get; private set; }
public int DaysRented { get; private set; } public Rental(Movie movie, int daysRented)
{
@Movie = movie;
DaysRented = daysRented;
}
} public class Customer
{
public string Name { get; private set; } public List<Rental> Rentals
{
get { return _rentals; }
}
private readonly List<Rental> _rentals = new List<Rental>(); public Customer(string name)
{
Name = name;
} public void AddRental(Rental rental)
{
_rentals.Add(rental);
} public string Statement()
{
double totalAmount = 0;
int frequentRenterPoints = 0;
string result = "Rental Record for " + Name + "\n"; foreach (Rental rental in _rentals)
{
double thisAmount = 0; switch (rental.Movie.PriceCode)
{
case Movie.Regular:
thisAmount += 2;
if (rental.DaysRented > 2)
thisAmount += (rental.DaysRented - 2)*1.5;
break;
case Movie.NewRelease:
thisAmount += rental.DaysRented*3;
break;
case Movie.Childrens:
thisAmount += 1.5;
if (rental.DaysRented > 3)
thisAmount += (rental.DaysRented - 3) * 1.5;
break;
} frequentRenterPoints ++;
if ((rental.Movie.PriceCode == Movie.NewRelease) && rental.DaysRented > 1)
frequentRenterPoints ++; result += "\t" + rental.Movie.Title + "\t" + thisAmount + "\n";
totalAmount += thisAmount;
} result += "Amount owed is " + totalAmount + "\n";
result += "You earned " + frequentRenterPoints + " frequent renter points";
return result;
}
}
}
Customer customer = new Customer("Dennis Ding");
customer.AddRental(new Rental(new Movie("Water World", Movie.Childrens), 5));
customer.AddRental(new Rental(new Movie("Roman Holiday", Movie.Regular), 3));
customer.AddRental(new Rental(new Movie("The First Blood", Movie.NewRelease), 6));
TextBox1.Text = customer.Statement();
一、拆解主体方法
简要预览代码,我们很直观的发现 Customer 中的方法 Statement 过长,不便于阅读,于是我们首先便考虑解体方法 Statement,我们可以这样考虑,为什么要重构,重构再于便于后期维护和功能拓展,对于Statement方法,生成一个文字版的影片借阅结果单,如果我们版本更新,要生成一个Html的结果单,或者直接返回数据Json,形成接口供第三方调用呢,我们不得不重新写一个叫HtmlStatement 或者 JsonStatement的方法,因为我们把细节到计算的整个生成逻辑多放在了这一个方法中了!这是一种很不合理的设计!
public string Statement()
{
double totalAmount = 0;
int frequentRenterPoints = 0;
string result = "Rental Record for " + Name + "\n"; foreach (Rental rental in _rentals)
{
var thisAmount = AmountFor(rental); frequentRenterPoints ++;
if ((rental.Movie.PriceCode == Movie.NewRelease) && rental.DaysRented > 1)
frequentRenterPoints ++; result += "\t" + rental.Movie.Title + "\t" + thisAmount + "\n";
totalAmount += thisAmount;
} result += "Amount owed is " + totalAmount + "\n";
result += "You earned " + frequentRenterPoints + " frequent renter points";
return result;
} private static double AmountFor(Rental rental)
{
double thisAmount = 0; switch (rental.Movie.PriceCode)
{
case Movie.Regular:
thisAmount += 2;
if (rental.DaysRented > 2)
thisAmount += (rental.DaysRented - 2)*1.5;
break;
case Movie.NewRelease:
thisAmount += rental.DaysRented*3;
break;
case Movie.Childrens:
thisAmount += 1.5;
if (rental.DaysRented > 3)
thisAmount += (rental.DaysRented - 3)*1.5;
break;
}
return thisAmount;
}
这时候,我们发现,方法 AmountFor 并没有使用到和 Customer 相关的数据,但却放在 Customer 中,于是我们先考虑吧方法移到 Rental 中,
public class Rental
{
public Movie Movie { get; private set; }
public int DaysRented { get; private set; } public Rental(Movie movie, int daysRented)
{
Movie = movie;
DaysRented = daysRented;
} public double AmountFor()
{
double thisAmount = 0; switch (this.Movie.PriceCode)
{
case Movie.Regular:
thisAmount += 2;
if (DaysRented > 2)
thisAmount += (DaysRented - 2) * 1.5;
break;
case Movie.NewRelease:
thisAmount += DaysRented * 3;
break;
case Movie.Childrens:
thisAmount += 1.5;
if (DaysRented > 3)
thisAmount += (DaysRented - 3) * 1.5;
break;
}
return thisAmount;
}
}
原 Statement 方法变成 :
public string Statement()
{
double totalAmount = 0;
int frequentRenterPoints = 0;
string result = "Rental Record for " + Name + "\n"; foreach (Rental rental in _rentals)
{
var thisAmount = rental.AmountFor(); frequentRenterPoints ++;
if ((rental.Movie.PriceCode == Movie.NewRelease) && rental.DaysRented > 1)
frequentRenterPoints ++; result += "\t" + rental.Movie.Title + "\t" + thisAmount + "\n";
totalAmount += thisAmount;
} result += "Amount owed is " + totalAmount + "\n";
result += "You earned " + frequentRenterPoints + " frequent renter points";
return result;
}
(为了让方法符合实际功能,我们可以把方法 AmountFor 命名为 GetCharge)
在方法Statement中,此时我们看到这样的代码
var thisAmount = rental.GetCharge(); //……被省略的代码
result += "\t" + rental.Movie.Title + "\t" + thisAmount + "\n";
totalAmount += thisAmount;
由于 rental.GetCharge() 被使用两次,为了避免二次计算,代码中使用了一个临时变量 thisAmount,于是乎就产生了一个《重构与性能》的话题,这是一个很大的话题,所以我暂时也没深入学习,在重构中,我们可以去掉这样的临时变量(一两个不可怕,但是在一个项目中过度的临时变量使得代码无比混乱),实际上我们去掉这样的临时变量,可以尝试从 Rental 类中寻求优化方法。至于《重构与性能》后面介绍!
代码如下:
//……被省略的代码
result += "\t" + rental.Movie.Title + "\t" + rental.GetCharge() + "\n";
totalAmount += rental.GetCharge();
再往后,Statement方法中,有这样的代码:
frequentRenterPoints ++;
if ((rental.Movie.PriceCode == Movie.NewRelease) && rental.DaysRented > 1)
frequentRenterPoints ++;
frequentRenterPoints 是一个在方法中定义的临时变量(阿飘),这小部分代码的作用也不过是计算积分(现在的各种商店都有积分),这两句代码只是积分的计算方式,和实际生成逻辑没有关联,于是我们考虑把它拆出来。
public string Statement()
{
double totalAmount = 0;
int frequentRenterPoints = 0;
string result = "Rental Record for " + Name + "\n"; foreach (Rental rental in _rentals)
{
frequentRenterPoints += GetFrequentRenterPoints(rental); result += "\t" + rental.Movie.Title + "\t" + rental.GetCharge() + "\n";
totalAmount += rental.GetCharge();
} result += "Amount owed is " + totalAmount + "\n";
result += "You earned " + frequentRenterPoints + " frequent renter points";
return result;
} private int GetFrequentRenterPoints(Rental rental)
{
if ((rental.Movie.PriceCode == Movie.NewRelease) && rental.DaysRented > 1)
return 2;
return 1;
}
接下来,我们接着处理“阿飘”的问题,对于临时变量,能少用则少用,这是某某牛人给出的重构名言一则。
代码中 totalAmount 的计算可以单独封装为:
private double GetTotalAmount()
{
return _rentals.Sum(rental => rental.GetCharge());
}
到此时,Statement方法已经比较简洁了,我们Copy再稍加修改就可以提供HtmlStatement和JsonStatement了,因为方法里面的计算逻辑已经被我们提取出来!
二、拆分后的方法调整
下一步,对于刚刚拆解的方法GetCharge,主体为一个switch,而switch却依赖对象 Movie的一个成员PriceCode,既然这样,为什么不把计算方式迁移到Movie类呢:
public double GetCharge()
{
double thisAmount = 0; switch (this.Movie.PriceCode)
{
case Movie.Regular:
thisAmount += 2;
if (DaysRented > 2)
thisAmount += (DaysRented - 2) * 1.5;
break;
case Movie.NewRelease:
thisAmount += DaysRented * 3;
break;
case Movie.Childrens:
thisAmount += 1.5;
if (DaysRented > 3)
thisAmount += (DaysRented - 3) * 1.5;
break;
}
return thisAmount;
}
于是我们尝试在Movie中新建:
public double GetCharge(int daysRented)
{
double thisAmount = 0; switch (PriceCode)
{
case Movie.Regular:
thisAmount += 2;
if (daysRented > 2)
thisAmount += (daysRented - 2) * 1.5;
break;
case Movie.NewRelease:
thisAmount += daysRented * 3;
break;
case Movie.Childrens:
thisAmount += 1.5;
if (daysRented > 3)
thisAmount += (daysRented - 3) * 1.5;
break;
}
return thisAmount;
}
原方法则可用:
public double GetCharge(int daysRented)
{
return Movie.GetCharge(DaysRented);
}
然后看Customer中方法:
private int GetFrequentRenterPoints(Rental rental)
{
if ((rental.Movie.PriceCode == Movie.NewRelease) && rental.DaysRented > 1)
return 2;
return 1;
}
我们可以迁移到Rental中:
public int GetFrequentRenterPoints()
{
if ((Movie.PriceCode == Movie.NewRelease) && DaysRented > 1)
return 2;
return 1;
}
接着发现和上面同样使用了Movie的成员,尝试移动到Movie中:
public int GetFrequentRenterPoints(int daysRented)
{
if ((PriceCode == Movie.NewRelease) && daysRented > 1)
return 2;
return 1;
}
Rental中可以这样:
public int GetFrequentRenterPoints()
{
return Movie.GetFrequentRenterPoints(DaysRented);
}
和之前的GetTotalAmount方法一样,我们也可以有个GetTotalFrequentRenterPoints:
private double GetTotalFrequentRenterPoints()
{
return _rentals.Sum(rental => rental.GetFrequentRenterPoints());
}
于是Statement就表示为:
public string Statement()
{
string result = "Rental Record for " + Name + "\n"; foreach (Rental rental in _rentals)
{
result += "\t" + rental.Movie.Title + "\t" + rental.GetCharge() + "\n";
} result += "Amount owed is " + GetTotalAmount() + "\n";
result += "You earned " + GetTotalFrequentRenterPoints() + " frequent renter points";
return result;
}
三、大的变化——继承
例子中,我们的Movie类分成三个类型
/// <summary>
/// 常规的
/// </summary>
public const int Regular = 0;
/// <summary>
/// 新发布
/// </summary>
public const int NewRelease = 1;
/// <summary>
/// 儿童版
/// </summary>
public const int Childrens = 2;
从而出现了方法 GetCharge 中计算的条件判断,当我们做同一件事使用不同的方式时候,我们想到了继承!对不同的电影类型,有不同的价格,我们认为价格是一个基类,每种电影的价格为子类,最终代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web; namespace Reconsitution
{
public class Movie
{
#region 常量电影分类 /// <summary>
/// 常规的
/// </summary>
public const int Regular = 0;
/// <summary>
/// 新发布
/// </summary>
public const int NewRelease = 1;
/// <summary>
/// 儿童版
/// </summary>
public const int Childrens = 2; #endregion public string Title { get; private set; } public int PriceCode
{
get
{
return _price.GetPriceCode();
}
private set
{
switch (value)
{
case Regular:
_price = new RegularPrice();
break;
case Childrens:
_price = new ChidrensPrice();
break;
case NewRelease:
_price = new NewReleasePrice();
break;
default:
throw new Exception("Incorrect Price Code!");
}
}
}
private Price _price ; public Movie(string title, int priceCode)
{
Title = title;
PriceCode = priceCode;
} public double GetCharge(int daysRented)
{
return _price.GetCharge(daysRented);
} public int GetFrequentRenterPoints(int daysRented)
{
return _price.GetFrequentRenterPoints(daysRented);
}
} #region 价格类Price public abstract class Price
{
public abstract int GetPriceCode();
public abstract double GetCharge(int daysRented);
public abstract int GetFrequentRenterPoints(int daysRented);
} public class ChidrensPrice : Price
{
public override int GetPriceCode()
{
return Movie.Childrens;
}
public override double GetCharge(int daysRented)
{
double thisAmount = 1.5;
if (daysRented > 3)
thisAmount += (daysRented - 3) * 1.5;
return thisAmount;
}
public override int GetFrequentRenterPoints(int daysRented)
{
return 1;
}
} public class NewReleasePrice : Price
{
public override int GetPriceCode()
{
return Movie.NewRelease;
}
public override double GetCharge(int daysRented)
{
double thisAmount = daysRented * 3;
return thisAmount;
}
public override int GetFrequentRenterPoints(int daysRented)
{
return (daysRented > 1) ? 2 : 1;
}
} public class RegularPrice : Price
{
public override int GetPriceCode()
{
return Movie.Regular;
}
public override double GetCharge(int daysRented)
{
double thisAmount = 2;
if (daysRented > 2)
thisAmount += (daysRented - 2) * 1.5;
return thisAmount;
}
public override int GetFrequentRenterPoints(int daysRented)
{
return 1;
}
} #endregion public class Rental
{
public Movie Movie { get; private set; }
public int DaysRented { get; private set; } public Rental(Movie movie, int daysRented)
{
Movie = movie;
DaysRented = daysRented;
} public double GetCharge()
{
return Movie.GetCharge(DaysRented);
} public int GetFrequentRenterPoints()
{
return Movie.GetFrequentRenterPoints(DaysRented);
}
} public class Customer
{
public string Name { get; private set; } public List<Rental> Rentals
{
get { return _rentals; }
}
private readonly List<Rental> _rentals = new List<Rental>(); public Customer(string name)
{
Name = name;
} public void AddRental(Rental rental)
{
_rentals.Add(rental);
} private double GetTotalAmount()
{
return _rentals.Sum(rental => rental.GetCharge());
}
private double GetTotalFrequentRenterPoints()
{
return _rentals.Sum(rental => rental.GetFrequentRenterPoints());
} public string Statement()
{
string result = "Rental Record for " + Name + "\n"; foreach (Rental rental in _rentals)
{
result += "\t" + rental.Movie.Title + "\t" + rental.GetCharge() + "\n";
} result += "Amount owed is " + GetTotalAmount() + "\n";
result += "You earned " + GetTotalFrequentRenterPoints() + " frequent renter points";
return result;
} }
}
可能有人会问了, 为什么要这样做,是不是没有必要,我想你可以这样设想:正如之前提及的,对于同一一件事,如果使用的不同的方式去解决,就应该尝试通过继承分离出来,好处在于,如果我有新的电影分类出现,将只需要再继承一个Movie产生新的子类,就能解决问题,或者某类型电影价格、积分计算方式变动,我们也只用更改相应子类的计算方式,不影响整体方式!
重构——一个小例子的更多相关文章
- java连接mysql的一个小例子
想要用java 连接数据库,需要在classpath中加上jdbc的jar包路径 在eclipse中,Project的properties里面的java build path里面添加引用 连接成功的一 ...
- java操作xml的一个小例子
最近两天公司事比较多,这两天自己主要跟xml打交道,今天更一下用java操作xml的一个小例子. 原来自己操作xml一直用这个包:xstream-1.4.2.jar.然后用注解的方式,很方便,自己只要 ...
- MVVM模式的一个小例子
使用SilverLight.WPF也有很长时间了,但是知道Binding.Command的基本用法,对于原理性的东西,一直没有深究.如果让我自己建一个MVVM模式的项目,感觉还是无从下手,最近写了一个 ...
- 使用Trinity拼接以及分析差异表达一个小例子
使用Trinity拼接以及分析差异表达一个小例子 2017-06-12 09:42:47 293 0 0 Trinity 将测序数据分为许多独立的de Brujin grap ...
- 从一个小例子认识SQL游标
1 什么是游标: 关系数据库中的操作会对整个行集起作用. 例如,由 SELECT 语句返回的行集包括满足该语句的 WHERE 子句中条件的所有行. 这种由语句返回的完整行集称为结果集. 应用程序 ...
- 关于SVN配置文件的一个小例子
1 背景假设 厦门央瞬公司是一家电子元器件设备供应商,其中有个ARM部门,专门负责ARM芯片的方案设计.销售,并在北京.上海各设立了一个办事处.对于工作日志,原先采用邮件方式发给经理,但是这种方式 ...
- Vue2.x源码学习笔记-从一个小例子查看vm实例生命周期
学习任何一门框架,都不可能一股脑儿的从入口代码从上到下,把代码看完, 这样其实是很枯燥的,我想也很少有人这么干,或者这么干着干着可能干不下去了. 因为肯定很无聊. 我们先从一个最最简单的小例子,来查看 ...
- Spring和Hibernate结合的一个小例子
1.新建一个SpringHibernate的maven项目 2.pom文件的依赖为 <dependency> <groupId>junit</groupId> &l ...
- Spring.Net在ASP.NET Mvc里使用的一个小例子
就贴个小例子,就不注意格式了. 1.下载dll NuGet的下载地址:http://docs.nuget.org/docs/start-here/installing-nuget 在vs的NuGet里 ...
随机推荐
- 常用 ADB 命令[ZZ]
https://blog.csdn.net/yang_zhang_1992/article/details/71404186 1. 显示当前运行的全部模拟器: adb devices 2. 对某一模拟 ...
- 原来你离BAT只有一步之遥
ladies and乡亲们 喜迎全民嗨购双11 i春秋准备搞一波大优惠 优惠力度有多大 跨店凑单满400-50? 指定商品199减100? 史无钜惠 不凑单 不指定 一次直降9000元 原价:2580 ...
- Kali学习笔记1:Linux基本命令及安装Java
ls -l 详细信息ls /dev/ -ls 很详细ls -a 显示隐藏ls -lh 方便看ls -lh --sort=size 按大小排序.开头的都是隐藏 cd /media/ 进入cd .. 上一 ...
- 一个applicationContext 加载错误导致的阻塞解决小结
问题为对接一个sso的验证模块,正确的对接姿势为,接入一个 filter, 然后接入一个 SsoListener . 然而在接入之后,却导致了应用无法正常启动,或者说看起来很奇怪,来看下都遇到什么样的 ...
- Redis之分布式锁
目录 一.加锁原因 二.原子操作 三.分布式锁 四.分布式锁常见问题 一.加锁原因 在一些比较高并发的业务场景,经常听到通过加锁的方法实现线程安全. 下面简单介绍一下 1.1 加锁方式 数据库锁 数据 ...
- Linux的 文件 和 目录 管理
包括了文件和目录的创建.删除.修改,权限.压缩.搜索.分区.挂载 简单的一些命令: [ pwd ]查看当前所在目录 [ cd .. ]上级目录 [ cd ~ ]当前用户的家目录 [cd -]上次打开目 ...
- java开发个人简历
求职意向 Java开发工程师 陈 楠 性 别:男 出生年月 :1995.07 民 族:汉族 联系方式 :159-3306-7520 学 历:本科 电子邮件 :15933067520@163.com 教 ...
- 【spring】从简单配置使用到深入
一.使用前的配置 1.maven引入需要的jar包 <properties> <spring.version>4.1.6.RELEASE</spring.version& ...
- MySQL5.7免安装版配置图文教程
MySQL5.7免安装版配置图文教程 更新时间:2017年09月06日 10:22:11 作者:吾刃之所向 我要评论 Mysql是一个比较流行且很好用的一款数据库软件,如下记录了我学习总结的 ...
- 【ABP杂烩】Extensions后缀扩展方法
1.Extensions介绍 扩展方法使你能够向现有类型“添加”方法,而无需创建新的派生类型.重新编译或以其他方式修改原始类型. 扩展方法是一种特殊的静态方法,但可以像扩展类型上的实例方法一样进行调用 ...