将单项关联变为双向关联
有时候,我们两个类需要使用对方属性,但期间只有一条单向连接
此时可以添加一个反向指针,并修改关系函数使他能够同时更新两条连接
开发初期,我们可能会在两个类之间建立一条单向连接,使其中一个类可以引用另一个类
随着时间推移,我们发现被引用类需要得到其引用者才能进行某些处理,也就是说需要一个反向指针
但是指针是一个单向连接,我们不能单向操作他,此时便可以建立双向引用关系了
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类了
结语
该篇文章对上一章进行说明
本文转自叶小钗博客园博客,原文链接http://www.cnblogs.com/yexiaochai/p/3409379.html,如需转载请自行联系原作者