示例:影片出租店程序(重构——分解并重组Statement) 步骤: 1、提炼“金额计算”代码 1.1、提炼“逻辑泥团”——“提炼方法(Extract Method)” Statement()中一个明显的“逻辑泥团”就是case语句,把它提炼到独立函数“AmountFor”中。 function TCustomer.Statement: string; var totalAmount: double; //--总消费金额 frequentRenterPoints: integer; //--常客积点 Rentals: TEnumeration; thisAmount: double; each: TRental; //---计算一笔租片费用 function AmountFor(each: TRental): double; var thisAmount: double; begin thisAmount := 0; case each.Movie.PriceCode of //--取得影片出租价格 REGULAR: //--普通片 begin thisAmount := thisAmount + 2; if each.DaysRented > 2 then thisAmount := thisAmount + (each.DaysRented - 2) * 1.5; end; NEW_RELEASE: //--新片 begin thisAmount := thisAmount + each.DaysRented * 3; end; CHILDRENS: //--儿童片 begin thisAmount := thisAmount + 1.5; if each.DaysRented > 3 then thisAmount := thisAmount + (each.DaysRented - 3) * 1.5; end; end; //--- Result := thisAmount; end; begin Result := 'Rental Record for ' + self.Name + #13#10; //--- totalAmount := 0; frequentRenterPoints := 0; Rentals := TEnumeration.Create(FRentals); while Rentals.HasMoreElements do begin each := TRental(Rentals.NextElement); //--取得一笔租借记录 thisAmount := AmountFor(each); //---累加常客积点 frequentRenterPoints := frequentRenterPoints + 1; if (each.Movie.PriceCode = NEW_RELEASE) and (each.DaysRented > 1) then frequentRenterPoints := frequentRenterPoints + 1; //---显示此笔租借数据 Result := Result + each.Movie.Title + ' ' + FloatToStr(thisAmount) + #13#10; totalAmount := totalAmount + thisAmount; end; Rentals.Free; //---结尾打印 Result := Result + 'Amount owed is ' + FloatToStr(totalAmount) + #13#10; Result := Result + 'You earned ' + IntToStr(frequentRenterPoints) + ' frequent renter points'; end; 1.2、规范变量名称 好的代码应该清楚表达出自己的功能,变量名称是代码清浙的关键。 //---计算一笔租片费用 function AmountFor(ARental: TRental): double; begin Result := 0; case ARental.Movie.PriceCode of //--取得影片出租价格 REGULAR: //--普通片 begin Result := Result + 2; if ARental.DaysRented > 2 then Result := Result + (ARental.DaysRented - 2) * 1.5; end; NEW_RELEASE: //--新片 begin Result := Result + ARental.DaysRented * 3; end; CHILDRENS: //--儿童片 begin Result := Result + 1.5; if ARental.DaysRented > 3 then Result := Result + (ARental.DaysRented - 3) * 1.5; end; end; end; 1.3、搬移“金额计算”代码——“搬移方法(Move Method)” AmountFor函数使用了来自Rental类的信息.却没有使用来自Customer类的信息。这表明它可能是被放错了位置,应 将AmountFor()移到Rental类中。此外,还要在搬移的同时变更函数名称(AmountFor –> GetCharge)。 function TRental.GetCharge: double; {计算一笔租片费用} begin Result := 0; //--- case self.Movie.PriceCode of //--取得影片出租价格 REGULAR: //--普通片 begin Result := Result + 2; if self.DaysRented > 2 then Result := Result + (self.DaysRented - 2) * 1.5; end; NEW_RELEASE: //--新片 begin Result := Result + self.DaysRented * 3; end; CHILDRENS: //--儿童片 begin Result := Result + 1.5; if self.DaysRented > 3 then Result := Result + (self.DaysRented - 3) * 1.5; end; end; end; function TCustomer.Statement: string; var totalAmount: double; //--总消费金额 frequentRenterPoints: integer; //--常客积点 Rentals: TEnumeration; thisAmount: double; each: TRental; begin Result := 'Rental Record for ' + self.Name + #13#10; //--- totalAmount := 0; frequentRenterPoints := 0; Rentals := TEnumeration.Create(FRentals); while Rentals.HasMoreElements do begin each := TRental(Rentals.NextElement); thisAmount := each.GetCharge; //--取得一笔租借记录 //---累加常客积点 frequentRenterPoints := frequentRenterPoints + 1; if (each.Movie.PriceCode = NEW_RELEASE) and (each.DaysRented > 1) then frequentRenterPoints := frequentRenterPoints + 1; //---显示此笔租借数据 Result := Result + each.Movie.Title + ' ' + FloatToStr(thisAmount) + #13#10; totalAmount := totalAmount + thisAmount; end; Rentals.Free; //---结尾打印 Result := Result + 'Amount owed is ' + FloatToStr(totalAmount) + #13#10; Result := Result + 'You earned ' + IntToStr(frequentRenterPoints) + ' frequent renter points'; end; 1.4、删除临时变量——“替换临时变量(Replace Temp with Query)” Statement方法中的临时变量thisAmount接受each.GetCharge的执行结果,然后就不再有任何改变。所以尽量除去这 一类临时变量。 function TCustomer.Statement: string; var totalAmount: double; //--总消费金额 frequentRenterPoints: integer; //--常客积点 Rentals: TEnumeration; each: TRental; begin Result := 'Rental Record for ' + self.Name + #13#10; //--- totalAmount := 0; frequentRenterPoints := 0; Rentals := TEnumeration.Create(FRentals); while Rentals.HasMoreElements do begin each := TRental(Rentals.NextElement); //---累加常客积点 frequentRenterPoints := frequentRenterPoints + 1; if (each.Movie.PriceCode = NEW_RELEASE) and (each.DaysRented > 1) then frequentRenterPoints := frequentRenterPoints + 1; //---显示此笔租借数据 Result := Result + each.Movie.Title + ' ' + FloatToStr(each.GetCharge) + #13#10; totalAmount := totalAmount + each.GetCharge; end; Rentals.Free; //---结尾打印 Result := Result + 'Amount owed is ' + FloatToStr(totalAmount) + #13#10; Result := Result + 'You earned ' + IntToStr(frequentRenterPoints) + ' frequent renter points'; end; 2、提炼“常客积点计算”代码 2.1、提炼“常客积点计算”代码——“提炼函数(Extract Method)” 首先,我们需要针对“常客积点计算”这部分代码运用“提炼函数(Extract Method)”重构准则,将其提炼为函 数“GetFrequentRenterPoints”。 function TCustomer.Statement: string; var totalAmount: double; //--总消费金额 frequentRenterPoints: integer; //--常客积点 Rentals: TEnumeration; each: TRental; //---获取常客积点 function GetFrequentRenterPoints: integer; begin frequentRenterPoints := frequentRenterPoints + 1; if (each.Movie.PriceCode = NEW_RELEASE) and (each.DaysRented > 1) then frequentRenterPoints := frequentRenterPoints + 1; end; begin Result := 'Rental Record for ' + self.Name + #13#10; //--- totalAmount := 0; frequentRenterPoints := 0; Rentals := TEnumeration.Create(FRentals); while Rentals.HasMoreElements do begin each := TRental(Rentals.NextElement); //---累加常客积点 GetFrequentRenterPoints; //---显示此笔租借数据 Result := Result + each.Movie.Title + ' ' + FloatToStr(each.GetCharge) + #13#10; totalAmount := totalAmount + each.GetCharge; end; Rentals.Free; //---结尾打印 Result := Result + 'Amount owed is ' + FloatToStr(totalAmount) + #13#10; Result := Result + 'You earned ' + IntToStr(frequentRenterPoints) + ' frequent renter points'; end; 然后,在“GetFrequentRenterPoints”函数内找到局部变量each,它可以被当作参数传入新函数中。另一个临时变 量是frequentRenterPoints。本例中的它在被使用之前已经先有初值,但提炼出来的函数并没有读取该值,所以我 们不需要将它当作参数传进去,只需对它执行“追加赋值操作“就行 。 function TCustomer.Statement: string; var totalAmount: double; //--总消费金额 frequentRenterPoints: integer; //--常客积点 Rentals: TEnumeration; each: TRental; //---获取常客积点 function GetFrequentRenterPoints(each: TRental):integer; var frequentRenterPoints: integer; //--常客积点 begin frequentRenterPoints := 1; if (each.Movie.PriceCode = NEW_RELEASE) and (each.DaysRented > 1) then frequentRenterPoints := frequentRenterPoints + 1; //--- Result := frequentRenterPoints; end; begin Result := 'Rental Record for ' + self.Name + #13#10; //--- totalAmount := 0; frequentRenterPoints := 0; Rentals := TEnumeration.Create(FRentals); while Rentals.HasMoreElements do begin each := TRental(Rentals.NextElement); //---累加常客积点 frequentRenterPoints := frequentRenterPoints + GetFrequentRenterPoints(each); //---显示此笔租借数据 Result := Result + each.Movie.Title + ' ' + FloatToStr(each.GetCharge) + #13#10; totalAmount := totalAmount + each.GetCharge; end; Rentals.Free; //---结尾打印 Result := Result + 'Amount owed is ' + FloatToStr(totalAmount) + #13#10; Result := Result + 'You earned ' + IntToStr(frequentRenterPoints) + ' frequent renter points'; end; 2.2、规范变量名称 //---获取常客积点 function GetFrequentRenterPoints(each: TRental):integer; begin if (each.Movie.PriceCode = NEW_RELEASE) and (each.DaysRented > 1) then Result := 2 else Result := 1; end; 2.3、搬移“常客积点计算”代码——“搬移方法(Move Method)” function TRental.GetFrequentRenterPoints: integer; {获取常客积点} begin if (self.Movie.PriceCode = NEW_RELEASE) and (self.DaysRented > 1) then Result := 2 else Result := 1; end; function TCustomer.Statement: string; var totalAmount: double; //--总消费金额 frequentRenterPoints: integer; //--常客积点 Rentals: TEnumeration; each: TRental; begin Result := 'Rental Record for ' + self.Name + #13#10; //--- totalAmount := 0; frequentRenterPoints := 0; Rentals := TEnumeration.Create(FRentals); while Rentals.HasMoreElements do begin each := TRental(Rentals.NextElement); //---累加常客积点 frequentRenterPoints := frequentRenterPoints + each.GetFrequentRenterPoints; //---显示此笔租借数据 Result := Result + each.Movie.Title + ' ' + FloatToStr(each.GetCharge) + #13#10; totalAmount := totalAmount + each.GetCharge; end; Rentals.Free; //---结尾打印 Result := Result + 'Amount owed is ' + FloatToStr(totalAmount) + #13#10; Result := Result + 'You earned ' + IntToStr(frequentRenterPoints) + ' frequent renter points'; end; 3、总量计算(去除临时变量) statement函数中有两个临时变量totalAmount和frequentRenterPoints,两者都是用来从Customer对象相关的 Rental对象中获得某个总量。因为不论 ASCll 版或 HTML 版都需要这些总量。所以,我们运用“替换临时变量 (Replace Temp with Query)”和“查询方法(Query Method)”来取代临时变量。 通过去除临时变量,可以将冗长复杂的函数中的逻辑理顺,并使其更为清晰。如果系统中的其它地方需要这些信息 ,也可以很轻松地将“查询方法”加入Customer类的公共接口。 3.1、总消费金额 使用“查询方法GetTotalCharge”去除“临时变量totalAmount”。 function TCustomer.GetTotalCharge: double; {总消费金额} var Rentals: TEnumeration; each: TRental; begin Result := 0; //--- Rentals := TEnumeration.Create(FRentals); while Rentals.HasMoreElements do begin each := TRental(Rentals.NextElement); Result := Result + each.GetCharge; end; Rentals.Free; end; function TCustomer.Statement: string; var frequentRenterPoints: integer; //--常客积点 Rentals: TEnumeration; each: TRental; begin Result := 'Rental Record for ' + self.Name + #13#10; //--- frequentRenterPoints := 0; Rentals := TEnumeration.Create(FRentals); while Rentals.HasMoreElements do begin each := TRental(Rentals.NextElement); //---累加常客积点 frequentRenterPoints := frequentRenterPoints + each.GetFrequentRenterPoints; //---显示此笔租借数据 Result := Result + each.Movie.Title + ' ' + FloatToStr(each.GetCharge) + #13#10; end; Rentals.Free; //---结尾打印 Result := Result + 'Amount owed is ' + FloatToStr(self.GetTotalCharge) + #13#10; Result := Result + 'You earned ' + IntToStr(frequentRenterPoints) + ' frequent renter points'; end; 3.2、总常客积点 用“查询方法GetTotalFrequentRenterPoints”去除“临时变量frequentRenterPoints”。 function TCustomer.GetTotalFrequentRenterPoints: integer; {总常客积点} var Rentals: TEnumeration; each: TRental; begin Result := 0; //--- Rentals := TEnumeration.Create(FRentals); while Rentals.HasMoreElements do begin each := TRental(Rentals.NextElement); Result := Result + each.GetFrequentRenterPoints; end; Rentals.Free; end; function TCustomer.Statement: string; var Rentals: TEnumeration; each: TRental; begin Result := 'Rental Record for ' + self.Name + #13#10; //--- Rentals := TEnumeration.Create(FRentals); while Rentals.HasMoreElements do begin each := TRental(Rentals.NextElement); //---显示此笔租借数据 Result := Result + each.Movie.Title + ' ' + FloatToStr(each.GetCharge) + #13#10; end; Rentals.Free; //---结尾打印 Result := Result + 'Amount owed is ' + FloatToStr(self.GetTotalCharge) + #13#10; Result := Result + 'You earned ' + IntToStr(self.GetTotalFrequentRenterPoints) + ' frequent renter points'; end; 代码: unit uMovie_Refactoring; interface uses SysUtils,Contnrs; const REGULAR = 0; NEW_RELEASE = 1; CHILDRENS = 2; type TEnumeration = class private FList: TObjectList; FIndex: integer; public constructor Create(const AList: TObjectList); //--- function HasMoreElements: boolean; function NextElement: TObject; end; //--影片 TMovie = class private FTitle: string; //--名称 FPriceCode: integer; //--价格(代号) function GetPriceCode: integer; function GetTitle: string; procedure SetPriceCode(const Value: integer); public constructor Create(const ATitle: string; APriceCode: integer); //--- property Title: string read GetTitle; property PriceCode: integer read GetPriceCode write SetPriceCode; end; //--租赁 TRental = class private FMovie: TMovie; FDaysRented: integer; //--租期 function GetDaysRented: integer; function GetMovie: TMovie; public constructor Create(const AMovie: TMovie; ADaysRented: integer); //--- function GetCharge: double; //---计算一笔租片费用 function GetFrequentRenterPoints: integer; //--- property Movie: TMovie read GetMovie; property DaysRented: integer read GetDaysRented; end; //--顾客 TCustomer = class private FName: string; FRentals: TObjectList; function GetName: string; function GetTotalCharge: double; function GetTotalFrequentRenterPoints: integer; public constructor Create(const AName: string); destructor Destroy; override; //--- procedure AddRental(arg: TRental); function Statement: string; //--统计报表 //--- property Name: string read GetName; end; implementation constructor TMovie.Create(const ATitle: string; APriceCode: integer); begin FTitle := ATitle; FPriceCode := APriceCode; end; function TMovie.GetPriceCode: integer; begin Result := FPriceCode; end; function TMovie.GetTitle: string; begin Result := FTitle; end; procedure TMovie.SetPriceCode(const Value: integer); begin FPriceCode := Value; end; constructor TRental.Create(const AMovie: TMovie; ADaysRented: integer); begin FMovie := AMovie; FDaysRented := ADaysRented; end; function TRental.GetCharge: double; {计算一笔租片费用} begin Result := 0; //--- case self.Movie.PriceCode of //--取得影片出租价格 REGULAR: //--普通片 begin Result := Result + 2; if self.DaysRented > 2 then Result := Result + (self.DaysRented - 2) * 1.5; end; NEW_RELEASE: //--新片 begin Result := Result + self.DaysRented * 3; end; CHILDRENS: //--儿童片 begin Result := Result + 1.5; if self.DaysRented > 3 then Result := Result + (self.DaysRented - 3) * 1.5; end; end; end; function TRental.GetDaysRented: integer; begin Result := FDaysRented; end; function TRental.GetFrequentRenterPoints: integer; {获取常客积点} begin if (self.Movie.PriceCode = NEW_RELEASE) and (self.DaysRented > 1) then Result := 2 else Result := 1; end; function TRental.GetMovie: TMovie; begin Result := FMovie; end; constructor TCustomer.Create(const AName: string); begin FName := AName; FRentals := TObjectList.Create; end; destructor TCustomer.Destroy; begin FRentals.Free; //--- inherited; end; procedure TCustomer.AddRental(arg: TRental); begin FRentals.Add(arg); end; function TCustomer.GetName: string; begin Result := FName; end; function TCustomer.GetTotalCharge: double; {总消费金额} var Rentals: TEnumeration; each: TRental; begin Result := 0; //--- Rentals := TEnumeration.Create(FRentals); while Rentals.HasMoreElements do begin each := TRental(Rentals.NextElement); Result := Result + each.GetCharge; end; Rentals.Free; end; function TCustomer.GetTotalFrequentRenterPoints: integer; {总常客积点} var Rentals: TEnumeration; each: TRental; begin Result := 0; //--- Rentals := TEnumeration.Create(FRentals); while Rentals.HasMoreElements do begin each := TRental(Rentals.NextElement); Result := Result + each.GetFrequentRenterPoints; end; Rentals.Free; end; function TCustomer.Statement: string; var Rentals: TEnumeration; each: TRental; begin Result := 'Rental Record for ' + self.Name + #13#10; //--- Rentals := TEnumeration.Create(FRentals); while Rentals.HasMoreElements do begin each := TRental(Rentals.NextElement); //---显示此笔租借数据 Result := Result + each.Movie.Title + ' ' + FloatToStr(each.GetCharge) + #13#10; end; Rentals.Free; //---结尾打印 Result := Result + 'Amount owed is ' + FloatToStr(self.GetTotalCharge) + #13#10; Result := Result + 'You earned ' + IntToStr(self.GetTotalFrequentRenterPoints) + ' frequent renter points'; end; constructor TEnumeration.Create(const AList: TObjectList); begin FList := AList; FIndex := 0; end; function TEnumeration.HasMoreElements: boolean; begin Result := FIndex < FList.Count; end; function TEnumeration.NextElement: TObject; begin Result := FList[FIndex]; FIndex := FIndex + 1; end; end.