重构第八章——重新组织数据

第8章介绍重新组织对象的数据的重构方法,对象的数据包括普通字段、常量、类型码等,以下16条重构手段涉及到封装、解耦、设计模式。

1、自封装字段

什么时候进行自封装:当类的字段在其他地方(如子类中、别的类中)使用时,进行自封装是必要的,因为不封装的话,只能修改字段的可见范围(对象的字段可见范围绝大多数情况下都应该是private的,在继承体系中可能是protected,不能根据访问需要而改动)。进行自封装就是添加getter和setter方法。

2、以对象取代数据值

随着业务的扩增需要,一些字段需要承担更多的责任,于是就把数据值取代为对象。

3|4、使用值对象 VS 使用引用对象

区分:值对象(value object)是不可修改内容的,比如String、Integer、日期、钱等,引用对象(reference object)是可以修改内容的,比如数组、ArrayList、客户、账户等。

使用场景:比如一个Order对应一个Customer,一个Customer可对应多个Order,所以这个Customer应该是被多个Order所共享的,也就是说修改某一个Order的Customer的个人信息(如地址、电话),会导致这个Customer的所有Order的Customer的个人信息都改变。

用途区别:①分布系统和并发系统中,不可变的值对象特别有用,因为你无需考虑它们的同步问题;②如果对象是可变的,那么使用引用对象更好,因为不用考虑改变一个对象时,需要同步更新其他“代表相同事物”的对象。

注意事项:①使用引用对象时,使用工厂对象创建,因为对象不能随便创建,不然就不能保证多个订单指向的是同一个对象,使用工厂可以统一对象创建的入口。②String、Money对象不可变,表示值本身不能修改,如果引用需要修改,只能另一个对象代替现有的对象,现有对象自身不能修改。

5、以对象取代数组

数组应该只用于以某种顺序容纳一组相似的对象,如果不相似,则使用对象封装。

6、复制被观察数据

编程中的一大原则是:不要复制相同的数据,否则将带来恼人的数据一致性的维护工作。在GUI控件、Viewer界面,访问数据是例外,需要复制数据,这样做的目的是降低耦合(前后端分离),使得界面只与数据有关,不参与逻辑。

使用观察者模式:数据由计算逻辑部分得到,然后复制给显示逻辑部分,数据存在两处,因此需要保持数据同步,也即计算部分的数据改变后,应该立即同步改变显示部分的数据。

7|8、将单向关联改为双向关联 VS 将双向关联改为单向关联

1)单变双:

关键步骤:保存反向指针、决定谁控制关联关系

以Order-Customer为例:Order持有Customer引用,Customer同时也持有Order集合引用,由于一个Customer可以有多个Order,因此让持有单一引用的Order控制关联关系。让Order控制,在Customer中添加方法使得Order可以通过Customer获得同一Customer的Order集合。双向关联后:Order修改自己的customer,过程为在删除旧的Customer对自己的引用,在将新的Customer中添加对自己的引用。

2)双变单:

双向连接的弊端:双向连接很容易造成僵尸对象,即对象本应该死了,但是由于引用存在,无法被回收;双连耦合性非常强。

以Order-Customer为例:只保留单方面的引用,只留下Order对Customer的引用,或者只留下Customer对Order的引用。比如只留下Order对Customer的引用,Customer需要使用Order引用的地方,可以通过函数传参形式实现,也可以通过Substitute Algorithm得到Order引用(一种方法:查数据库所有Order,然后根据customer筛选出一个对应的Order)。

9、以符号常量取代魔数

创建一个常量,根据其意义命名,用常量代替魔数。

魔数含义:①class类型的文件的起始字节,作为标识符方便jvm识别,这个魔数是由四个字节的无符号数组成的,为0xCAFEBABE;②未定义的数字常量或者字符串,他们没有注释,并且从命名上也看不出什么意思,很可能在过一段时间之后谁也不知道这个常量或者字符串代表什么意思(魔鬼啊简直),我们就称这个常量或者字符串为魔数。

在《阿里巴巴Java开发手册》中也有关于魔数的要求,【强制】不允许任何魔法值(即未定义的常量)直接出现在代码中。

10、封装字段

封装或者说数据隐藏是面对对象的首要原则之一,public字段容易在对象不知道的情况下被访问、修改,导致数据和行为分开,这是大忌。

做法:设置为private,提供getter和setter。

11、封装集合

返回要求:有一个函数返回一个集合,则应该让这个函数返回该集合的一个只读副本(为了阻止在其他地方,不通过这个类而进行修改,java中Collection.unmodifiableXxx()可以得到集合的只读副本),并在这个类中提供添加移除元素的方法。

注意:不能直接把传入集合赋值给对象的集合:因此这样做存在设完值之后,用户可以通过传入集合修改对象的结合的元素的情况(他们持有同一对象),就会破坏封装性,必须新建一个集合副本。

封装之后效果:只能通过对象提供的添加、移除方法修改集合元素,最好是不提供对外访问内部集合的机会,如果提供访问,则访问只读副本。

12、以数据类取代记录

为数据记录创建一个哑巴数据对象,也就是POJO(plain original java object),也有称DTO(data transform object),只有getter和setter方法,没有业务逻辑。

13、以类取代类型码

类中有几个个数值类型码,考虑将这几个类型码抽象成一个类型对象,然后用类型对象引用代替原来类中的类型码。13和下面14、15的使用区别在于,这里的类型码不同不会影响到类的函数的逻辑;14、15函数逻辑依据类型码的不同而改变。

14|15、以子类取代类型码 VS 以状态模式\策略模式取代类型码

类型码通常会引入switch case代码块,这就是一种bad smell,需要通过重构解决。类型码的存在经常会导致,对象的方法需要依据类型码做判断,有两种重构方式,原理都是使用多态。我对重构第一章的总结笔记中,也讲到了这两种方式:重构第一章——重构三部曲:抽取方法、移动方法、使用多态

1)以子类取代类型码:如果是不可变的类型码,即对象创建后这个类型码是不变的,使用子类取代这个类型码,根据类型码划分成不同的子类,父类中将依据类型码做不同逻辑计算的方法编程抽象方法,不同的子类依据自身类型码实现不同的方法,这样原来switch的逻辑代码,根据多态,可以调用到实际某种类型子类的逻辑代码。

2)以状态模式\策略模式取代类型码:两种情况只能用这种方式,①状态码可变②类型码的宿主类,已经存在子类,如果使用子类的话会导致子类层次混乱。
使用过程:①创建类型码的父类,将原来类依据类型码的计算逻辑方法,抽象到新建的父类中 ②子类继承这个类型码父类,并实现各自的计算方法 ③原来类中持有对类型码父类对象的引用,在运行中,这个引用会指向直接的某一种类型码子类对象 ④原类的计算逻辑方法,变成了调用持有的类型码引用的计算方法。

16、以字段取代子类

如果子类只有常量函数(硬编码返回结果为常量),这样的子类没有价值,可以删之,在超类中设计这样的字段和返回方法。然后通过内联方法的形式,将子类的构造函数内联到超类工厂函数中。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值