【重构笔记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类了

结语

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



本文转自叶小钗博客园博客,原文链接http://www.cnblogs.com/yexiaochai/p/3409379.html,如需转载请自行联系原作者

相关文章
|
机器学习/深度学习 人工智能 监控
chatgpt对未来渗透的影响
ChatGPT(Generative Pre-trained Transformer)是 AI 领域的最新发展,由 Sam Altman 领导的研究公司 OpenAI 创建,并得到 Microsoft、Elon Musk、LinkedIn 联合创始人 Reid Hoffman 和 Khosla Ventures 的支持。 人工智能聊天机器人可以与模仿各种风格的人进行对话。ChatGPT 创建的文本远比之前构建的硅谷聊天机器人更具想象力和复杂性。它是根据从网络、存档书籍和维基百科获得的大量文本数据进行训练的。
193 1
chatgpt对未来渗透的影响
|
6天前
|
NoSQL Cloud Native Redis
Redis核心开发者的新征程:阿里云与Valkey社区的技术融合与创新
阿里云瑶池数据库团队后续将持续参与Valkey社区,如过往在Redis社区一样耕耘,为开源社区作出持续贡献。
Redis核心开发者的新征程:阿里云与Valkey社区的技术融合与创新
|
5天前
|
关系型数据库 分布式数据库 数据库
PolarDB闪电助攻,《香肠派对》百亿好友关系实现毫秒级查询
PolarDB分布式版助力《香肠派对》实现百亿好友关系20万QPS的毫秒级查询。
PolarDB闪电助攻,《香肠派对》百亿好友关系实现毫秒级查询
|
7天前
|
消息中间件 Cloud Native Serverless
RocketMQ 事件驱动:云时代的事件驱动有啥不同?
本文深入探讨了云时代 EDA 的新内涵及它在云时代再次流行的主要驱动力,包括技术驱动力和商业驱动力,随后重点介绍了 RocketMQ 5.0 推出的子产品 EventBridge,并通过几个云时代事件驱动的典型案例,进一步叙述了云时代事件驱动的常见场景和最佳实践。
115028 1
|
7天前
|
弹性计算 安全 API
访问控制(RAM)|云上安全使用AccessKey的最佳实践
集中管控AK/SK的生命周期,可以极大降低AK/SK管理和使用成本,同时通过加密和轮转的方式,保证AK/SK的安全使用,本次分享为您介绍产品原理,以及具体的使用步骤。
101800 1
|
7天前
|
自然语言处理 Cloud Native Serverless
通义灵码牵手阿里云函数计算 FC ,打造智能编码新体验
近日,通义灵码正式进驻函数计算 FC WebIDE,让使用函数计算产品的开发者在其熟悉的云端集成开发环境中,无需再次登录即可使用通义灵码的智能编程能力,实现开发效率与代码质量的双重提升。
95382 2
Doodle Jump — 使用Flutter&Flame开发游戏真不错!
用Flutter&Flame开发游戏是一种什么体验?最近网上冲浪的时候,我偶然发现了一个国外的游戏网站,类似于国内的4399。在浏览时,我遇到了一款经典的小游戏:Doodle Jump...
112727 12
|
11天前
|
SQL 存储 JSON
Flink+Paimon+Hologres 构建实时湖仓数据分析
本文整理自阿里云高级专家喻良,在 Flink Forward Asia 2023 主会场的分享。
71310 1
Flink+Paimon+Hologres 构建实时湖仓数据分析
|
15天前
|
弹性计算 运维 安全
访问控制(RAM)|云上程序使用临时凭证的最佳实践
STS临时访问凭证是阿里云提供的一种临时访问权限管理服务,通过STS获取可以自定义时效和访问权限的临时身份凭证,减少长期访问密钥(AccessKey)泄露的风险。本文将为您介绍产品原理,以及具体的使用步骤。
151041 4
|
14天前
|
监控 负载均衡 Java
深入探究Java微服务架构:Spring Cloud概论
**摘要:** 本文深入探讨了Java微服务架构中的Spring Cloud,解释了微服务架构如何解决传统单体架构的局限性,如松耦合、独立部署、可伸缩性和容错性。Spring Cloud作为一个基于Spring Boot的开源框架,提供了服务注册与发现、负载均衡、断路器、配置中心、API网关等组件,简化了微服务的开发、部署和管理。文章详细介绍了Spring Cloud的核心模块,如Eureka、Ribbon、Hystrix、Config、Zuul和Sleuth,并通过一个电商微服务系统的实战案例展示了如何使用Spring Cloud构建微服务应用。
103517 9