【重構筆記01】第一個案例/補齊插件


前言

一次寫的日歷插件基本完成,中間我和團隊一個高手交流了一下,其實就是他code review我的代碼了,最后我發現我之前雖然能完成交待下來的任務但是代碼卻不好看。

這個不好看,是由於各種原因就這樣了,於是當時就想說重構下吧,但是任務一來就給放下了。

現在想來,就算真的要重構,但是也不一定知道如何重構,無論最近學習jquery代碼還是其他其實都是為了在思想上有所提升而不一定是代碼上

如何然自己的代碼更優雅

如何讓自己的程序可擴展性高

如何讓自己的代碼更可用

這些都是接下來需要解決的問題,學習一事如逆水行舟啊!所以我這里搞了一本《重構》一書,准備在好好學習一番。

關於插件

這個說是插件其實代碼還是比較糟糕的,寫到后面也沒怎么思考了,這里暫且搞出來各位看看,等后面點《重構》學習結束了做一次重構吧!

由於是公司已經再用的代碼,我這里就只貼js代碼,CSS就不搞出來了,有興趣的同學就自己看看吧,我這里截個圖各位覺得有用就看看代碼吧:

簡單列表應用

 

觸發change事件

這個東西就是第一列的變化第二個會跟着變,第二個變了第三個也會變,然后點擊確定后會回調一個函數,並獲得所選值。

不可選項

這個中當滑動到無效(灰色)的選項時,會重置為最近一個可選項

源代碼

  1 var ScrollList = function (opts) {
  2 
  3         //兼容性方案處理,以及后期資源清理
  4         var isTouch = 'ontouchstart' in document.documentElement;
  5                 isTouch = true;
  6         this.start = isTouch ? 'touchstart' : 'mousedown';
  7         this.move = isTouch ? 'touchmove' : 'mousemove';
  8         this.end = isTouch ? 'touchend' : 'mouseup';
  9         this.startFn;
 10         this.moveFn;
 11         this.endFn;
 12 
 13         opts = opts || {};
 14 
 15         //數據源
 16         this.data = opts.data || [];
 17         this.dataK = {}; //以id作為檢索鍵值
 18 
 19         this.initBaseDom(opts);
 20 
 21         this._changed = opts.changed || null;
 22         //當選情況下會有一個初始值
 23         this.selectedIndex = parseInt(this.disItemNum / 2); //暫時不考慮多選的情況
 24         if (this.type == 'list') {
 25             this.selectedIndex = -1;
 26         }
 27         this.selectedIndex = opts.index != undefined ? opts.index : this.selectedIndex;
 28 
 29         //如果數組長度有問題的話
 30         this.selectedIndex = this.selectedIndex > this.data.length ? 0 : this.selectedIndex;
 31 
 32         var isFind = false, index = this.selectedIndex;
 33         if (this.data[index] && (typeof this.data[index].disabled == 'undefined' || this.data[index].disabled == false)) {
 34             for (i = index, len = this.data.length; i < len; i++) {
 35                 if (typeof this.data[i].disabled == 'undefined' || this.data[i].disabled == true) {
 36                     index = i;
 37                     isFind = true;
 38                     break;
 39                 }
 40             }
 41             if (isFind == false) {
 42                 for (i = index; i != 0; i--) {
 43                     if (typeof this.data[i].disabled == 'undefined' || this.data[i].disabled == true) {
 44                         index = i;
 45                         isFind = true;
 46                         break;
 47                     }
 48                 }
 49             }
 50             if (isFind) this.selectedIndex = index;
 51         }
 52 
 53         this.animateParam = opts.animateParam || [50, 40, 30, 25, 20, 15, 10, 8, 6, 4, 2]; //動畫參數
 54         this.animateParam = opts.animateParam || [10, 8, 6, 5, 4, 3, 2, 1, 0, 0, 0]; //動畫參數
 55 
 56         this.setBaseParam();
 57         this.init();
 58     };
 59     ScrollList.prototype = {
 60         constructor: ScrollList,
 61         init: function () {
 62             this.initItem();
 63             this.wrapper.append(this.body);
 64             this.initEventParam();
 65             this.bindEvent();
 66             this.setIndex(this.selectedIndex, true);
 67         },
 68         //基本參數設置
 69         setBaseParam: function () {
 70             /*
 71             定位實際需要用到的信息
 72             暫時不考慮水平移動吧
 73             */
 74             this.setHeight = 0; //被設置的高度
 75             this.itemHeight = 0; //單個item高度
 76             this.dragHeight = 0; //拖動元素高度
 77             this.dragTop = 0; //拖動元素top
 78             this.timeGap = 0; //時間間隔
 79             this.touchTime = 0; //開始時間
 80             this.moveAble = false; //是否正在移動
 81             this.moveState = 'up'; //移動狀態,up right down left
 82             this.oTop = 0; //拖動前的top值
 83             this.curTop = 0; //當前容器top
 84             this.mouseY = 0; //鼠標第一次點下時相對父容器的位置
 85             this.cooling = false; //是否處於冷卻時間
 86         },
 87         initBaseDom: function (opts) {
 88             //容器元素
 89             this.wrapper = opts.wrapper || $(document);
 90             this.type = opts.type || 'list'; //list, radio
 91 
 92             //顯示的項目,由此確定顯示區域的高度,所以height無用
 93             this.disItemNum = 5;
 94 
 95             var id = opts.id || 'id_' + new Date().getTime();
 96             var className = opts.className || 'cui-roller-bd';
 97 
 98             var scrollClass;
 99             //單選的情況需要確定顯示選擇項
100             if (this.type == 'list') {
101                 scrollClass = 'cui-select-view';
102             }
103             else if (this.type == 'radio') {
104                 scrollClass = 'ul-list';
105                 this.disItemNum = 3;
106             }
107             this.disItemNum = opts.disItemNum || this.disItemNum;
108             this.disItemNum = this.disItemNum % 2 == 0 ? this.disItemNum + 1 : this.disItemNum; //必須是奇數
109 
110             scrollClass = opts.scrollClass || scrollClass;
111             this.scrollClass = scrollClass;
112 
113             //這里使用height不還有待商榷,因為class含有樣式
114 
115             this.body = $([
116                     '<div class="' + className + '" style="overflow: hidden; position: relative; " id="' + id + '" >',
117                     '</div>'
118                     ].join(''));
119             //真正拖動的元素(現在是ul)
120             this.dragEl = $([
121                     '<ul class="' + scrollClass + '" style="position: absolute; width: 100%;">',
122                     '</ul>'
123                     ].join(''));
124             this.body.append(this.dragEl);
125             //單選情況需要加入蒙版
126             //            if (this.type == 'radio' && this.disItemNum != 1) {
127             //                this.body.append($([
128             //                        '<div class="cui-mask"></div>',
129             //                        '<div class="cui-lines">&nbsp;</div>'
130             //                        ].join('')));
131             //            }
132         },
133         //增加數據
134         initItem: function () {
135             var _tmp, _data, i, k, val;
136             this.size = this.data.length; //當前容量
137             for (var i in this.data) {
138                 _data = this.data[i];
139                 _data.index = i;
140 
141                 if (typeof _data.key == 'undefined') _data.key = _data.id;
142                 if (typeof _data.val == 'undefined') _data.val = _data.name;
143 
144 
145                 val = _data.val || _data.key;
146                 this.dataK[_data.key] = _data;
147                 _tmp = $('<li>' + val + '</li>');
148                 _tmp.attr('data-index', i);
149                 if (typeof _data.disabled != 'undefined' && _data.disabled == false) {
150                     _tmp.css('color', 'gray');
151                 }
152 
153                 this.dragEl.append(_tmp);
154             }
155 
156         },
157         //初始化事件需要用到的參數信息
158         initEventParam: function () {
159             //如果沒有數據的話就在這里斷了吧
160             if (this.data.constructor != Array || this.data.length == 0) return false;
161             var offset = this.dragEl.offset();
162             var li = this.dragEl.find('li').eq(0);
163             var itemOffset = li.offset();
164             //暫時不考慮邊框與外邊距問題
165             this.itemHeight = itemOffset.height;
166             this.setHeight = this.itemHeight * this.disItemNum;
167             this.body.css('height', this.setHeight);
168             this.dragTop = offset.top;
169             this.dragHeight = this.itemHeight * this.size;
170             var s = '';
171         },
172         bindEvent: function () {
173             var scope = this;
174             this.startFn = function (e) {
175                 scope.touchStart.call(scope, e);
176             };
177             this.moveFn = function (e) {
178                 scope.touchMove.call(scope, e);
179             };
180             this.endFn = function (e) {
181                 scope.touchEnd.call(scope, e);
182             };
183 
184             this.dragEl[0].addEventListener(this.start, this.startFn, false);
185             this.dragEl[0].addEventListener(this.move, this.moveFn, false);
186             this.dragEl[0].addEventListener(this.end, this.endFn, false);
187         },
188         removeEvent: function () {
189             this.dragEl[0].removeEventListener(this.start, this.startFn);
190             this.dragEl[0].removeEventListener(this.move, this.moveFn);
191             this.dragEl[0].removeEventListener(this.end, this.endFn);
192         },
193         touchStart: function (e) {
194             var scope = this;
195             //冷卻時間不能開始
196             if (this.cooling) {
197                 setTimeout(function () {
198                     scope.cooling = false;
199                 }, 500);
200                 return false;
201             }
202             //需要判斷是否是拉取元素,此處需要遞歸驗證,這里暫時不管
203             //!!!!!!!!此處不嚴謹
204             var el = $(e.target).parent(), pos;
205             if (el.hasClass(this.scrollClass)) {
206                 this.touchTime = e.timeStamp;
207                 //獲取鼠標信息
208                 pos = this.getMousePos((e.changedTouches && e.changedTouches[0]) || e);
209                 //注意,此處是相對位置,注意該處還與動畫有關,所以高度必須動態計算
210                 //可以設置一個冷卻時間參數,但想想還是算了
211                 //最后還是使用了冷卻時間
212                 //最后的最后我還是決定使用動態樣式獲取算了
213                 var top = parseFloat(this.dragEl.css('top')) || 0;
214                 this.mouseY = pos.top - top;
215                 //                        this.mouseY = pos.top - this.curTop;
216                 this.moveAble = true;
217             }
218         },
219         touchMove: function (e) {
220             if (!this.moveAble) return false;
221             var pos = this.getMousePos((e.changedTouches && e.changedTouches[0]) || e);
222             //先獲取相對容器的位置,在將兩個鼠標位置相減
223             this.curTop = pos.top - this.mouseY;
224             this.dragEl.css('top', this.curTop + 'px');
225             e.preventDefault();
226         },
227         touchEnd: function (e) {
228             if (!this.moveAble) return false;
229             this.cooling = true; //開啟冷卻時間
230 
231             //時間間隔
232             var scope = this;
233             this.timeGap = e.timeStamp - this.touchTime;
234             var flag = this.oTop <= this.curTop ? 1 : -1; //判斷是向上還是向下滾動
235             var flag2 = this.curTop > 0 ? 1 : -1; //這個會影響后面的計算結果
236             this.moveState = flag > 0 ? 'up' : 'down';
237             var ih = parseFloat(this.itemHeight);
238             var ih1 = ih / 2;
239 
240             var top = Math.abs(this.curTop);
241             var mod = top % ih;
242             top = (parseInt(top / ih) * ih + (mod > ih1 ? ih : 0)) * flag2;
243             var step = parseInt(this.timeGap / 10 - 10);
244 
245             step = step > 0 ? step : 0;
246             var speed = this.animateParam[step] || 0;
247             var increment = speed * ih * flag;
248             top += increment;
249 
250             //!!!此處動畫可能導致數據不同步,后期改造需要加入冷卻時間
251             if (this.oTop != this.curTop && this.curTop != top) {
252                 this.dragEl.animate({
253                     top: top + 'px'
254                 }, 100 + (speed * 20), 'ease-out', function () {
255                     //                                scope.curTop = top;
256                     scope.reset.call(scope, top);
257                 });
258             } else {
259                 var item = this.dragEl.find('li');
260                 var el = $(e.target);
261                 item.removeClass('current');
262                 el.addClass('current');
263 
264                 //這個由於使用了邊距等東西,使用位置定位有點不靠譜了
265                 this.selectedIndex = el.attr('data-index');
266                 //單選多選列表觸發的事件,反正都會觸發
267                 this.type == 'list' && this.onTouchEnd();
268                 this.cooling = false; //關閉冷卻時間
269             }
270             this.moveAble = false;
271             e.preventDefault();
272 
273         },
274         //超出限制后位置還原
275         reset: function (top) {
276             var scope = this;
277             var num = parseInt(scope.type == 'list' ? 0 : scope.disItemNum / 2);
278             var _top = top, t = false;
279 
280             var sHeight = scope.type == 'list' ? 0 : parseFloat(scope.itemHeight) * num;
281             var eHeight = scope.type == 'list' ? scope.setHeight : parseFloat(scope.itemHeight) * (num + 1);
282             var h = this.dragHeight;
283 
284             if (top >= 0) {
285                 if (top > sHeight) {
286                     _top = sHeight;
287                     t = true;
288                 } else {
289                     //出現該情況說明項目太少,達不到一半
290                     if (h <= sHeight) {
291                         _top = sHeight - scope.itemHeight * (this.size - 1);
292                         t = true;
293                     }
294                 }
295             }
296             if (top < 0 && (top + scope.dragHeight <= eHeight)) {
297                 t = true;
298                 _top = (scope.dragHeight - eHeight) * (-1);
299             }
300             if (top == _top) {
301                 t = false;
302             }
303             if (t) {
304                 scope.dragEl.animate({
305                     top: _top + 'px'
306                 }, 50, 'ease-in-out', function () {
307                     scope.oTop = _top;
308                     scope.curTop = _top;
309                     scope.cooling = false; //關閉冷卻時間
310                     //單選時候的change事件
311                     scope.type == 'radio' && scope.onTouchEnd();
312                 });
313             } else {
314                 scope.oTop = top;
315                 scope.curTop = top;
316                 //單選時候的change事件
317                 scope.type == 'radio' && scope.onTouchEnd();
318             }
319             scope.cooling = false; //關閉冷卻時間
320         },
321         onTouchEnd: function (scope) {
322             scope = scope || this;
323 
324             var secItem, i, len, index, isFind;
325             var changed = this._changed;
326             var num = parseInt(this.type == 'list' ? 0 : this.disItemNum / 2);
327             len = this.data.length;
328             if (this.type == 'radio') {
329                 i = parseInt((this.curTop - this.itemHeight * num) / parseFloat(this.itemHeight));
330                 this.selectedIndex = Math.abs(i);
331                 secItem = this.data[this.selectedIndex];
332             } else {
333                 secItem = this.data[this.selectedIndex];
334             }
335 
336             //默認不去找
337             isFind = false; //檢測是否找到可選項
338             //檢測是否當前項不可選,若是不可選,需要還原到最近一個可選項
339             if (typeof secItem.disabled != 'undefined' && secItem.disabled == false) {
340                 index = this.selectedIndex;
341                 //先向上計算
342                 if (this.moveState == 'up') {
343                     for (i = index; i != 0; i--) {
344                         if (typeof this.data[i].disabled == 'undefined' || this.data[i].disabled == true) {
345                             index = i;
346                             isFind = true;
347                             break;
348                         }
349                     }
350                     if (isFind == false) {
351                         for (i = index; i < len; i++) {
352                             if (typeof this.data[i].disabled == 'undefined' || this.data[i].disabled == true) {
353                                 index = i;
354                                 isFind = true;
355                                 break;
356                             }
357                         }
358                     }
359                 } else {
360                     for (i = index; i < len; i++) {
361                         if (typeof this.data[i].disabled == 'undefined' || this.data[i].disabled == true) {
362                             index = i;
363                             isFind = true;
364                             break;
365                         }
366                     }
367                     if (isFind == false) {
368                         for (i = index; i != 0; i--) {
369                             if (typeof this.data[i].disabled == 'undefined' || this.data[i].disabled == true) {
370                                 index = i;
371                                 isFind = true;
372                                 break;
373                             }
374                         }
375                     }
376                 }
377             }
378 
379             //會有還原的邏輯
380             if (isFind) {
381                 this.selectedIndex = index;
382                 this.setIndex(index);
383             } else {
384                 var changed = this._changed;
385                 if (changed && typeof changed == 'function') {
386                     changed.call(scope, secItem);
387                 }
388             }
389         },
390         //數據重新加載
391         reload: function (data) {
392 
393             this.data = data;
394             this.dragEl.html('');
395             if (data.constructor == Array && data.length > 0) {
396                 this.selectedIndex = parseInt(this.disItemNum / 2); //暫時不考慮多選的情況
397                 this.selectedIndex = this.selectedIndex > this.data.length ? this.data.length - 1 : this.selectedIndex;
398                 this.initItem();
399                 this.initEventParam();
400                 this.cooling = false;
401                 this.setIndex(this.selectedIndex, true);
402             }
403         },
404         setKey: function (k) {
405             if (k == undefined || k == null) return false;
406             var i = this.dataK[k] && this.dataK[k].index;
407             this.setIndex(i);
408         },
409         setIndex: function (i, init) {
410             if (i == undefined || i < 0) return false;
411             var scope = this;
412             //                    this.cooling = true; //關閉冷卻時間
413             var num = parseInt(scope.disItemNum / 2);
414 
415             if (scope.type == 'list') {
416                 num = i == 0 ? 0 : 1;
417             }
418 
419             var i = parseInt(i), top;
420             if (i < 0) return false;
421             if (i >= this.data.length) i = this.data.length - 1;
422             this.selectedIndex = i;
423             top = (i * this.itemHeight * (-1) + this.itemHeight * num);
424 
425             //防止設置失敗
426             scope.oTop = top;
427             scope.curTop = top;
428             scope.cooling = false; //關閉冷卻時間
429             //            scope.dragEl.css('top', top + 'px');
430 
431             scope.dragEl.animate({ 'top': top + 'px' }, 50, 'ease-in-out');
432 
433 
434             if (scope.type == 'list') {
435                 var item = scope.dragEl.find('li');
436                 item.removeClass('current');
437                 item.eq(i).addClass('current');
438             }
439             //初始化dom選項時不觸發事件
440             if (!init) {
441                 //單選時候的change事件
442                 scope.onTouchEnd();
443             }
444         },
445         getSelected: function () {
446             return this.data[this.selectedIndex];
447         },
448         getByKey: function (k) {
449             var i = this.dataK[k] && this.dataK[k].index;
450             if (i != null && i != undefined)
451                 return this.data[i];
452             return null;
453         },
454         //獲取鼠標信息
455         getMousePos: function (event) {
456             var top, left;
457             top = Math.max(document.body.scrollTop, document.documentElement.scrollTop);
458             left = Math.max(document.body.scrollLeft, document.documentElement.scrollLeft);
459             return {
460                 top: top + event.clientY,
461                 left: left + event.clientX
462             };
463         }
464     };
465     return ScrollList;
View Code

請使用手機/或者使用chrome開啟touch功能查看,最新js代碼已處理兼容性問題

http://sandbox.runjs.cn/show/prii13pm

總結

代碼沒來得及重構,各位將就下吧,接下來進入我們的重構學習!

重構第一步

簡單程序

原來作者使用java寫的,我這里用js實現可能有所不同,如果有問題請提出

首先我們跟着學習第一個例子,實例據說比較簡單,是一個影片出租店用的程序,計算每一個顧客的消費金額並打印詳情。

操作者告訴程序,影片分為三類:普通片/兒童片/租期多長,程序便根據租賃時間和影片類型計算費用,並且為常客計算積分

PS:然后作者畫了個圖,我們不去管他

Movie(影片)

 1 //影片,單純的數據類
 2 var Movie = function (title, priceCode) {
 3     this._title = title;
 4     this._priceCode = priceCode;
 5 
 6 };
 7 Movie.CHILDRENS = 2;
 8 Movie.REGULAR = 0;
 9 Movie.NEW_RELEASE = 1;
10 
11 Movie.prototype = {
12     constructor: Movie,
13     getPriceCode: function () {
14         return this._priceCode;
15     },
16     setPriceCode: function (arg) {
17         this._priceCode = arg;
18     },
19     getTitle: function () {
20         return this._title;
21     }
22 };

租賃

 1 //租賃
 2 var Rental = function (movie, daysRented) {
 3     this._movie = movie;
 4     this._daysRented = daysRented;
 5 };
 6 
 7 Rental.prototype = {
 8     constructor: Rental,
 9     getDaysRented: function () {
10         return this._daysRented;
11     },
12     getMovie: function () {
13         return this._movie;
14     }
15 };

顧客

PS:這里用到了Vector,但是我們用數組代替吧

 1 var Customer = function (name) {
 2     this._name = name;
 3     this._rentals = [];
 4 };
 5 Customer.prototype = {
 6     constructor: Customer,
 7     addRental: function (arg) {
 8         //加入的是一個rental實例
 9         this._rentals.push(arg);
10 
11     },
12     getName: function () {
13         return this._name;
14     },
15     //生成詳細訂單的函數,並擁有交互代碼
16     statement: function () {
17         var totalAmount = 0,
18         //積分
19             frequentRenterPoints = 0,
20         //原文為枚舉類型
21             rentals = this._rentals,
22             result = 'rental record for ' + this.getName() + '\n';
23 
24         var i,
25             thisAmount = 0,
26             each = null,
27 
28             len = rentals.length;
29 
30         //PS:尼瑪兩年不搞java了,這里居然有點讀不懂了。。。
31         //這里大概是要遍歷rentals的意思,所以代碼我給變了點
32         for (i = 0; i < len; i++) {
33             thisAmount = 0;
34             each = rentals[i];
35             switch (each.getMovie().getPriceCode()) {
36                 case Movie.REGULAR:
37                     thisAmount += 2;
38                     if (each.getDaysRented() > 2) thisAmount += (each.getDaysRented() - 2) * 1.5;
39                     break;
40                 case Movie.NEW_RELEASE:
41                     thisAmount += each.getDaysRented() * 3;
42                     break;
43                 case Movie.CHILDRENS:
44                     thisAmount += 1.5;
45                     if (each.getDaysRented() > 3) thisAmount += (each.getDaysRented() - 3) * 1.5;
46                     break;
47             }
48             frequentRenterPoints++;
49             if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) && each.getDaysRented() > 1) frequentRenterPoints++;
50 
51             result += each.getMovie().getTitle() + ':' + thisAmount + '\n';
52             totalAmount += thisAmount;
53         }
54         result += 'amount owed is ' + thisAmount + '\n';
55         result += 'you earned ' + frequentRenterPoints;
56         return result;
57     }
58 };

先試試程序吧

 1 //此處先做一個例子試試吧
 2 var m1 = new Movie('刀戟戡魔錄', 0);
 3 var m2 = new Movie('霹靂神州', 1);
 4 var m3 = new Movie('開疆記', 2);
 5 
 6 var r1 = new Rental(m1, 1);
 7 var r2 = new Rental(m2, 2);
 8 var r3 = new Rental(m3, 3);
 9 
10 var y = new Customer('葉小釵');
11 
12 y.addRental(r1);
13 y.addRental(r2);
14 y.addRental(r3);
15 
16 alert(y.statement());

程序總結

PS:這里完全就算調用作者的話了,老夫到此除了認識到對java忘得差不多了,沒有其他感受......

該程序具有以下特點:

① 不符合面向對象精神

② statement過長(這個我是真的感覺很長,我打了很久字)

③ 擴展性差

以上如果用戶希望對系統做一點修改,比如希望用html輸出,我們就發現statement整個就是一個2B了,於是我們一般會復雜粘貼一番(趕時間的情況至少我會這么做)

這樣一來也許多了一個htmlStatement的函數,但是大量重復的代碼,我是不能接受的,以下是一個因素:

如果計費標准發生變化了我們就需要修改代碼!而且是維護兩端代碼(讀到這,老夫感受很深啊),所以這里還可能帶來潛在威脅哦!

於是現在來了第二個變化:

用戶希望改變影片分類規則,但又不知道怎么改,他設想了幾種方案,這些方案都會影響計算方式,那么又應該如何呢??

PS:尼瑪這簡直是我們工作真正的寫照啊!老板/產品 想要一個方案,但是又不知道想要神馬!於是我們一般說的是這個不能實現(其實我們知道是可以實現的)

綜上,你知道為什么要重構了嗎?

至於你知不知道,反正我知道了。。。。。。

分解重組

測試

開始之前,作者大力強調了一下測試與建立單元測試的重要性,而且第四章會講,我這里先不糾結啦:)

分解重組statement

第一步,我們需要將長得離譜的statement干掉,代碼越小越簡單,代碼越小越少BUG

於是我們首先要找出代碼的邏輯泥團,並運用extract method,至於本例,邏輯泥團就是switch語句,我們將它提煉成單獨的函數

我們提煉一個函數時,我們要知道自己可能出什么錯,提煉不好就可能引入BUG

PS:這種情況也經常在工作中出現,我改一個BUG,結果由於新的代碼引起其它BUG!!!

提煉函數

找出在代碼中的局部變量,這里是each與thisAmount,前者未變,后者會變

任何不會改變的變量都可以被當成參數傳入新的函數,至於需要改變的變量就需要格外小心
如果只有一個變量會被修改,我們可以將它作為返回值

thisAmount是個臨時變量,每次循環都會被初始化為0 ,並且在switch以前不會被修改,所以我們可以將它作為返回值使用

重構的代碼

 1 statement: function () {
 2     var totalAmount = 0,
 3     //積分
 4         frequentRenterPoints = 0,
 5     //原文為枚舉類型
 6         rentals = this._rentals,
 7         result = 'rental record for ' + this.getName() + '\n';
 8 
 9     var i,
10         thisAmount = 0,
11         each = null,
12 
13         len = rentals.length;
14 
15     //PS:尼瑪兩年不搞java了,這里居然有點讀不懂了。。。
16     //這里大概是要遍歷rentals的意思,所以代碼我給變了點
17     for (i = 0; i < len; i++) {
18         thisAmount = 0;
19         each = rentals[i];
20         thisAmount = this._amountFor(each); 21         frequentRenterPoints++;
22         if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) && each.getDaysRented() > 1) frequentRenterPoints++;
23 
24         result += each.getMovie().getTitle() + ':' + thisAmount + '\n';
25         totalAmount += thisAmount;
26     }
27     result += 'amount owed is ' + thisAmount + '\n';
28     result += 'you earned ' + frequentRenterPoints;
29     return result;
30 },
31 _amountFor: function (each) {
32     var thisAmount = 0; 33     switch (each.getMovie().getPriceCode()) {
34         case Movie.REGULAR:
35             thisAmount += 2;
36             if (each.getDaysRented() > 2) thisAmount += (each.getDaysRented() - 2) * 1.5;
37             break;
38         case Movie.NEW_RELEASE:
39             thisAmount += each.getDaysRented() * 3;
40             break;
41         case Movie.CHILDRENS:
42             thisAmount += 1.5;
43             if (each.getDaysRented() > 3) thisAmount += (each.getDaysRented() - 3) * 1.5;
44             break;
45     }
46     return thisAmount; 47 }

這里雖說只是做了一點改變,但是明顯代碼質量有所提升,然后內部的變量名也可以改變,比如:

① each => rental

② thisAmount => result

_amountFor: function (rental) {
    var result = 0;
    switch (rental.getMovie().getPriceCode()) {
        case Movie.REGULAR:
            result += 2;
            if (rental.getDaysRented() > 2) result += (rental.getDaysRented() - 2) * 1.5;
            break;
        case Movie.NEW_RELEASE:
            result += rental.getDaysRented() * 3;
            break;
        case Movie.CHILDRENS:
            result += 1.5;
            if (rental.getDaysRented() > 3) result += (rental.getDaysRented() - 3) * 1.5;
            break;
    }
    return result;
}
View Code

搬移“計算”代碼

觀察amountFor時,我們發現此處具有rental的信息,卻沒有customer的信息,所以這里有一個問題:

絕大多數情況,函數應該放在他使用的數據的所屬對象內

所以amountFor其實應該放到rental中去,為了適應變化,就得去掉參數,並且我們這里講函數名一並更改了

這里貼出完整的代碼,各位自己看看

var Movie = function (title, priceCode) {
    this._title = title;
    this._priceCode = priceCode;

};
Movie.CHILDRENS = 2;
Movie.REGULAR = 0;
Movie.NEW_RELEASE = 1;

Movie.prototype = {
    constructor: Movie,
    getPriceCode: function () {
        return this._priceCode;
    },
    setPriceCode: function (arg) {
        this._priceCode = arg;
    },
    getTitle: function () {
        return this._title;
    }
};

//租賃
var Rental = function (movie, daysRented) {
    this._movie = movie;
    this._daysRented = daysRented;
};

Rental.prototype = {
    constructor: Rental,
    getDaysRented: function () {
        return this._daysRented;
    },
    getMovie: function () {
        return this._movie;
    },
    getChange: function () {
        var result = 0;
        switch (this.getMovie().getPriceCode()) {
            case Movie.REGULAR:
                result += 2;
                if (this.getDaysRented() > 2) result += (this.getDaysRented() - 2) * 1.5;
                break;
            case Movie.NEW_RELEASE:
                result += this.getDaysRented() * 3;
                break;
            case Movie.CHILDRENS:
                result += 1.5;
                if (this.getDaysRented() > 3) result += (this.getDaysRented() - 3) * 1.5;
                break;
        }
        return result;
    }
};

//顧客
var Customer = function (name) {
    this._name = name;
    this._rentals = [];
};
Customer.prototype = {
    constructor: Customer,
    addRental: function (arg) {
        //加入的是一個rental實例
        this._rentals.push(arg);

    },
    getName: function () {
        return this._name;
    },
    //生成詳細訂單的函數,並擁有交互代碼
    statement: function () {
        var totalAmount = 0,
        //積分
        frequentRenterPoints = 0,
        //原文為枚舉類型
        rentals = this._rentals,
        result = 'rental record for ' + this.getName() + '\n';

        var i,
        thisAmount = 0,
        each = null,

        len = rentals.length;

        //PS:尼瑪兩年不搞java了,這里居然有點讀不懂了。。。
        //這里大概是要遍歷rentals的意思,所以代碼我給變了點
        for (i = 0; i < len; i++) {
            thisAmount = 0;
            each = rentals[i];
            thisAmount = this._amountFor(each);
            frequentRenterPoints++;
            if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) && each.getDaysRented() > 1) frequentRenterPoints++;

            result += each.getMovie().getTitle() + ':' + thisAmount + '\n';
            totalAmount += thisAmount;
        }
        result += 'amount owed is ' + thisAmount + '\n';
        result += 'you earned ' + frequentRenterPoints;
        return result;
    },
    _amountFor: function (rental) {
        return rental.getChange();
    }
};

//此處先做一個例子試試吧
var m1 = new Movie('刀戟戡魔錄', 0);
var m2 = new Movie('霹靂神州', 1);
var m3 = new Movie('開疆記', 2);

var r1 = new Rental(m1, 1);
var r2 = new Rental(m2, 2);
var r3 = new Rental(m3, 3);

var y = new Customer('葉小釵');

y.addRental(r1);
y.addRental(r2);
y.addRental(r3);

alert(y.statement());
View Code
 1 Rental.prototype = {
 2     constructor: Rental,
 3     getDaysRented: function () {
 4         return this._daysRented;
 5     },
 6     getMovie: function () {
 7         return this._movie;
 8     },
 9     getChange: function () {
10         var result = 0;
11         switch (this.getMovie().getPriceCode()) {
12             case Movie.REGULAR:
13                 result += 2;
14                 if (this.getDaysRented() > 2) result += (this.getDaysRented() - 2) * 1.5;
15                 break;
16             case Movie.NEW_RELEASE:
17                 result += this.getDaysRented() * 3;
18                 break;
19             case Movie.CHILDRENS:
20                 result += 1.5;
21                 if (this.getDaysRented() > 3) result += (this.getDaysRented() - 3) * 1.5;
22                 break;
23         }
24         return result;
25     }
26 };
27 
28 //顧客
29 var Customer = function (name) {
30     this._name = name;
31     this._rentals = [];
32 };
33 Customer.prototype = {
34     constructor: Customer,
35     addRental: function (arg) {
36         //加入的是一個rental實例
37         this._rentals.push(arg);
38 
39     },
40     getName: function () {
41         return this._name;
42     },
43     //生成詳細訂單的函數,並擁有交互代碼
44     statement: function () {
45         var totalAmount = 0,
46         //積分
47         frequentRenterPoints = 0,
48         //原文為枚舉類型
49         rentals = this._rentals,
50         result = 'rental record for ' + this.getName() + '\n';
51 
52         var i,
53         thisAmount = 0,
54         each = null,
55 
56         len = rentals.length;
57 
58         //PS:尼瑪兩年不搞java了,這里居然有點讀不懂了。。。
59         //這里大概是要遍歷rentals的意思,所以代碼我給變了點
60         for (i = 0; i < len; i++) {
61             thisAmount = 0;
62             each = rentals[i];
63             thisAmount = each.getChange();
64             frequentRenterPoints++;
65             if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) && each.getDaysRented() > 1) frequentRenterPoints++;
66 
67             result += each.getMovie().getTitle() + ':' + thisAmount + '\n';
68             totalAmount += thisAmount;
69         }
70         result += 'amount owed is ' + thisAmount + '\n';
71         result += 'you earned ' + frequentRenterPoints;
72         return result;
73     }
74 };

去除多余變量

於是,現在statement中就有一些多余的變量了:this.Amount,因為他完全等於each.getCharge()

於是乎,去掉吧:

 1 statement: function () {
 2     var totalAmount = 0,
 3     //積分
 4     frequentRenterPoints = 0,
 5     //原文為枚舉類型
 6     rentals = this._rentals,
 7     result = 'rental record for ' + this.getName() + '\n';
 8 
 9     var i,
10     each = null,
11     len = rentals.length;
12     //PS:尼瑪兩年不搞java了,這里居然有點讀不懂了。。。
13     //這里大概是要遍歷rentals的意思,所以代碼我給變了點
14     for (i = 0; i < len; i++) {
15         each = rentals[i];
16         frequentRenterPoints++;
17         if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) && each.getDaysRented() > 1) frequentRenterPoints++;
18 
19         result += each.getMovie().getTitle() + ':' + each.getChange() + '\n';
20         totalAmount += each.getChange();
21     }
22     result += 'amount owed is ' + totalAmount + '\n';
23     result += 'you earned ' + frequentRenterPoints;
24     return result;
25 }

提煉“常客積分”計算

下面開始對常客積分計算進行處理,積分的計算因為種類而有所不同,看來有理由把積分計算的責任放入rental

var Movie = function (title, priceCode) {
    this._title = title;
    this._priceCode = priceCode;

};
Movie.CHILDRENS = 2;
Movie.REGULAR = 0;
Movie.NEW_RELEASE = 1;

Movie.prototype = {
    constructor: Movie,
    getPriceCode: function () {
        return this._priceCode;
    },
    setPriceCode: function (arg) {
        this._priceCode = arg;
    },
    getTitle: function () {
        return this._title;
    }
};

//租賃
var Rental = function (movie, daysRented) {
    this._movie = movie;
    this._daysRented = daysRented;
};

Rental.prototype = {
    constructor: Rental,
    getDaysRented: function () {
        return this._daysRented;
    },
    getMovie: function () {
        return this._movie;
    },
    getChange: function () {
        var result = 0;
        switch (this.getMovie().getPriceCode()) {
            case Movie.REGULAR:
                result += 2;
                if (this.getDaysRented() > 2) result += (this.getDaysRented() - 2) * 1.5;
                break;
            case Movie.NEW_RELEASE:
                result += this.getDaysRented() * 3;
                break;
            case Movie.CHILDRENS:
                result += 1.5;
                if (this.getDaysRented() > 3) result += (this.getDaysRented() - 3) * 1.5;
                break;
        }
        return result;
    },
    getFrequentRenterPoints: function () {
        if ((this.getMovie().getPriceCode() == Movie.NEW_RELEASE) && this.getDaysRented() > 1) return 2;
        else return 1;
    }
};

//顧客
var Customer = function (name) {
    this._name = name;
    this._rentals = [];
};
Customer.prototype = {
    constructor: Customer,
    addRental: function (arg) {
        //加入的是一個rental實例
        this._rentals.push(arg);

    },
    getName: function () {
        return this._name;
    },
    //生成詳細訂單的函數,並擁有交互代碼
    statement: function () {
        var totalAmount = 0,
        //積分
    frequentRenterPoints = 0,
        //原文為枚舉類型
    rentals = this._rentals,
    result = 'rental record for ' + this.getName() + '\n';

        var i,
    each = null,
    len = rentals.length;
        //PS:尼瑪兩年不搞java了,這里居然有點讀不懂了。。。
        //這里大概是要遍歷rentals的意思,所以代碼我給變了點
        for (i = 0; i < len; i++) {
            each = rentals[i];
            /*
            重構掉的
            frequentRenterPoints++;
            if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) && each.getDaysRented() > 1) frequentRenterPoints++;
            */
            frequentRenterPoints += each.getFrequentRenterPoints();

            result += each.getMovie().getTitle() + ':' + each.getChange() + '\n';
            totalAmount += each.getChange();
        }
        result += 'amount owed is ' + each.getChange() + '\n';
        result += 'you earned ' + frequentRenterPoints;
        return result;
    }
};

//此處先做一個例子試試吧
var m1 = new Movie('刀戟戡魔錄', 0);
var m2 = new Movie('霹靂神州', 1);
var m3 = new Movie('開疆記', 2);

var r1 = new Rental(m1, 1);
var r2 = new Rental(m2, 2);
var r3 = new Rental(m3, 3);

var y = new Customer('葉小釵');

y.addRental(r1);
y.addRental(r2);
y.addRental(r3);

alert(y.statement());
View Code

PS:由於篇幅較長,我就不像上面一一標注改變啦

下面再去除一點臨時變量:totalAmount

PS:但是,這里會多一次循環,到底哪個好,我也不知道了,多一個循環應該方便后面擴展吧,感覺作者要消滅所有臨時變量啦

去除totalAmount/frequentRenterPoints

 1 var Customer = function (name) {
 2     this._name = name;
 3     this._rentals = [];
 4 };
 5 Customer.prototype = {
 6     constructor: Customer,
 7     addRental: function (arg) {
 8         //加入的是一個rental實例
 9         this._rentals.push(arg);
10 
11     },
12     getName: function () {
13         return this._name;
14     },
15     //生成詳細訂單的函數,並擁有交互代碼
16     statement: function () {
17         var each = null, result = '';
18         for (var i = 0, len = this._rentals.length; i < len; i++) {
19             each = this._rentals[i];
20             result += each.getMovie().getTitle() + ':' + each.getChange() + '\n';
21         }
22         result += 'amount owed is ' + this.getTotal() + '\n';
23         result += 'you earned ' + this.getTotalFrequentRenterPoints();
24         return result;
25     },
26     getTotal: function () {
27         var result = 0, each = null;
28         for (var i = 0, len = this._rentals.length; i < len; i++) {
29             each = this._rentals[i];
30             result += each.getChange();
31         }
32         return result;
33     },
34     getTotalFrequentRenterPoints: function () {
35         var result = 0, each = null;
36         for (var i = 0, len = this._rentals.length; i < len; i++) {
37             each = this._rentals[i];
38             result += each.getFrequentRenterPoints();
39         }
40         return result;
41     }
42 };

請各位仔細看,到這里我們的程序已經變話了許多了!!!你還記得最初的statement嗎?

階段總結

可以看到,我們這次重構沒有減少代碼,反而加了很多代碼!而且還可能多了些循環呢!所以這次重構的結果是:

① 代碼易讀性提高

② 分離了statement

③ 代碼增多

④ 性能降低

在此看來,可能因為1,2我們便不做重構了,但是

不能因為:
① 重構增加了代碼量
② 重構降低了性能
而不做重構,因為重構完成前,這些只是你的一廂情願

添加htmlStatement

 1 htmlStatement: function () {
 2     var each = null,
 3     result = '<h1>rental record for ' + this.getName() + '</h1>';
 4     for (var i = 0, len = this._rentals.length; i < len; i++) {
 5         each = this._rentals[i];
 6         result += each.getMovie().getTitle() + ':' + each.getChange() + '<br/>';
 7     }
 8     result += 'amount owed is ' + this.getTotal() + '<br/>';
 9     result += 'you earned ' + this.getTotalFrequentRenterPoints();
10     return result;
11 },

多態與if

好了,用戶提出新需求了,需要修改分類規則。

這里我們又重新回到了我們的switch語句,我其實一般不使用switch語句,作者說最好不要在另一個對象屬性繼承上運用switch語句,要用也要在自己的數據上,而我基本不用。。。。。。

所以第一步,我們是將getCharge放入Movie中

getCharge搬家

PS:我怕好像將getCharge寫錯了。。。。。。

  1 var Movie = function (title, priceCode) {
  2     this._title = title;
  3     this._priceCode = priceCode;
  4 
  5 };
  6 Movie.CHILDRENS = 2;
  7 Movie.REGULAR = 0;
  8 Movie.NEW_RELEASE = 1;
  9 
 10 Movie.prototype = {
 11     constructor: Movie,
 12     getPriceCode: function () {
 13         return this._priceCode;
 14     },
 15     setPriceCode: function (arg) {
 16         this._priceCode = arg;
 17     },
 18     getTitle: function () {
 19         return this._title;
 20     },
 21     getCharge: function (daysRented) {
 22         var result = 0;
 23         switch (this.getPriceCode()) {
 24             case Movie.REGULAR:
 25                 result += 2;
 26                 if (daysRented > 2) result += (daysRented - 2) * 1.5;
 27                 break;
 28             case Movie.NEW_RELEASE:
 29                 result += daysRented * 3;
 30                 break;
 31             case Movie.CHILDRENS:
 32                 result += 1.5;
 33                 if (daysRented > 3) result += (daysRented - 3) * 1.5;
 34                 break;
 35         }
 36         return result;
 37     }
 38 };
 39 
 40 //租賃
 41 var Rental = function (movie, daysRented) {
 42     this._movie = movie;
 43     this._daysRented = daysRented;
 44 };
 45 
 46 Rental.prototype = {
 47     constructor: Rental,
 48     getDaysRented: function () {
 49         return this._daysRented;
 50     },
 51     getMovie: function () {
 52         return this._movie;
 53     },
 54     getCharge: function () {
 55         return this.getMovie().getCharge(this.getDaysRented());
 56     },
 57     getFrequentRenterPoints: function () {
 58         if ((this.getMovie().getPriceCode() == Movie.NEW_RELEASE) && this.getDaysRented() > 1) return 2;
 59         else return 1;
 60     }
 61 };
 62 
 63 //顧客
 64 var Customer = function (name) {
 65     this._name = name;
 66     this._rentals = [];
 67 };
 68 Customer.prototype = {
 69     constructor: Customer,
 70     addRental: function (arg) {
 71         //加入的是一個rental實例
 72         this._rentals.push(arg);
 73 
 74     },
 75     getName: function () {
 76         return this._name;
 77     },
 78     //生成詳細訂單的函數,並擁有交互代碼
 79     statement: function () {
 80         var each = null,
 81         result = 'rental record for ' + this.getName() + '\n';
 82         for (var i = 0, len = this._rentals.length; i < len; i++) {
 83             each = this._rentals[i];
 84             result += each.getMovie().getTitle() + ':' + each.getCharge() + '\n';
 85         }
 86         result += 'amount owed is ' + this.getTotal() + '\n';
 87         result += 'you earned ' + this.getTotalFrequentRenterPoints();
 88         return result;
 89     },
 90 htmlStatement: function () {
 91     var each = null,
 92     result = '<h1>rental record for ' + this.getName() + '</h1>';
 93     for (var i = 0, len = this._rentals.length; i < len; i++) {
 94         each = this._rentals[i];
 95         result += each.getMovie().getTitle() + ':' + each.getCharge() + '<br/>';
 96     }
 97     result += 'amount owed is ' + this.getTotal() + '<br/>';
 98     result += 'you earned ' + this.getTotalFrequentRenterPoints();
 99     return result;
100 },
101     getTotal: function () {
102         var result = 0, each = null;
103         for (var i = 0, len = this._rentals.length; i < len; i++) {
104             each = this._rentals[i];
105             result += each.getCharge();
106         }
107         return result;
108     },
109     getTotalFrequentRenterPoints: function () {
110         var result = 0, each = null;
111         for (var i = 0, len = this._rentals.length; i < len; i++) {
112             each = this._rentals[i];
113             result += each.getFrequentRenterPoints();
114         }
115         return result;
116     }
117 };
View Code

Movie

 1     getCharge: function (daysRented) {
 2         var result = 0;
 3         switch (this.getPriceCode()) {
 4             case Movie.REGULAR:
 5                 result += 2;
 6                 if (daysRented > 2) result += (daysRented - 2) * 1.5;
 7                 break;
 8             case Movie.NEW_RELEASE:
 9                 result += daysRented * 3;
10                 break;
11             case Movie.CHILDRENS:
12                 result += 1.5;
13                 if (daysRented > 3) result += (daysRented - 3) * 1.5;
14                 break;
15         }
16         return result;
17     }

Rental

1     getCharge: function () {
2         return this.getMovie().getCharge(this.getDaysRented());
3     },

getFrequentRenterPoints采用同樣方法處理

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title></title>
    <script type="text/javascript">
//影片,單純的數據類
var Movie = function (title, priceCode) {
    this._title = title;
    this._priceCode = priceCode;

};
Movie.CHILDRENS = 2;
Movie.REGULAR = 0;
Movie.NEW_RELEASE = 1;

Movie.prototype = {
    constructor: Movie,
    getPriceCode: function () {
        return this._priceCode;
    },
    setPriceCode: function (arg) {
        this._priceCode = arg;
    },
    getTitle: function () {
        return this._title;
    },
    getCharge: function (daysRented) {
        var result = 0;
        switch (this.getPriceCode()) {
            case Movie.REGULAR:
                result += 2;
                if (daysRented > 2) result += (daysRented - 2) * 1.5;
                break;
            case Movie.NEW_RELEASE:
                result += daysRented * 3;
                break;
            case Movie.CHILDRENS:
                result += 1.5;
                if (daysRented > 3) result += (daysRented - 3) * 1.5;
                break;
        }
        return result;
    },
    getFrequentRenterPoints: function (daysRented) {
        if ((this.getPriceCode() == Movie.NEW_RELEASE) && daysRented > 1) return 2;
        else return 1;
    }
};

//租賃
var Rental = function (movie, daysRented) {
    this._movie = movie;
    this._daysRented = daysRented;
};

Rental.prototype = {
    constructor: Rental,
    getDaysRented: function () {
        return this._daysRented;
    },
    getMovie: function () {
        return this._movie;
    },
    getCharge: function () {
        return this.getMovie().getCharge(this.getDaysRented());
    },
    getFrequentRenterPoints: function () {
        return this.getMovie().getFrequentRenterPoints(this.getDaysRented());
    }
};

//顧客
var Customer = function (name) {
    this._name = name;
    this._rentals = [];
};
Customer.prototype = {
    constructor: Customer,
    addRental: function (arg) {
        //加入的是一個rental實例
        this._rentals.push(arg);

    },
    getName: function () {
        return this._name;
    },
    //生成詳細訂單的函數,並擁有交互代碼
    statement: function () {
        var each = null,
        result = 'rental record for ' + this.getName() + '\n';
        for (var i = 0, len = this._rentals.length; i < len; i++) {
            each = this._rentals[i];
            result += each.getMovie().getTitle() + '' + each.getCharge() + '\n';
        }
        result += 'amount owed is ' + this.getTotal() + '\n';
        result += 'you earned ' + this.getTotalFrequentRenterPoints();
        return result;
    },
htmlStatement: function () {
    var each = null,
    result = '<h1>rental record for ' + this.getName() + '</h1>';
    for (var i = 0, len = this._rentals.length; i < len; i++) {
        each = this._rentals[i];
        result += each.getMovie().getTitle() + '' + each.getCharge() + '<br/>';
    }
    result += 'amount owed is ' + this.getTotal() + '<br/>';
    result += 'you earned ' + this.getTotalFrequentRenterPoints();
    return result;
},
    getTotal: function () {
        var result = 0, each = null;
        for (var i = 0, len = this._rentals.length; i < len; i++) {
            each = this._rentals[i];
            result += each.getCharge();
        }
        return result;
    },
    getTotalFrequentRenterPoints: function () {
        var result = 0, each = null;
        for (var i = 0, len = this._rentals.length; i < len; i++) {
            each = this._rentals[i];
            result += each.getFrequentRenterPoints();
        }
        return result;
    }
};

//此處先做一個例子試試吧
var m1 = new Movie('刀戟戡魔錄', 0);
var m2 = new Movie('霹靂神州', 1);
var m3 = new Movie('開疆記', 2);

var r1 = new Rental(m1, 1);
var r2 = new Rental(m2, 2);
var r3 = new Rental(m3, 3);

var y = new Customer('葉小釵');

y.addRental(r1);
y.addRental(r2);
y.addRental(r3);
window.onload = function () {
    document.getElementById('d').innerHTML = y.htmlStatement();
};
    </script>
</head>
<body>
 <div id="d"></div>
</body>
</html>
View Code

PS:這里搞完了,我沒有發現和多態有太多關系的東西啦。。。。。。於是,繼續往下看吧

繼承

PS:這里要用到繼承,我們應該使用前面博客的方法,但是現在就隨便搞下吧

我們為Movie建立三個子類

ChildrenMovie RegularMovie NewReleseaMovie

PS:作者這里使用了抽象類神馬的,我思考下這里怎么寫......

 

 

 

 

 

 

結語

好了,今天的學習暫時到此,下次我們就真的開始系統學習重構知識了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM