1、改善代码的三部曲
《设计模式》-> 《重构》-> 《重构与模式》。也就是设计->重构->重构出新设计。程序员
《设计模式》主要详细说明20几种模式,为咱们带来了常见设计问题的经典解决方案,从而改变了整个面向对象开发的面貌。为设计而著。算法
《重构》改善既有代码的设计,总结了咱们会用到的各类重构手法,为咱们带来了一种改进代码的高效过程,从而完全改变了面向对象设计的方式。侧重去除坏代码的味道。编程
《重构与模式》是设计模式相关的重构。模式不是设计出来的,是重构出来的。好的设计也不是设计出来的,是重构出来的。不要怕改变,只要改变得法,变就再也不是灾难,而是进步的良机。侧重设计模式+重构手段。设计模式
在阅读重构与模式以前,最好熟读前面两本:《设计模式》和《重构》。安全
设计模式表明了传统的软件开发思想:好的设计会产生好的软件,所以在实际开发以前,值得花时间去作一个全面而细致的设计。重构表明了敏捷软件开发的浪潮:软件并非在一开始就能够设计得天衣无缝的,所以能够先进行实际开发,而后经过对代码不断的进行小幅度的修改来改善其设计。两者从不一样角度阐述了设计的重要性。
有些人在编写任何代码以前,都要很早地为模式作计划,而有些人在编写了大量代码以后才开始添加模式。
第二种使用模式的方式就是重构,由于是要在不增长系统特性或者不改变其外部行为的状况下改变系统的设计。
有些人在程序中加入模式,只是由于以为模式可以使程序更容易修改;更多人这样作只是为了简化目前的设计。
若是代码已经编写,这两种情形都是重构,由于前者是经过重构使修改更容易,然后者则是经过重构在修改后进行整理。
虽然模式是在程序中可以看到的东西,可是模式也是一种程序转换。
重构是实现设计模式的一种手段,设计模式每每也是重构的目的。
应该经过重构实现模式、趋向模式和去除模式(refactoring to, towards, and away from pattern),而不是在预先设计中使用模式,也再也不过早的在代码中加入模式。这技能避免过分设计,又不至于设计不足。
1.
过分设计:代码的灵活性和复杂性超出所需。有些开始设计的时候,认为某些地方会频繁的改动,甚至开始使用了某种设计模式预留扩展,可是后来却没怎么动,也就是致使了废设计和功能.
2.
设计不足
产生设计不足的缘由:
1)程序员没有时间,没有抽出时间,或者时间不容许进行重构
2)程序员在何为好的软件设计方面知识不足
3)程序员被要求在既有系统中快速的添加新功能
4)程序员被迫同时进行太多项目
长期的设计不足,会使软件开发节奏变成“快,慢,更慢”,可能的后果是:
1.0版本很快就交付了,可是代码质量不好
2.0版本也交付了,但质量低劣的代码使咱们慢下来
在企图交付将来版本时,随着劣质代码的倍增,开发速度也愈来愈慢,最后人们对系统、程序员乃至使你们陷入这种境地的整个过程都失去了信心
到了4.0版本时或者以后,咱们意识到这样确定不行,开始考虑推倒重来
3.
测试驱动开发和持续重构,
测试驱动开发和持续重构提供了一种精益、迭代和训练有素的编程风格,可以最大程度的有张有弛,提升生产率。“迅速而又从容不迫”
使用测试驱动开发和持续重构的益处:
1)保持较低的缺陷数量
2)大胆的进行重构
3)获得更加简单、更加优秀的代码
4)编程时没有压力
模式和重构之间存在着自然联系,模式是你想达到的目的地,而重构则是从其余地方抵达这个目的地的条条道路。
4.演进式设计
演进式设计即趋向性设计,主要是避免过分设计。
经过重构产生设计结构,也就是经过重构实现模式或者重构趋向模式。为设计而设计的思路并不适合大项目,按部就班从重构到设计模式才是设计模式的王道。
敏捷开发中常常采用的演进式架构设计:架构
不少程序员可能都碰见过这种事:某块代码亟待修改,却没有人愿意接手。为何会这样?这段代码正巧是两个组件间的接口,修改工做太过困难。而在演进式设计中,咱们经常会作这种修改。代码应当是"活的"而且是"可生长"的,决不能无视强烈的变化需求 而保持一成不变。正由于如此,演进式设计能够提升设计质量,进而提升整个系统的质量。框架
6.1 用Creating Method替换构造函数
当类中有多个构造函数,所以很难决定在开发期间用哪个时,能够用可以说明意图的返回对象实例的Creation Method替换构造函数
动机:
Creation Method——类中的一个静态或者非静态的负责实例化类的新实例方法。因Creating Method命名没有限制,因此能够取最能表达所建立对象的名字。
类中有太多构造函数→提炼类或者提炼子类 或者 用Creation Method替换构造函数来澄清构造函数的意图
优缺点:
+ 比构造函数可以更好的表达所建立的实例种类
+ 避免了构造函数的局限,好比两个构造函数的参数数目和类型不能相同
+ 更容易发现无用的建立代码
- 建立方式是非标准的,有的用new实例化,而有的用Creation Method实例化
变体:
不须要为每一个对象的配置都设立一个Creation Method,非必要状况下能够添加参数来减小Creation Method的数量
当Creation Method过多的分散了类的主要职责是,应该考虑将相关的Creation Method重构为一个Factory
6.2 将建立知识搬移到Factory
当用来实例化一个类的数据和代码在多个类中处处都是时,能够讲有关建立的知识搬移到一个Factory中
动机:
建立蔓延——将建立的职责放在了不该该承担对象建立任务的类中,是解决方案蔓延中的一种,通常是以前的设计问题致使。
使用一个Factory类封装建立逻辑和客户代码的实例化选项,客户能够告诉Factory实例如何实例化一个对象,而后用同一个Factory实例在运行时执行实例化。
Factory不须要用具体类专门实现,可使用一个接口定义Factory,而后让现有的类实现这个接口。
若是Factory中建立逻辑过于复杂,应将其重构为Abstract Factory,客户代码能够配置系统使用某个ConcreteFactory(AbstractFactory的一个具体实现)或者默认的ConcreteFactory。
只有确实改进了代码设计,或者没法直接进行实例化时才有足够的理由进行Factory重构
优缺点:
+ 合并建立逻辑和实例化选项
+ 将客户代码与建立逻辑解耦
- 若是能够直接实例化,会使设计复杂化
6.3 用Factory封装类
当直接实例化处在同一包结构中、实现统一接口的多个类。能够把类的构造函数声明为非公共的,并经过Factory来建立它们的实例
动机:
能够经过Factory将一组客户并不需关心的子类屏蔽到包内部。
若是类共享一个通用的公共接口、共享相同的超类、而且处在同一包结构中,该重构可能有用。
优缺点:
+ 经过意图导向的Creation Method简化了不一样种类实例的建立
+ 经过隐藏不须要公开的类减小了包的“概念重量”
+ 帮助严格执行“面向接口编程,而不是面向实现”这一格言
- 当须要建立新种类的实例时,必须更新Creation Method
- 当客户只能得到Factory的二进制代码而没法得到源码时,对Factory的定制将受到限制
6.4 用Factory Method引入多态建立
当一个层次中的类都类似的实现一个方法,只是对象建立的步骤不一样时,能够建立调用Factory Method来处理实例化方法的惟一超类版本
动机:
Factory Method是OOP中最多见的模式,因其提供了多台建立对象的方法
使用Factory Method后的代码每每比在类中赋值方法来建立自定义对象要简单
使用Factory Method的主要状况:
当兄弟子类实现了除对象建立步骤外都很类似的方法时
当超类和子类实现了除对象建立步骤外都很类似的方法时
优缺点:
+ 减小因建立自定义对象而产生的重复代码
+ 有效的表达了对象建立发生的位置,以及如何重写对象的建立
+ 强制Factory Method使用的类必须实现统一的类型
- 可能会向Factory Method的一些实现者传递没必要要的参数
6.5 用Builder封装Composite
当构造Composite是重复的、复杂的且容易出错的工做时,经过使用Builder处理构造细节来简化构造过程。
动机:
构造Composite是重复的、复杂的、容易出错的工做,经过使用Builder处理构造细节来简化构造过程
Builder模式很擅长处理繁重的、复杂的构造步骤。
Builder模式的意图:将一个复杂对象的构建与它的表示分离,使得一样的构建过程能够建立不一样的表示。
优缺点:
+ 简化了构造Composite的客户代码
+ 减小了建立Composite的重复和易出错的本性
+ 在客户代码和Composite之间实现了松耦合
+ 容许对已封装的Composite或复杂对象建立不一样的表示
- 接口可能不会很清楚的表达其意图
6.6 内联Singleton
当代码须要访问一个对象,可是不须要对象的全局入口时,能够把Singleton的功能搬移到一个保存并提供对象访问入口的类中。删除Singleton。
动机:
Singleton意图:确保一个类仅有一个实例,并提供一个访问它的全局访问点
保持暴露对象和保护对象之间的平衡对维护系统的灵活性是相当重要的
任何全局数据在被证实是无害以前都是有害的
若是遇到本不应实现为Singleton的Singleton,不要犹豫,内联它!
优缺点:
+ 使对象的协做变得更明显和明确
+ 保护了单一的实例,且不须要特殊的代码
- 当在许多层次间传递对象实例比较困难的时候,会使设计变得复杂
咱们所编写的绝大部分代码都不会从一开始就很简单。
算法常常会由于支持多种变化而变得复杂。
控制状态转换的转换的逻辑每每会变得愈来愈复杂。
7.1 组合方法
当你没法迅速的理解一个方法的逻辑时,把方法的逻辑转换成几个同一层面上的、可以说明意图的步骤。
动机:
Composed Method由对其余方法的调用组成,好的Composed Method的代码都在细节的同一层面上。
Composed Method通常不会引入性能问题
优缺点:
+ 清晰的描述了一个方法所实现的功能以及如何实现
+ 把方法分解成命名良好的、处在细节的同一层面上的行为模块,以此来简化方法
- 可能会产生过多的小方法
- 可能会使调试变得困难,由于程序的逻辑分散在许多小方法中
Composed Method指导原则:
Composed Method都很小。通常在5行左右,不多超过10行
删除重复代码和死代码。除去明显的和微妙的代码重复,除去没有被使用的代码,以减小方法的代码量
表达意图。清楚的命名程序中的变量、方法和参数,使它们明确表达意图。
简化。转换代码,使它尽量简单。
使用细节的统一层面。当把一个方法分解成一组行为时,要保证这些行为在细节的类似层面上。
7.2 用Strategy替换条件逻辑
当方法中条件逻辑控制着应该执行计算的哪一个变体时,为每一个变体建立一个Strategy并使方法把计算委托到Strategy实例。
动机:
——为算法的各个变体生成一系列的类,并用Strategy的一个实例装配主类,主类在运行时委托到该Strategy实例
复杂的条件逻辑是最常致使复杂度上升的地点之一
优缺点:
+ 经过减小或去除条件逻辑使算法变得清晰易懂
+ 经过把算法的变体搬移到类层次中简化了类
+ 容许在运行时用一种算法替换另外一种算法
- 当应用基于继承的解决方案或“
简化条件表达式”中的重构更简单时,会增长设计的复杂度
- 增长了算法如何获取或接收上下文类数据的复杂度
7.3 将装饰功能搬移到Decorator
当代码向类和核心职责提供装饰功能时,能够考虑将装饰代码搬移到Decorator
不管多么喜欢一个模式,不要在没必要要的时候使用它
优缺点:
+ 把装饰功能从类中移除,从而简化类
+ 有效的把类的核心职责和装饰功能区分开来
+ 能够去除几个相关类中重复的装饰逻辑
- 改变了被装饰对象的类型
- 会使代码变得更难理解和调试
- 当Decorator组合产生负面影响的时候,会增长设计的复杂度
7.4 用State替换状态改变条件语句
当控制一个对象状态转换的条件表达式过于复杂时,能够考虑用处理特殊状态转换的State类替换条件语句
优缺点:
+ 减小或去除状态改变条件逻辑
+ 简化了复杂的状态改变逻辑
+ 提供了观察状态改变逻辑的很好的鸟瞰图
- 当状态转换逻辑已经易于理解的时候,会增长设计的复杂度
7.5 用Composite替换隐含树
当用原生表示法隐含的造成了树结构时,能够考虑用Composite替换这个原生表示法
优缺点:
+ 封装重复的指令,如格式化、添加或删除结点
+ 提供了处理类似逻辑增加的通常性方法
+ 简化了客户代码的构造职责
- 当构造隐式树更简单的时候,会增长设计的复杂度
7.6 用Command替换条件调度程序
当条件逻辑用来调度请求和执行操做时,为每一个动做建立一个Command。把这些Command存储在一个集合中,并用获取及执行Command的代码替换条件逻辑。
为每一个动做建立一个Command,把这些Command存储在一个集合中,并用获取及执行Command的代码替换条件逻辑
优缺点:
+ 提供了用统一方法执行不一样行为的简单机制
+ 容许在运行时改变所处理的请求,以及如何处理请求
+ 仅仅须要不多的代码实现
- 当条件调度程序已经足够的时候,会增长设计的复杂度
泛化是把特殊代码转换成通用目的代码的过程。泛化代码的产生每每的重构的结果。
8.1 造成Template Method
当子类中的两个方法以相同的顺序执行类似的步骤,可是步骤并不彻底相同。经过把这些步骤提取成具备相同签名的方法来泛化这两个方法,而后上移这些泛化方法,造成Template Method。
优缺点:
+ 经过把不变行为搬移到超类,去除子类中的重复代码
+ 简化并有效的表达了一个通用算法的步骤
+ 容许子类很容易的定制一个算法
- 当为了生成算法,子类必须实现不少方法的时候,会增长设计的复杂度
8.2 提取Composite
当一个类层次结构中的多个子类实现了同一个Composite时,能够提取一个实现该Composite的超类
优缺点:
+ 去除重复的类存储逻辑和类处理逻辑
+ 可以有效的表达类处理逻辑的可继承性
8.3 用Composite替换一/多之分
当类使用不一样的代码处理单一对象与多个对象时,用Composite可以产生既能够处理单一对象又能够处理多个对象的代码
优缺点:
+ 去除与处理一个或多个对象相关联的重复代码
+ 提供处理一个或多个对象的统一方法
+ 支持处理多个对象的更丰富的方法
- 可能会在Composite的构造过程当中要求类型安全的运行时检查
8.4 用Observer替换硬编码的通知
当子类经过硬编码来通知另外一个类的实例时能够去除这些子类,并使其超类可以通知一个或多个实现了Observer接口的类
优缺点:
+ 使主题及其观察者访问松散耦合
+ 支持一个或多个观察者
- 当硬编码的通知已经足够的时候,会增长设计的复杂度
- 当出现串联通知的时候,会增长代码的复杂度
- 当观察者没有从它们的主题中被删除的时候,可能会形成资源泄漏
8.5 经过Adapter统一接口
当客户代码与两个类交互,其中的一个类具备首选接口,能够用一个Adapter统一接口
动机:
当下面条件都为真时,重构Adapter就是有用的:
两个类所作的事情相同或类似,可是具备不一样的接口
若是类共享同一个接口,客户代码会更简单、更直接、更紧凑
没法轻易改变其中一个类的接口,由于它是第三方库中的一部分,或者它是一个已经被其余客户代码普遍使用的框架的一部分,或者没法得到源码
优缺点:
+ 使客户代码能够经过相同的接口与不一样的类交互,从而去除或减小重复代码
+ 使客户代码能够经过公共的接口与多个对象交互,从而简化了客户代码
+ 统一了客户代码与不一样类的交互方式
- 当类的接口能够改变的时候,会增长设计的复杂度
8.6 提取Adapter
当一个类适配了多个版本的组件、类库、API或其余实体时,能够为组件、类库、API或其余实体的每一个版本提取一个Adapter
Adapter用来适配对象,Facade用来适配整个系统,Facade一般用来与遗留系统进行交互
优缺点:
+ 隔离了不一样版本的组件、类库或API之间的不一样之处
+ 使类只负责适配代码的一个版本
+ 避免频繁的修改代码
- 若是某个重要行为在Adapter中不可用的话,那么客户代码将没法执行这一重要行为
8.7 用Interpreter替换隐式语言
当类中的许多方法组合成了一种隐式语言的元素,能够为隐式语言的元素定义类,这样就能够经过类实例组合,造成易于理解的表达式
优缺点:
+ 比隐式语言更好的支持语言元素的组合
+ 不须要解析新的代码来支持语言元素的新组合
+ 容许行为的运行时配置
- 会产生定义语言和修改客户代码的开销
- 若是语言很复杂,则须要不少的编程工做
- 若是语言自己就很简单,则会增长设计的复杂度
9.1 用类替换类型代码
字段的类型没法保护它免受不正确的复制和非法的等同性比较,能够把字段的类型声明为类,从而限制复制和等同性比较
优缺点:
+ 更好的避免非法赋值和比较
- 比使用不安全类型要求更多的代码
9.2 用Singleton限制实例化
代码建立了一个对象的多个实例,并致使内存使用过多和系统性能降低时,能够用Singleton替换多个实例
不要作不成熟的代码优化,通过不成熟优化的代码比未优化的代码更难于重构。在代码优化以前,你会发现更多能够改进的地方
优缺点:
+ 改进性能
- 在任何地方均可以很容易的访问。在不少状况下,这多是设计的缺点
- 当对象含有不能共享的状态时,本重构无效
9.3 引入Null Object
当代码中处处都是处理null字段或变量的重复逻辑时,将null逻辑替换为一个Null Object,一个提供正确null行为的对象
优缺点:
+ 不须要重复的null逻辑就能够避免null错误
+ 经过最小化null测试简化了代码
- 当系统不太须要null测试的时候,会增长设计的复杂度
- 若是程序员不知道Null Object的存在,就会产生多余的null测试
- 使维护变得复杂,拥有超类的Null Object必须重写全部新继承到的公共方法
10.1 将汇集操做搬移到Collecting Parameter
有一个很大的方法将信息汇集到一个局部变量中时,能够把结果汇集到一个Collecting Parameter中,并将它传入被提炼出的方法中
优缺点:
+ 帮助咱们把很大的方法转换成更小的,更简单的多个方法
- 使结果代码运行得更快
10.2 将汇集操做搬移到Visitor
有一个方法从不一样的类中
汇集信息,能够把汇集工做搬移到一个可以访问每一个类以便采集信息的Visitor中。
优缺点:
+ 调节多个算法,使其适用于不一样的对象结构
+ 访问相同或不一样继承结构中的类
+ 调用不一样类上的类型特定方法,无需类型转换
- 当可使用通用接口把互不相同的类变成类似类的时候,会增长代码的复杂度
- 新的可访问类须要新的接收方法,每一个Visitor中须要新的访问方法
- 可能会破坏访问类的封装性
11.1 链构造函数
有不少包含重复代码的构造函数时,能够把构造函数连接起来,从而得到最少的代码重复。
11.2 统一接口
当须要一个与其子类具备相同接口的超类或接口时,能够找到全部子类含有而超类没有的公共方法,把这些方法复制到超类中,并修改每一个方法,使其执行空行为。
11.3 提取参数
当一个方法或构造函数将一个字段赋值为一个局部实例化的值时,能够把赋值声明的右侧提取到一个参数中,并经过客户代码提供的参数对字段进行赋值。