【重构笔记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;  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() + '
';
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 + '
';
52             totalAmount += thisAmount;
53         }
54         result += 'amount owed is ' + thisAmount + '
';
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() + '
';
 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 + '
';
25         totalAmount += thisAmount;
26     }
27     result += 'amount owed is ' + thisAmount + '
';
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() + '
';

        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 + '
';
            totalAmount += thisAmount;
        }
        result += 'amount owed is ' + thisAmount + '
';
        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() + '
';
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 + '
';
68             totalAmount += thisAmount;
69         }
70         result += 'amount owed is ' + thisAmount + '
';
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() + '
';
 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() + '
';
20         totalAmount += each.getChange();
21     }
22     result += 'amount owed is ' + totalAmount + '
';
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() + '
';

        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() + '
';
            totalAmount += each.getChange();
        }
        result += 'amount owed is ' + each.getChange() + '
';
        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() + '
';
21         }
22         result += 'amount owed is ' + this.getTotal() + '
';
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() + '
';
 82         for (var i = 0, len = this._rentals.length; i < len; i++) {
 83             each = this._rentals[i];
 84             result += each.getMovie().getTitle() + ':' + each.getCharge() + '
';
 85         }
 86         result += 'amount owed is ' + this.getTotal() + '
';
 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() + '
';
        for (var i = 0, len = this._rentals.length; i < len; i++) {
            each = this._rentals[i];
            result += each.getMovie().getTitle() + '' + each.getCharge() + '
';
        }
        result += 'amount owed is ' + this.getTotal() + '
';
        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:作者这里使用了抽象类神马的,我思考下这里怎么写......

结语

好了,今天的学习暂时到此,下次我们就真的开始系统学习重构知识了。

原文地址:https://www.cnblogs.com/yexiaochai/p/3344213.html