由于原书是基于java,这里勇哥改为C#版本。
先引入第一章的开篇例子。
需求如下:
需求: 影片出租,计算每一位顾客的消费金额并打印详单。 操作得告诉程序:顾客租了哪些影片、租期多长,程序便根据租赁时间和影片类型计算出费用。 影片分为三类:普通片、儿童片和新片。 除了计算费用,还要为常客计算积分,积分会根据影片种类是否新片而不同。 Movie 影片类 Rental 租赁类 Customer 顾客类

priceCode:int 表示影片的种类,普通片、儿童片和新片
daysRented:int 表示出租天数
statement() 结算清单方法
源码如下:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
/*
代码需要重构的理由?
虽然编译器不区分代码的美丑,但是当我们打算修改代码的时候,就涉及到了人。
差劲的系统很难修改的原因是:很难找到修改点。
如果很难找到修改点,程序员就可能犯错,从而引入bug
这个程序的问题:
1. 如果希望以html格式输出消费单,则Statement()完全不支持,你得弄一个HtmlStatement()
这样只能复制粘贴Statement()进行修改
2. 如果计费标准发生变,则Statement()和HtmlStatement()都得一起修改。
这里复制粘贴带来的问题就浮现出来,因为要确保两处的一致性,以后这种修改会越来越复杂。
原则:如果你发现需要为程序添加一个特性,而代码结构使你无法很方便做到,
那就先重构那个程序,使特征的添加比较容易进行,然后再添加特性
*/
public partial class Form1 : Form
{
Customer lxy = new Customer("xaioyong");
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
lxy.AddRental(new Rental(new Movie("星球大战1", Movie.NEW_RELEASE), 3));
lxy.AddRental(new Rental(new Movie("勇敢者的游戏2", Movie.NEW_RELEASE), 4));
RtbMsg.Text = lxy.Statement();
}
}
/// <summary>
/// 影片类
/// </summary>
public class Movie
{
/// <summary>
/// 儿童片
/// </summary>
public const int CHILDRENS = 2;
/// <summary>
/// 普通片
/// </summary>
public const int REGULAR = 0;
/// <summary>
/// 新片
/// </summary>
public const int NEW_RELEASE = 1;
private string _title;
private int _priceCode;
public Movie(string title, int priceCode)
{
_title = title;
_priceCode = priceCode;
}
public int GetPriceCode()
{
return _priceCode;
}
public void SetPriceCode(int arg)
{
_priceCode = arg;
}
public string GetTitle()
{
return _title;
}
}
/// <summary>
/// 租赁,表示某个顾客租了一部影片
/// </summary>
public class Rental
{
private Movie _movie;
private int _dayRented;
public Rental(Movie movie, int days)
{
_movie = movie;
_dayRented = days;
}
public int GetDaysRented()
{
return _dayRented;
}
public Movie GetMovie()
{
return _movie;
}
}
/// <summary>
/// 顾客类
/// </summary>
public class Customer
{
private string _name;
private List<Rental> _rentals = new List<Rental>();
public Customer(string name)
{
_name = name;
}
public void AddRental(Rental arg)
{
_rentals.Add(arg);
}
public string GetName()
{
return _name;
}
/// <summary>
/// 生成顾客消费单
/// </summary>
/// <returns></returns>
public string Statement()
{
double totalAmount = 0;
int fPoints = 0;
var result = new StringBuilder();
result.Append($"Rental Record for {GetName()} \n");
foreach(var m in _rentals)
{
double thisAmount = 0;
switch(m.GetMovie().GetPriceCode())
{
case Movie.REGULAR:
thisAmount += 2;
if (m.GetDaysRented() > 2)
thisAmount += (m.GetDaysRented() - 2) * 1.5;
break;
case Movie.NEW_RELEASE:
thisAmount += m.GetDaysRented() * 3;
break;
case Movie.CHILDRENS:
thisAmount += 1.5;
if (m.GetDaysRented() > 3)
thisAmount += (m.GetDaysRented() - 3)*1.5;
break;
}
fPoints++;
if (m.GetMovie().GetPriceCode() == Movie.NEW_RELEASE &&
m.GetDaysRented() > 1)
fPoints++;
result.Append($"\t {m.GetMovie().GetTitle()}\t{thisAmount}\n");
totalAmount += thisAmount;
}
result.Append($"Amount owed is {totalAmount}\n");
result.Append($"You earned {fPoints} frequent renter points");
return result.ToString();
}
}
}(一)拆分长函数,并把较小块的代码移至更适合的类。
这样做的目的是降低代码重复量。
首先我们对长函数Statement() 那个switch动手。
public string Statement()
{
double totalAmount = 0;
int fPoints = 0;
var result = new StringBuilder();
result.Append($"Rental Record for {GetName()} \n");
foreach (var m in _rentals)
{
double thisAmount = amountFor(m); //这里switch变成了函数调用。
fPoints++;
if (m.GetMovie().GetPriceCode() == Movie.NEW_RELEASE &&
m.GetDaysRented() > 1)
fPoints++;
result.Append($"\t {m.GetMovie().GetTitle()}\t{thisAmount}\n");
totalAmount += thisAmount;
}
result.Append($"Amount owed is {totalAmount}\n");
result.Append($"You earned {fPoints} frequent renter points");
return result.ToString();
}
//函数变量还进行了重命名,这是值得的。
//任何一下傻瓜都能写出计算机可以理解的代码,唯有写出人类容易理解的代码,
//才是优秀的程序员
private double amountFor(Rental aRental)
{
double result = 0;
switch (aRental.GetMovie().GetPriceCode())
{
case Movie.REGULAR:
result += 2;
if (aRental.GetDaysRented() > 2)
result += (aRental.GetDaysRented() - 2) * 1.5;
break;
case Movie.NEW_RELEASE:
result += aRental.GetDaysRented() * 3;
break;
case Movie.CHILDRENS:
result += 1.5;
if (aRental.GetDaysRented() > 3)
result += (aRental.GetDaysRented() - 3) * 1.5;
break;
}
return result;
}(二)函数功能放错位置
注意上面的amountFor() 函数,它没有用到顾客类Customer的信息,只是使用了Rental类的信息。
因此它放错了位置。
绝大多数情况下,函数应该放在它所用到数据的所属对象内。
//此函数形参不变,内容变一下。
private double amountFor(Rental aRental)
{
return aRental.GetCharge();
}
//下面函数放到Rental类中去。
public double GetCharge()
{
double result = 0;
switch (GetMovie().GetPriceCode())
{
case Movie.REGULAR:
result += 2;
if (GetDaysRented() > 2)
result += (GetDaysRented() - 2) * 1.5;
break;
case Movie.NEW_RELEASE:
result += GetDaysRented() * 3;
break;
case Movie.CHILDRENS:
result += 1.5;
if (GetDaysRented() > 3)
result += (GetDaysRented() - 3) * 1.5;
break;
}
return result;
}(三)更新一下新函数的调用方式
一般来讲,尽量去除临时变量,它会导致大量参数被传来传去。
为了这个目的,下面调用了2次m.GetCharge(),浪费了一点性能。
其它对于去掉临时变量这一点,勇哥有一点不一样的观点。
有时候,临时变量的存在,是为了方便调试代码。因为你可以鼠标悬停在临时变量上面查阅结果,但是你无法这样查看函数的返回结果。
所以勇哥觉得,如果是简单变量,且需要返回结果需要被关注时,还是可以用临时变量。
public string Statement()
{
double totalAmount = 0;
int fPoints = 0;
var result = new StringBuilder();
result.Append($"Rental Record for {GetName()} \n");
foreach (var m in _rentals)
{
fPoints++;
if (m.GetMovie().GetPriceCode() == Movie.NEW_RELEASE &&
m.GetDaysRented() > 1)
fPoints++;
result.Append($"\t {m.GetMovie().GetTitle()}\t{m.GetCharge()}\n");
totalAmount += m.GetCharge();
}
result.Append($"Amount owed is {totalAmount}\n");
result.Append($"You earned {fPoints} frequent renter points");
return result.ToString();
}(四)提炼“常客积分计算”的代码。
常客积分就是那个fPoints变量。
此积分计算的责任应该放在Rental类上面。
下面计算常客积分的函数放在Rental类中去。
public int GetfPoints()
{
if (GetMovie().GetPriceCode() == Movie.NEW_RELEASE &&
GetDaysRented() > 1)
return 2;
else
return 1;
}改一下Statement函数的调用。
public string Statement()
{
double totalAmount = 0;
int fPoints = 0;
var result = new StringBuilder();
result.Append($"Rental Record for {GetName()} \n");
foreach (var m in _rentals)
{
fPoints += m.GetfPoints();
//fPoints++;
//if (m.GetMovie().GetPriceCode() == Movie.NEW_RELEASE &&
// m.GetDaysRented() > 1)
// fPoints++;
result.Append($"\t {m.GetMovie().GetTitle()}\t{m.GetCharge()}\n");
totalAmount += m.GetCharge();
}
result.Append($"Amount owed is {totalAmount}\n");
result.Append($"You earned {fPoints} frequent renter points");
return result.ToString();
}(五)


少有人走的路

















