|
Posted on 2009-02-02 12:52 lymons 阅读(399) 评论(0) 编辑 收藏 引用 所属分类: C++
重構, 第一個案例 Refactoring, a First Example
這是一個影片出租店用的程式,計算每位顧客的消費金額並列 印報表(statement)。操作者告訴程式:顧客租了哪些影片、租期多長,程式便 根據租賃時間和影片類型算出費用。影片分為三類:普通片、兒童片和新片。除 了計算費用,還要為常客計算點數;點數會隨著「租片種類是否為新片」而有不 同。 我以數個classes 表現這個例子?的元素。圖1.1 是一張UML class diagram(類別 圖),用以顯示這些classes。
1// movie.cpp : Defines the entry point for the console application. 2// 3#include <string> 4#include <vector> 5#include <iostream> 6 7using namespace std; 8 9 10// Refactoring, a First Example, step1, (~p5) 11 12class Movie { 13public: 14 enum TYPE { 15 REGULAR, 16 NEW_RELEASE, 17 CHILDRENS 18 }; 19 20private: 21 string _title; //movie name 22 int _priceCode; //price code 23 24public: 25 //default construtor for `Rental::Rental(Movie, int)' 26 Movie() { 27 _title = "unname"; 28 _priceCode = 0; 29 } 30 Movie(string title, int priceCode){ 31 _title = title; 32 _priceCode = priceCode; 33 } 34 35 int getPriceCode() { 36 return _priceCode; 37 } 38 39 void setPriceCode(int arg) { 40 _priceCode = arg; 41 } 42 43 string getTitle() { 44 return _title; 45 } 46}; 47 48class Rental { 49private: 50 Movie _movie; 51 int _daysRented; 52 53public: 54 Rental(Movie movie, int daysRented) { 55 _movie = movie; 56 _daysRented = daysRented; 57 } 58 59 int getDaysRented() { 60 return _daysRented; 61 } 62 63 Movie getMovie() { 64 return _movie; 65 } 66}; 67 68typedef vector<Rental> Vector; 69typedef vector<Rental>::iterator Reniter; 70 71class Customer { 72private: 73 string _name; 74 Vector _rentals; 75 76public: 77 Customer(string name) { 78 _name = name; 79 } 80 81 void addRental(Rental arg) { 82 _rentals.push_back(arg); 83 } 84 85 string getName() { 86 return _name; 87 } 88 89 string statement() { 90 double totalAmount = 0; 91 int frequentRenterPoints = 0; 92 Reniter iter; 93 char amount[32]; 94 string result = string("Rental Record for ") + getName() + string("\n"); 95 96 for (iter = _rentals.begin(); iter != _rentals.end(); ++ iter) { 97 double thisAmount = 0; 98 Rental each = *iter; 99 100 //determine amounts for each line 101 switch(each.getMovie().getPriceCode()){ 102 case Movie::REGULAR: 103 thisAmount += 2; 104 if(each.getDaysRented()>2) 105 thisAmount += (each.getDaysRented()-2)*1.5; 106 break; 107 108 case Movie::NEW_RELEASE: 109 thisAmount += each.getDaysRented()*3; 110 break; 111 112 case Movie::CHILDRENS: 113 thisAmount += 1.5; 114 if(each.getDaysRented()>3) 115 thisAmount += (each.getDaysRented()-3)*1.5; 116 break; 117 } 118 119 // add frequent renter points? 120 frequentRenterPoints ++; 121 // add bonus for a two day new release rental 122 if ((each.getMovie().getPriceCode() == Movie::NEW_RELEASE) && 123 each.getDaysRented() > 1) 124 frequentRenterPoints ++; 125 126 // show figures for this rental? 127 snprintf(amount, 32,"%f\n",thisAmount); 128 result += string("\t") + each.getMovie().getTitle() + string("\t") + 129 string(amount); 130 totalAmount += thisAmount; 131 } 132 133 // add footer lines? 134 snprintf(amount, 32, "%f\n",totalAmount); 135 result += string("Amount owed is ") + string(amount); 136 snprintf(amount, 32,"%d",frequentRenterPoints); 137 result += string("You earned ") + string(amount) + 138 string(" frequent renter points"); 139 return result; 140 } 141}; 142 143int main(int argc, char* argv[]) 144{ 145 cout<<"Refactoring, a First Example, step1"<<endl; 146 147 Movie m1 = Movie("Seven", Movie::NEW_RELEASE); 148 Movie m2 = Movie("Terminator", Movie::REGULAR); 149 Movie m3 = Movie("Star Trek", Movie::CHILDRENS); 150 151 Rental r1 = Rental(m1, 4); 152 Rental r2 = Rental(m1, 2); 153 Rental r3 = Rental(m3, 7); 154 Rental r4 = Rental(m2, 5); 155 Rental r5 = Rental(m3, 3); 156 157 Customer c1 = Customer("jjhou"); 158 c1.addRental(r1); 159 c1.addRental(r4); 160 161 Customer c2 = Customer("gigix"); 162 c2.addRental(r1); 163 c2.addRental(r3); 164 c2.addRental(r2); 165 166 Customer c3 = Customer("jiangtao"); 167 c3.addRental(r3); 168 c3.addRental(r5); 169 170 Customer c4 = Customer("yeka"); 171 c4.addRental(r2); 172 c4.addRental(r3); 173 c4.addRental(r5); 174 175 cout <<c1.statement() <<endl; 176 cout <<c2.statement() <<endl; 177 cout <<c3.statement() <<endl; 178 cout <<c4.statement() <<endl; 179 180 return 0; 181} 182 183
重构后的C++代码
1// movie-ref.cpp : Defines the entry point for the console application. 2// 3#include <string> 4#include <vector> 5#include <iostream> 6 7using namespace std; 8// Refactoring, a First Example, step7, (~p52) 9 10enum TYPE 11{ 12 REGULAR, 13 NEW_RELEASE, 14 CHILDRENS 15}; 16 17class Price 18{ 19public: 20 virtual int getPriceCode () = 0; 21 virtual double getCharge (int daysRented) = 0; 22 23 int getFrequentRenterPoints (int daysRented) 24 { 25 return 1; 26 } 27 28}; 29 30class ChildrensPrice:public Price 31{ 32public: 33 int getPriceCode () 34 { 35 return CHILDRENS; 36 } 37 38 double getCharge (int daysRented) 39 { 40 double result = 1.5; 41 if (daysRented > 3) 42 result += (daysRented - 3) * 1.5; 43 return result; 44 } 45}; 46 47class NewReleasePrice:public Price 48{ 49public: 50 int getPriceCode () 51 { 52 return NEW_RELEASE; 53 } 54 55 double getCharge (int daysRented) 56 { 57 return daysRented * 3; 58 } 59 60 int getFrequentRenterPoints (int daysRented) 61 { 62 return (daysRented > 1) ? 2 : 1; 63 } 64}; 65 66class RegularPrice:public Price 67{ 68public: 69 int getPriceCode () 70 { 71 return REGULAR; 72 } 73 74 double getCharge (int daysRented) 75 { 76 double result = 2; 77 if (daysRented > 2) 78 result += (daysRented - 2) * 1.5; 79 return result; 80 } 81}; 82 83class Movie 84{ 85private: 86 string _title; //movie name 87 Price *_price; //price code 88 89public: 90 //default construtor for `Rental::Rental(Movie&, int)' 91 Movie () 92 { 93 _title = "unname"; 94 setPriceCode (0); 95 } 96 97 Movie (string title, int priceCode) 98 { 99 _title = title; 100 setPriceCode (priceCode); 101 } 102 103 Movie (const Movie& movie) 104 { 105 _title = movie._title; 106 setPriceCode (movie._price->getPriceCode()); 107 } 108 109 ~Movie() 110 { 111 delete _price; 112 } 113 114 int getPriceCode () 115 { 116 return _price->getPriceCode (); 117 } 118 119 void setPriceCode (int arg) 120 { 121 switch (arg) 122 { 123 case REGULAR: 124 _price = new RegularPrice (); 125 break; 126 case CHILDRENS: 127 _price = new ChildrensPrice (); 128 break; 129 case NEW_RELEASE: 130 _price = new NewReleasePrice (); 131 break; 132 default: 133 cout << "Incorrect Price Code" << endl; 134 break; 135 } 136 } 137 138 string getTitle () 139 { 140 return _title; 141 } 142 143 double getCharge (int daysRented) 144 { 145 return _price->getCharge (daysRented); 146 } 147 148 int getFrequentRenterPoints (int daysRented) 149 { 150 return _price->getFrequentRenterPoints (daysRented); 151 } 152}; 153 154class Rental 155{ 156private: 157 Movie _movie; 158 int _daysRented; 159 160public: 161 Rental (Movie& movie, int daysRented) 162 { 163 _movie = movie; 164 _daysRented = daysRented; 165 } 166 167 int getDaysRented () 168 { 169 return _daysRented; 170 } 171 172 Movie getMovie () 173 { 174 return _movie; 175 } 176 177 double getCharge () 178 { 179 return _movie.getCharge (_daysRented); 180 } 181 182 int getFrequentRenterPoints () 183 { 184 return _movie.getFrequentRenterPoints (_daysRented); 185 } 186}; 187 188typedef vector < Rental > Vector; 189typedef vector < Rental >::iterator Reniter; 190 191class Customer 192{ 193private: 194 string _name; 195 Vector _rentals; 196 197public: 198 Customer (string name) 199 { 200 _name = name; 201 } 202 203 void addRental (Rental& arg) 204 { 205 _rentals.push_back (arg); 206 } 207 208 string getName () 209 { 210 return _name; 211 } 212 213 string statement () 214 { 215 Reniter iter; 216 char amount[32]; 217 string result = 218 string ("Rental Record for ") + getName () + string ("\n"); 219 220 for (iter = _rentals.begin (); iter != _rentals.end (); ++iter) 221 { 222 Rental each = *iter; 223 // show figures for this rental 224 snprintf (amount, 32, "%f\n", each.getCharge ()); 225 result += string ("\t") + each.getMovie ().getTitle () 226 + string ("\t") + string (amount); 227 } 228 // add footer lines 229 snprintf (amount, 32, "%f\n", getTotalCharge ()); 230 result += string ("Amount owed is ") + string (amount); 231 snprintf (amount, 32, "%d", getTotalFrequentRenterPoints ()); 232 result += string ("You earned ") + string (amount) + 233 string (" frequent renter points"); 234 return result; 235 } 236 237 string htmlStatement () 238 { 239 Reniter iter; 240 char amount[32]; 241 string result = 242 string ("<H1>Rentals for <EM>") + getName () + 243 string ("</EM></H1><P>\n"); 244 245 for (iter = _rentals.begin (); iter != _rentals.end (); ++iter) 246 { 247 Rental each = *iter; 248 // show figures for this rental 249 snprintf (amount, 32, "%f<BR>\n", each.getCharge ()); 250 result += each.getMovie ().getTitle () + string (":") + string (amount); 251 } 252 253 // add footer lines? 254 snprintf (amount, 32, "%f</EM><P>\n", getTotalCharge ()); 255 result += string ("<P>You owe <EM>") + string (amount); 256 snprintf (amount, 32, "%d</EM> frequent renter points<P>", 257 getTotalFrequentRenterPoints ()); 258 result += string ("On this rental you earned <EM>") + string (amount); 259 return result; 260 } 261 262 int getTotalFrequentRenterPoints () 263 { 264 int result = 0; 265 Reniter iter; 266 for (iter = _rentals.begin (); iter != _rentals.end (); ++iter) 267 { 268 Rental each = *iter; 269 result += each.getFrequentRenterPoints (); 270 } 271 return result; 272 } 273 274 double getTotalCharge () 275 { 276 double result = 0; 277 Reniter iter; 278 for (iter = _rentals.begin (); iter != _rentals.end (); ++iter) 279 { 280 Rental each = *iter; 281 result += each.getCharge (); 282 } 283 return result; 284 } 285 286}; 287 288int 289main (int argc, char *argv[]) 290{ 291 cout << "Refactoring, a First Example, step7" << endl; 292 293 Movie m1 = Movie ("Seven", NEW_RELEASE); 294 Movie m2 = Movie ("Terminator", REGULAR); 295 Movie m3 = Movie ("Star Trek", CHILDRENS); 296 297 Rental r1 = Rental (m1, 4); 298 Rental r2 = Rental (m1, 2); 299 Rental r3 = Rental (m3, 7); 300 Rental r4 = Rental (m2, 5); 301 Rental r5 = Rental (m3, 3); 302 303 Customer c1 = Customer ("jjhou"); 304 c1.addRental (r1); 305 c1.addRental (r4); 306 307 Customer c2 = Customer ("gigix"); 308 c2.addRental (r1); 309 c2.addRental (r3); 310 c2.addRental (r2); 311 312 Customer c3 = Customer ("jiangtao"); 313 c3.addRental (r3); 314 c3.addRental (r5); 315 316 Customer c4 = Customer ("yeka"); 317 c4.addRental (r2); 318 c4.addRental (r3); 319 c4.addRental (r5); 320 321 cout << c1.statement () << endl; 322 cout << c2.statement () << endl; 323 cout << c3.statement () << endl; 324 cout << c4.statement () << endl; 325 326 return 0; 327} 328 329
結語 這是一個簡單的例子,但我希望它能讓你對於「重構是什麼樣子」有一點感覺。 例如我已經示範了數個重構準則,包括Extract Method(110)、Move Method(142)、 Replace Conditional with Polymorphism(255)、Self Encapsulate Field(171) 、 Replace Type Code with State/Strategy(227)。所有這些重構行為都使責任的分 配更合理,程式碼的維護更輕鬆。重構後的程式風格,將十分不同於程序式 (procedural)風格,後者也許是某些人習慣的風格。不過一旦你習慣了這種重構 後的風格,就很難再回到(再滿足於)結構化風格了。 這個例子給你的最重要一課是「重構的節奏」:測試、小修改、測試、小修改、 測試、小修改…。正是這種節奏讓重構得以快速而安全地前進。
|