• 【重构笔记04】重新组织数据(2)


    将单项关联变为双向关联

    有时候,我们两个类需要使用对方属性,但期间只有一条单向连接
    此时可以添加一个反向指针,并修改关系函数使他能够同时更新两条连接

    开发初期,我们可能会在两个类之间建立一条单向连接,使其中一个类可以引用另一个类
    随着时间推移,我们发现被引用类需要得到其引用者才能进行某些处理,也就是说需要一个反向指针
    但是指针是一个单向连接,我们不能单向操作他,此时便可以建立双向引用关系了
    PS:指针好像对js不是很实用的说

    这个是我们以后会依赖的创建类的工具类

     1 var base = {};
     2 var slice = [].slice;
     3 var bind = function (scope, fun, args) {
     4 args = args || [];
     5 return function () {
     6 fun.apply(scope, args.concat(slice.call(arguments)));
     7 };
     8 };
     9 
    10 base.Class = function (supClass, childAttr) {
    11 //若是第一个是类,便是继承;如果第一个是对象,第二个参数无意义,便是新建一个类
    12 if (typeof supClass === 'object') {
    13 childAttr = supClass;
    14 supClass = function () { };
    15 }
    16 //新建临时类,最后作为新类返回,可能是继承可能是新类
    17 /***
    18 这里非常关键,为整个方法的入口,一定得看到初始化后,这里会执行构造函数
    19 ***/
    20 var newClass = function () {
    21 //每个类都会使用该函数,作为第一步初始化,告诉类有哪些属性
    22 this._propertys_ && this._propertys_();
    23 //第二步初始化,相当于子类的构造函数,比较重要,初始化方法不一定会出现
    24 this.init && this.init.apply(this, arguments);
    25 };
    26 //发生继承关系,可能为空类
    27 newClass.prototype = new supClass();
    28 
    29 //新建类必定会包含初始化函数,要么继承,如果没继承,这里也会新建
    30 var supInit = newClass.prototype.init || function () { };
    31 //传入的子对象可能包含他的初始化方法,如果有一定要使用,至于父类使用与否看子类心情
    32 var childInit = childAttr.init || function () { };
    33 //父类的properys方法便是指定会具有哪些属性,一定会执行
    34 var _supAttr = newClass.prototype._propertys_ || function () { };
    35 //子类的初始化也一定会触发,先执行父类再执行子类
    36 var _childAttr = childAttr._propertys_ || function () { };
    37 
    38 //为新建类(可能继承可能新建)初始化原型,上面的会重写,没有就不管他
    39 for (var k in childAttr) {
    40 childAttr.hasOwnProperty(k) && (newClass.prototype[k] = childAttr[k]);
    41 }
    42 
    43 //处理继承情况
    44 if (arguments.length && arguments[0].prototype && arguments[0].prototype.init === supInit) {
    45 //根据父类重写新建类构造时会用到的方法
    46 newClass.prototype.init = function () {
    47 var scope = this;
    48 var args = [function () {
    49 //第一个参数为父类的初始化函数,执行与否看子类心情
    50 supInit.apply(scope, arguments)
    51 } ];
    52 childInit.apply(scope, args.concat(slice.call(arguments)));
    53 };
    54 }
    55 //前面说到的,父类与子类的初始化方法一定会执行,先父后子
    56 newClass.prototype._propertys_ = function () {
    57 _supAttr.call(this);
    58 _childAttr.call(this);
    59 };
    60 
    61 //成员属性也得继承
    62 for (var k in supClass) {
    63 supClass.hasOwnProperty(k) && (newClass[k] = supClass[k]);
    64 }
    65 return newClass;
    66 };

    下面我们创建两个类,订单类Order与客户类Customer,其中Order引用了Customer,Customer没有引用Order

    var Order= base.Class({
    _propertys_: function () {
    this.customer = {};
    },
    getCustomer: function () {
    return this.customer;
    },
    setCustomer: function (arg) {
    this.customer = arg;
    }
    });
    var Customer = base.Class({
    _propertys_: function () {
    this.orders = {};
    }
    });

    现在我们需要决定哪一个类负责控制关联关系,我们这里让单个类来操控,因为这样就可以将所有处理关联关系的逻辑安置到一地:

    ① 如果两者都是引用对象,而其间的关联为“一对多”关系,那么就由拥有单一引用的那一方承担控制者角色
    本例中,一个客户可能拥有多个订单,就由Order承担控制者角色
    ② 如果某个对象是组成另一个对象的部件,那么由后者负责关联关系
    ③ 如果两者都是引用对象,其间是多对多关系,那么就随便了

    本例中,由于Order负责关联关系,所以我们为Customer添加一个辅助函数,让Order可以直接访问订单集合
    order的修改函数将使用这个辅助函数对指针两端对象进行同步控制
    现在,我们改变函数时候,需要同时更新反向指针

    var Order = base.Class({
    _propertys_: function () {
    this.customer = {};
    },
    getCustomer: function () {
    return this.customer;
    },
    setCustomer: function (arg) {
    if (this.customer != null) this.customer.friendOrders().remove(this);
    this.customer = arg;
    if (this.customer != null) this.customer.friendOrders().add(this);
    }
    });
    var Customer = base.Class({
    _propertys_: function () {
    this.orders = {};
    },
    friendOrders: function () {
    return this.orders;
    }
    });

    PS:这段代码我看的也蛋疼,以下是这段蛋疼代码的蛋疼解释

    类之间关系是各式各样的,因此修改函数的代码也会随之有所差异,如果_customer的值不是null,那么可以拿掉上述第一个null检查
    但仍然需要检查传入参数是否为null,不过基本形式总是相同:先让对方删除指向你的指针,再将你的指针指向一个新对象,最后将那个新对象指针给自己
    PS:这一段我没搞懂......
    将双向关联改为单项关联与上述相反,我这里就不管他了,反正也看不懂

    以字面常量取代魔法数

    我们有一个字面常量,并且带有特别含义
    那么创造一个常量,根据其意义为他命名,并将上述的字面数值替换为这个常量

    1 function potentialEnergy(mass, height) {
    2 return mass * 9.81 * height;
    3 }
    4 var GRAVITATIONAL = 9.81;
    5 function potentialEnergy(mass, height) {
    6 return mass * GRAVITATIONAL * height;
    7 }

    这个比较简单,我们直接跳过了

    封装集合

    有个函数返回一个集合,让这个函数返回该集合的一个只读副本,并在这个类中提供添加/移除集合的函数

    我们常常会在类中使用集合来保存一组实例,这样的类通常也会提供针对该集合的取值/设值函数
    但是,集合的处理方式应该和其它种类数据略有不同,取值函数不该返回集合自身,因为这会让用户得以修改集合内容而拥有者一无所知
    这样会对用户暴露过多的内部信息。
    如果一个取值函数确实需要返回多个值,他应该避免用户之间操作对象内所保存的集合,并隐藏对象内存与用户无关的数据结构
    另外,不应该为整个集合提供一个设值函数,但应该提供添加/移除函数,这样集合拥有者就可以控制集合元素的增删

    怎么做

    ① 加入为集合添加/删除元素的函数
    ② 将保存集合的字段初始化为一个空集合
    ③ 找出集合设值函数的所有调用者,可以修改那个设值函数,让他使用新增的添加删除函数,也可以直接修改调用端,改用添加/删除函数
    两种情况下需要用到集合设值函数:集合为空;准备将原有集合替换为另一集合
    ④ 找出所有通过取值函数获得集合并修改其内容的函数,逐一修改这些函数,将他们改用添加/删除函数
    ⑤ 修改完上述函数后,修改取值函数自身,使他返回集合的一个只读副本
    ⑥ 找出取值函数的所有用户,从中找出应该存于集合所属对象的代码,将这些代码移入宿主对象
    ⑦ 修改现有取值函数的名字,然后添加一个新取值函数,使其返回一个枚举,找出旧取值函数的所有被调用点改为新的

    这里来一个例子吧:
    假如有人要去上课,我们用一个简单的Course来表示课程
    我们不关心课程细节,我们关心表示人的Person

    var Course = base.Class({
    _propertys_: function () {
    this.customer = {};
    },
    init: function (name, isAdvanced) { },
    isAdvanced: function () { }
    });
    var Person = base.Class({
    _propertys_: function () {
    this.courses = {};
    },
    getCourses: function () {
    return this.courses;
    },
    setCourses: function (arg) {
    this.courses = arg;
    }
    });

    有了这个我们可以为某人添加课程

    var p = new Person();
    var s = [];
    s.push(new Course('金X瓶X梅', false));
    s.push(new Course('痴情关上观痴情', true));
    p.setCourses(s);//size 2
    console.log(p.getCourses());
    var refact = new Course('不再含苞待放的日子', true)
    p.getCourses().push(refact);
    p.getCourses().push(new Course('后庭花下会后庭', true));
    console.log(p.getCourses());// size 4
    p.getCourses().pop();
    console.log(p.getCourses());// size 3
    
    //如果想了解高级课程可以这样做:
    var count = 0
    for (var i = 0, len = p.getCourses().length; i < len; i++) {
     if (p.getCourses()[i].isAdvanced()) count++;
    }
    
    我们要做的第一件事就是为person中的集合添加合适的修改函数:
    var Person = base.Class({
     _propertys_: function () {
     this.courses = [];
     },
     getCourses: function () {
     return this.courses;
     },
     initCourses: function (arg) {
     this.courses = arg;
     for (var i = 0, len = arg.length; i < len; i++) {
     this.addCourse(arg[i]);
     }
     },
     addCourse: function (arg) {
     this.courses.push(arg);
     },
     remove: function (arg) {
     //显然这个代码毫无意义,权当此处数组可以这样删除吧
    this.courses.remove(arg)
     }
    });

    现在,我们求高级课程的数量的代码就可以移入Person类了

    结语

    该篇文章对上一章进行说明

  • 相关阅读:
    Eclipse中普通java项目转成Web项目
    spring MVC 转发与重定向(传参)
    spring MVC <mvc:annotation-driven>
    spring MVC 全局的异常处理
    spring MVC拦截器
    Spring Bean的作用域
    Spring 常用注解
    在普通类中获取Spring管理的bean
    Spring事务配置
    spring在普通类中获取session和request
  • 原文地址:https://www.cnblogs.com/yexiaochai/p/3409379.html
Copyright © 2020-2023  润新知