• 【重构笔记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:作者这里使用了抽象类神马的,我思考下这里怎么写......

    结语

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

  • 相关阅读:
    Algs4-2.2.24-改进的有序测试
    Algs4-2.2.23-2比较正文中实现的归并和反向复制辅助数组归并之间的性能
    ssh登录卡住问题
    DELL R730安装ESXI虚拟化
    Linux umount的device is busy问题
    shell脚本调试技巧
    git编译安装
    卸载gitlab
    磁盘性能测试方法
    N! HDU 1042
  • 原文地址:https://www.cnblogs.com/yexiaochai/p/3344213.html
Copyright © 2020-2023  润新知