From 2008精选 |
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
7
using
namespace
std;
8
9
10
//
Refactoring, a First Example, step1, (~p5)
11
12
class
Movie
{
13
public:
14
enum TYPE
{
15
REGULAR,
16
NEW_RELEASE,
17
CHILDRENS
18
};
19
20
private:
21
string _title; //movie name
22
int _priceCode; //price code
23
24
public:
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
48
class
Rental
{
49
private:
50
Movie _movie;
51
int _daysRented;
52
53
public:
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
68
typedef vector
<
Rental
>
Vector;
69
typedef vector
<
Rental
>
::iterator Reniter;
70
71
class
Customer
{
72
private:
73
string _name;
74
Vector _rentals;
75
76
public:
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
143
int
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

2

3

4

5

6

7

8

9

10

11

12



13

14



15

16

17

18

19

20

21

22

23

24

25

26



27

28

29

30



31

32

33

34

35



36

37

38

39



40

41

42

43



44

45

46

47

48



49

50

51

52

53

54



55

56

57

58

59



60

61

62

63



64

65

66

67

68

69

70

71



72

73

74

75

76

77



78

79

80

81



82

83

84

85



86

87

88

89



90

91

92

93

94

95

96



97

98

99

100

101



102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144



145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

1
//
movie-ref.cpp : Defines the entry point for the console application.
2
//
3
#include
<
string
>
4
#include
<
vector
>
5
#include
<
iostream
>
6
7
using
namespace
std;
8
//
Refactoring, a First Example, step7, (~p52)
9
10
enum
TYPE
11
{
12
REGULAR,
13
NEW_RELEASE,
14
CHILDRENS
15
}
;
16
17
class
Price
18
{
19
public:
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
30
class
ChildrensPrice:
public
Price
31
{
32
public:
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
47
class
NewReleasePrice:
public
Price
48
{
49
public:
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
66
class
RegularPrice:
public
Price
67
{
68
public:
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
83
class
Movie
84
{
85
private:
86
string _title; //movie name
87
Price *_price; //price code
88
89
public:
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
154
class
Rental
155
{
156
private:
157
Movie _movie;
158
int _daysRented;
159
160
public:
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
188
typedef vector
<
Rental
>
Vector;
189
typedef vector
<
Rental
>
::iterator Reniter;
190
191
class
Customer
192
{
193
private:
194
string _name;
195
Vector _rentals;
196
197
public:
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
288
int
289
main (
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

2

3

4

5

6

7

8

9

10

11



12

13

14

15

16

17

18



19

20

21

22

23

24



25

26

27

28

29

30

31



32

33

34



35

36

37

38

39



40

41

42

43

44

45

46

47

48



49

50

51



52

53

54

55

56



57

58

59

60

61



62

63

64

65

66

67



68

69

70



71

72

73

74

75



76

77

78

79

80

81

82

83

84



85

86

87

88

89

90

91

92



93

94

95

96

97

98



99

100

101

102

103

104



105

106

107

108

109

110



111

112

113

114

115



116

117

118

119

120



121

122



123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139



140

141

142

143

144



145

146

147

148

149



150

151

152

153

154

155



156

157

158

159

160

161

162



163

164

165

166

167

168



169

170

171

172

173



174

175

176

177

178



179

180

181

182

183



184

185

186

187

188

189

190

191

192



193

194

195

196

197

198

199



200

201

202

203

204



205

206

207

208

209



210

211

212

213

214



215

216

217

218

219

220

221



222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238



239

240

241

242

243

244

245

246



247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263



264

265

266

267



268

269

270

271

272

273

274

275



276

277

278

279



280

281

282

283

284

285

286

287

288

289

290



291

292

293

294

295

296

297

298

299

300

301

302

303

304

305

306

307

308

309

310

311

312

313

314

315

316

317

318

319

320

321

322

323

324

325

326

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)風格,後者也許是某些人習慣的風格。不過一旦你習慣了這種重構
後的風格,就很難再回到(再滿足於)結構化風格了。
這個例子給你的最重要一課是「重構的節奏」:測試、小修改、測試、小修改、
測試、小修改…。正是這種節奏讓重構得以快速而安全地前進。