《重构》 — Delphi示例:影片出租店程序(3、重构——分解并重组Statement)

    技术2022-05-20  61

    示例:影片出租店程序(重构——分解并重组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.


    最新回复(0)