【重构.改善既有代码的设计】8、重新组织数据(更优雅的封装)

重新组织数据

Self Encapsulate Field(自封装值域)

自封装值域 就是 getter and setter,这就没啥好说的了。

Replace Data Value with Object(以对象取代数据值)

比如:不用字符串表示电话号码,而用一个电话号码的类表示。

好处:更容易扩展,比如:以后需要一个函数来提取号码中的区号时,就可以直接写在这个类里面了。

Change Value to Reference(将实值对象改为引用对象)

引用对象可以是指针、引用、下标等。

好处:省空间,比较简单。

缺点:不能局部修改。

Replace Array with Object(以对象取代数组)

使用场景:一个数组容纳了数种不同对象。

这种特定场景的,不再多看了。

Duplicate Observed Data(复制「被监视数据」)

业务分层后,数据也应该分层,复制一份到下层。

但分层后的数据如何同步?需要通过观察者模式/事件监昕器

典型场景:前端数据变化通知后端。

Change Unidirectional Association to Bidirectional(将单向关联改为双向)

两个classes都需要使用对方特性,但其间只有一条单向连接(one-way link)。

添加一个反向指针,并使修改函数(modifiers)能够同时更新两条连接。

目的:减少访问成本。

Change Bidirectional Association to Unidirectional(将双向关联改为单向)

两个鄉之间有双向关联,但其中一个class如今不再需要另一个class的特性。

去除不必要的关联(association)。

目的:减少维护成本、复杂度。

Replace Magic Number with Symbolic Constant(以符号常量/字面常量取代魔法数)

目的:
1、只修改一处。2、含义明确。

Encapsulate Field(封装值域)

就是改public 为 setter

Encapsulate Collection(封装群集)

描述:有个函数(method)返回一个群集(collection)。 让这个函数返回该群集的一个只读映件(read-only view),并在这个class中提供「添加/移除」(add/remove)群集元素的函数。

原因:会对用户暴露过多「对象内部数据结构」的信息。

本质上就是为群集做了一个委托。

目的:只有通过类提供的方法,才能修改群集。

实际上,Array就是这么做的,你是无法直接访问其中的数组datas的,而是要通过Array的add,remove等方法才能操作data。

Replace Record with Data Class(以数据类取代记录)

这里的Record指的是Struct

Replace Type Code with Class(以类取代型别码)

识别码是啥?

   public static final int O = 0;
   public static final int A = 1;
   public static final int B = 2;
   public static final int AB = 3;

现在有枚举了,用枚举替代。

Replace Type Code with Subclasses(以子类取代型别码)

这就是解决switch问题的那个基本的重构方法。

好处是,可以消除多处的switch,只有初始化时还保留了switch。

Replace Type Code with State/Strategy(以State/strategy 取代型别码)

解决switch的另一手段:状态模式/策略模式。

Replace Subclass with Fields(以值域取代子类)

描述:

你的各个subclasses 的惟一差别只在「返回常量数据」的函数身上。

修改这些函数,使它们返回superclass 中的某个(新增)值域,然后销毁subclasses 。

原因:不必要使用子类的地方,去掉子类,更简单。

举例:
如果男人类和女人类的区别只是获取性别函数返回不同的值,那就只需要人类,性别做为一个final属性。

原作者的总结

本章之中,我将讨论数个「能让你更轻松运用数据」的重构手法。

Replace Data Value with Object 让你可以将「哑」数据(dumb data)变成会说话的对象(articulate objects)。 如果你发现程序中有太多地方需要这一类对象,你也可以使用Change Value to Reference 将它们变成reference object。

对象之间的关联(links)可以单向,也可以双向。单向关联比较简单,但有时为了支持一项新功能,你需要以Change Unidirectional Association to Bidirectional 将它变成双向关联。Change Bidirectional Association to Unidirectional 则恰恰相反:如果你发现不再需要双向关联,可以使用这项重构将它变成单向关联。

我常常遇到这样的情况:GUI classes竟然去处理不该它们处理的业务逻辑(business logic)。为了把这些处理业务逻辑的行为移到合适的domain class去,你需要在domain class中保存这些逻辑的相关数据,并运用 Duplicate Observed Data 提供对GUI的支持。一般来说,我不喜欢重复的数据,但这是一个例外,因为这里的重复数据通常是不可避免的。

面向对象编程(OOP)的关键原则之一就是封装。如果一个class暴露了任何public数据,你就应该使用Encapsulate Field 将它高雅而正派地包装起来。如果被暴露的数据是个群集(collection),你就应该使用Encapsulate Collection 因为群集有其特殊协议。如果一整笔记录(record)都被裸露在外,你就应该使用 Replace Record with Data Class。

需要特别对待的一种数据是type code〔型别码):这是一种特殊数值,用来指出 「与实体所属之型别相关的某些东西」。Type code通常以枚举(enumeration)形式出现,并且通常以static final整数实现之。如果这些type code用来表现某种信息,并且不会改变所属class的行为,你可以运用 Replace Type Code with Class 将它们替换掉,这项重构会为你提供更好的型别检查,以及一个更好的平台,使你可以在未来更方便地将相关行为添加进去。另一方面,如果class的行为受到type code的影响,你就应该尽可能使用Replace Type Code with Subclasses。如果做不到,就只好使用更复杂(同时也更灵活)的 Replace Type Code with State/Strategy。

我的总结

这章是基于面向对象思想的一些重构手段,由于本书写于2001年,当年java还是1.1-1.2,面向对象也不是很流行,所以作者的有些方法太过老旧了,有些方法现在已经是被认为理所当然的方法了。

面向对象的思想:继承、封装、多态。

重构方法:
1、封装数据: Self Encapsulate Field(自封装值域)/Encapsulate Field(封装值域) 
2、魔鬼数字/枚举:Replace Magic Number with Symbolic Constant(以符号常量/字面常量取代魔法数)/Replace Type Code with Class(以类取代型别码)
3、尽量用对象,替代数据值/struct/群集:Change Value to Reference(将实值对象改为引用对象)/Replace Record with Data Class(以数据类取代记录)/ Encapsulate Collection(封装群集)
4、按需双向依赖:Change Unidirectional Association to Bidirectional(将单向关联改为双向)/Change Bidirectional Association to Unidirectional(将双向关联改为单向)
5、switch破除大法:子类/策略模式/状态模式:Replace Type Code with Class(以类取代型别码)/Replace Type Code with Subclasses(以子类取代型别码)/Replace Type Code with State/Strategy(以State/strategy 取代型别码) 6、避免过度使用子类。Replace Subclass with Fields(以值域取代子类)
7、数据分层。Duplicate Observed Data(复制「被监视数据」)

posted @ 2019-02-15 16:46  傲衣华少  阅读(342)  评论(0编辑  收藏  举报