何为禅?禅乃为一种境界,一种体验,一种精神领域的最高修为。何为设计模式?是对面向对象思想的深刻理解,对软件设计方法和编码经验的完美总结。设计模式是一种哲学,指导设计一个优秀的架构,编写健壮的代码,解决一个复杂的需求。正所谓“他山之石,可以攻玉”。
一、前言
通读本书,其实我不认为这本是什么“圣经”,但不否认 设计模式 的精妙。作为一名 Code worker,从学校到职场,相信都有听说过 设计模式 这几个字。
设计模式,我们谈得很多,但很多只有模糊的印象而已,如果去看相应的书籍,也会觉得很无聊,除非到特别的场合要用,才会去记一些,根本的原因是,我们并没有理解本质。设计模式的目的是让程序员写出更加高效、优雅的代码;让架构师设计出健壮、稳定、高效的系统,预防需求的变化,可以提高代码的可重用性,增强系统的可维护性。
通晓 23 种设计模式,并合理的应用。经过工作经验的积累,可以更高的层次去欣赏程序代码,软件设计,架构。工作经验的积累才能真正的理解设计模式。如果没有一定的实际开发经验与代码编写量,或者为了设计模式而设计模式,那么建议还是暂时不要管它。经验不是像青春痘一样,说长出来就长出来的,它是需要时间积累的,用心感受,然后慢慢明白为什么这么设计。一旦看懂了设计模式,就像悟出真理一样,那程序员的道路就顺畅了。
二、设计原则
有人说六大设计原则是给面向过程使用的,设计模式才是给面向对象使用的,我觉得不用太在意这样的区分,不管是面向过程还是面向对象,只要是 Coding,那就要遵守这些原则。
六项基本原则:
单一职责原则(SRP)、接口隔离原则、迪米特法则(最少知识原则):分别从类、接口和类关联上阐述一个功能设计上的 高内聚低耦合 观点,然而用了三个原则几十页文字转弯抹角讲的其实就是一句话:各家自扫门前雪,别多管闲事!
里氏替换原则:讲的是透明继承的设计理念,如果不是使用覆盖(override)刻意改写父类方法实现的话,子类对象必需能够无缝透明的替代父类对象。简单点说就是,避免子类重载(overload)方法抢闸,本该调用对象父类的方法实现却被子类的重载方法优先抢去了,有违父类的意图。具体方法是:子类使用前置条件(形参)相同的 override 方法覆盖父类方法,或使用前置条件(继承链上)同级或更宽松(父类级别)的 overload 方法。
依赖倒置原则:就是面向接口编程,使用抽象类或接口实现类关联,完成程序框架搭建,基本的东西没什么好解释的。实现依赖的三种方式:使用构造函数传递对象、使用 setter 方法传递和使用方法签名的形参传递,书上写的这三种分别称为构造函数注入、setter 依赖注入和接口注入。
开闭原则:这个原则推荐使用继承父类、增加代码、使用配置文件等扩展方式,取代对已有代码的删除修改。
三、设计模式
设计模式是本书的重中之重。设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。项目中合理的运用设计模式可以完美的解决很多问题,每种模式在现在中都有相应的原理来与之对应,每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是它能被广泛应用的原因。
分类 | 目的 | 名称 |
---|---|---|
创建型模式 | 用于创建对象 | 工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。共五种。 口诀:单抽元件(建)厂 |
结构型模式 | 处理类或对象的组合 | 配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。共七种。 口诀:外侨(桥)组员(元)戴(代)配饰 |
行为型模式 | 描述类与对象怎样交互、 怎样分配职责 |
策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。共十一种。 口诀:观摩(模)对(迭)策,责令解放(访),戒(介)忘台(态) |
3.1 创建型模式 - 单抽元件(建)厂
设计模式 | 简要说明 | 可改版的方面 |
---|---|---|
Singleton 单例 |
保证一个类仅有一个实例,并提供一个访问它的全局访问点 | 类的单个实例 |
Factory Method(类) 工厂方法 |
定义一个用于创建对象的接口,让子类决定将哪一个类实例化。使一个类的实例化延迟到其子类 | 实例化子类对象 |
Abstract Factory 抽象工厂 |
提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类 | 产品对象族 |
Builder 建造者 |
将一个复杂的对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示 | 如何建立一个组合对象 |
Prototype 原型 |
用原型实例指定创建对象的种类,并且通过拷贝这个原型来创建新的对象 | 实例化类的对象 |
3.1.1 单例 Singleton
切入故事:唯一的皇帝
这种设计模式,要求程序运行时候,不管是什么情况,只允许出现同一个对象。所以这个单例模式的 example 建立时候要保证,构造方法为空,不能通过 new 的方法生成一个另外的对象。对象声明的时候可以定义为 final 类型的,不能被修改,实现方法要加 synchronized 关键字,可以防止并发。
使用场景:
- 要求生成唯一序列号的环境
- 在整个项目中需要一个共享访问点或共享数据,如线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。
3.1.2 工厂方法 Factory Method
切入故事:女娲造人的故事
这种设计模式,可以不用知道需要的对象是怎么生成的,只要找到对应的 factory 接口,然后,给出想要的对象的名称,就可以生成一个需要的对象。在此模式中,工厂父类负责定义创建产品对象的公共接口,而工厂子类负责生产具体的产品对象,使一个类的实例化延迟到其子类,由子类来确定实例化哪个具体的产品类。
使用场景:
- 工厂方法模式是 new 一个对象的替代品,所以在所有需要生成对象的地方都可以使用
- 需要灵活的、可扩展的框架时(万物皆对象)
- 可以使用在测试驱动开发的框架下
3.1.3 抽象工厂 Abstract Factory
切入故事:女娲造人的故事续
这种设计模式,需要的某种对象,可以找到某对象的抽象 Factory,就可以生成一个需要的对象,而且,可以对对象的某些属性和方法提出具体的要求,弊端是,会生成大量的抽象factory.
使用场景:一个对象族(或者一组没有任何关系的对象)有相同的约束,则可以使用抽象工厂模式
3.1.4 建造者模式 Builder
切入故事:变化是永恒的
建造者模式又称为生成器模式,定义为:讲一个复杂对象的构建和它的表示分离,使得同样的构建过程可以创建不同的表示。
使用建造者模式可以使客户端不必知道产品内部组成的细节。建造者独立,容易扩展,便于控制细节风险。
使用场景:
- 相同的方法,不同的执行顺序,产生不同的结果时,可以采用建造者模式
- 多个部件或者零件,都可以装配到一个对象中,但是产生的运行结果又不相同时候,可以使用该模式
- 产品类非常复杂。或者产品类的调用顺序不同产生不同的效果
- 在对象创建过程中使用到系统的一些其他对象,这些对象在产品对象的创建过程中不易得到时,可以采用建造者模式封装该对象的创建过程。
要关注的是零件类型和装配工艺的顺序。
3.1.5 原型模式 Prototype
切入故事:拷贝
原型模式是在内存二进制流的拷贝,实现一个接口,然后重写 clone 方法,就完成了原型模式;要比直接 new 一个对象性能好很多,特别是要在一个循环体内产生大量对象时,原型模式可以更好地提现特点。
原型模式类似于细胞分裂,细胞在一定条件下,由一个分裂成 2 个,再由 2 个分裂成 4 个……,这个原始的细胞决定了分裂出来的细胞的组成结构。Prototype 类中包括一个 clone 方法,Client 调用其拷贝方法 clone 即可得到实例,不需要手工去创建实例。
- 浅拷贝:只拷贝对象,其对象内部的数组、引用对象等都不拷贝,还是指向原生对象的内部元素地址
- 深拷贝:完全拷贝,两个对象之间没有瓜葛,你修改你的,我修改我的,相互不影响
使用场景:
- 资源优化场景(类初始化需要消化非常多的资源,资源包括数据和硬件资源)
- 性能和安全要求的场景
- 一个对象多个修改者的场景
- 需要注意的是:构造函数不会被执行。
3.2 结构型模式 - 外侨(桥)组员(元)戴(代)配饰
设计模式 | 简要说明 | 可改版的方面 |
---|---|---|
Adapter(类) 适配器 |
将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作 | 与对象的接口 |
Bridge 桥接 |
将抽象部分与它的实现部分分离,使它们都可以独立地变化。(颜色、图形统一) | 对象的实现 |
Composite 组合模式 |
将对象组合成树形结构以表示“部分-整体”的层次结构。Composite使得客户对单个对象和复合对象的使用具有一致性 | 对象的结构和组合 |
Decorator 装饰模式 |
动态地给一个对象添加一些额外的职责。就扩展功能而言,Decorator模式比生成子类方式更为灵活(柠檬茉莉花茶) | 无子类对象责任 |
Facade 外观模式 |
为子系统中的一组接口提供一个一致的界面,外观模式通过提供一个高层接口,隔离了外部系统与子系统间复杂的交互过程,使得复杂系统的子系统更易使用 | 与子系统的接口 |
Flyweight 享元模式 |
运用共享技术有效地支持大量细粒度的对象 | 对象的存储代价 |
Proxy 代理模式 |
为其他对象提供一种代理以控制对这个对象的访问。代理模式使用代理对象完成用户请求,屏蔽用户对真实对象的访问 | 如何访问对象位置 |
3.2.1适配器模式 Adapter
切入故事:业务发展-上帝才能控制
描述:将一个类的接口变换成客户端所期待的另一个接口,从而使原来因接口不匹配而无法在一起工作的两个类能够在一起工作。
使用场景:
- 想使用一个已经存在的类,而它的接口不符合你的需求
- 想创建一个可以复用的类,该类可以与其他不相关的类或不可预见的类(接口不一定兼容的类)协同工作
适配器模式最好在详细设计阶段不要考虑它,它不是为了解决还处于开发阶段的问题,而是解决正在服役的项目问题。
3.2.2 外观(门面)模式 Facade
切入故事:我要投递信件
写信的过程:先写信,后装入信封,封好,再投递到信箱。
门面模式又叫外观模式,是一种比较常用的封装模式,定义:要求一个子系统的外部和其内部的通信必须通过一个统一的对象进行。门面模式提供一个高层次的接口,使得子系统更易于使用。
外观模式注重“统一的对象”,也就是提供一个访问子系统的接口,除了这个接口不允许有任何访问子系统的行为发生。
减少系统的相互依赖,提高灵活性,提高安全性。
使用场景:
- 为一个复杂的模块或子系统提供一个供外界访问的接口
- 子系统相对独立--外界对子系统的访问只要黑箱操作即可
- 预防低水平人员带来的风险扩散
3.2.3 桥接 Bridge
切入故事:有一个梦想
定义:桥梁模式又叫桥接模式,是将抽象和实现解耦,使得两者可以独立的变化。抽象角色引用实现角色,或者说抽象角色的部分实现是由实现角色完成的。
桥接式在 Java 应用中的一个非常典型的例子就是 JDBC 驱动器。JDBC 为所有的关系型数据库提供一个通用的界面。一个应用系统动态地选择一个合适的驱动器,然后通过驱动器向数据库引擎发出指令。这个过程就是将抽象角色的行为委派给实现角色的过程。
使用场景:
- 不希望或不适用使用继承的场景 -- 例如继承层次过渡、无法更细化设计颗粒等场景,需要考虑使用桥接模式
- 接口或抽象不稳定的场景 -- 明知道接口不稳定还想通过实现或继承来实现业务需求,那是得不偿失的
- 重用性要求较高的场景 --设计的颗粒越细,则被重用的可能性就越大,而采用继承则受父灯的限制,不可能出现太细的颗粒度
3.2.4 组合模式 Composite
切入故事:人事架构
组合模式又叫合成模式,有时称之为部分-整体模式,主要是用来描述部分与整体之间的关系。将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。组合模式描述了如何将容器对象和叶子对象进行递归组合,使得用户在使用时无须对它们进行区分,可以一致地对待容器对象和叶子对象。
使用场景:
- 维护和展示部分-整体关系的场景,如树形菜单、文件和文件夹管理
- 从一个整体中能够独立出部分模块或功能的场景
注:只要是树形结构,就要考虑使用组合模式。
3.2.5 装饰模式 Decorator
切入故事:罪恶的成绩单
简单描述:就是一个成绩单不好的情况下,做的一些其他工作,比如说告诉家长,最高分才**分,然后告诉家长自己多少分,可以免除惩罚。
装饰模式:动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比子类更为灵活。装饰类和被装饰类可以独立发展,而不会互相耦合。
比如,我们有一杯“茉莉茶”,现在加上一颗“柠檬”,那我们就有了一杯“柠檬茉莉花茶”。
装饰模式是继承关系的一个替代方案。装饰模式可以动态地扩展一个实现类的功能。
使用场景:
- 需要扩展一个类的功能,或给一个类增加附加的功能
- 需要动态地给一个对象增加功能,这些功能可以再动态地撤销
- 需要为一批兄弟类改装或加装功能
3.2.6 代理模式 Proxy
切入故事:事务,日志等
代理模式也叫委托模式,为其他对象提供一种代理以控制对这个对象的访问。
代理模式使用代理对象完成用户请求,屏蔽用户对真实对象的访问。现实世界的代理人被授权执行当事人的一些事宜,无需当事人出面,从第三方的角度看,似乎当事人并不存在,因为他只和代理人通信。而事实上代理人是要有当事人的授权,并且在核心问题上还需要请示当事人。
代理模式和适配器模式应该说很相像,但是他们的区别也很明显,代理模式和被代理者的接口是同一个,只是使用中客户访问不到被代理者,所以利用代理间接的访问,而适配器模式,是因为接口不同,为了让用户使用到统一的接口,把原先的对象通过适配器让用户统一的使用,大多数运用在代码维护的后期,或者借用第三方库的情况下 ,而外观模式,是大家经常无意中使用的,就是把错综复杂的子系统关系封装起来,然后提供一个简单的接口给客户使用,就类似于一个转接口。
使用场景:
为什么要使用代理?想想现实世界,打官司为什么要找个律师,因为不想参与中间过程的是是非非,只要完成自已的答辩就成,其他的比如事前调查、事后追查都由律师来搞定,这就是为了减轻你的负责。代理模式应用非常广泛,大到系统框架,企业平台,小到代码片段,事务处理,稍不留意就用到代理模式。
3.2.7 享元模式 Flyweight
切入故事:内存溢出,司空见惯
就是一种资源共享的连接池,资源池的理念。代表有 Tomcat 的连接池。
享元模式运用共享技术有效地支持大量细粒度的对象。享元模式通过共享技术实现相同或相似对象的重用。
如:内衣工厂有100种男士内衣、100中女士内衣,要求给每种内衣拍照。如果不使用享元模式则需要200个塑料模特;使用享元模式,只需要男女各1个模特。
又或者围棋游戏中,每个棋子都是白色或者黑色并且大小一样只是位置不同。如果每个棋子都用一个独立的对象存储,那么和上面一样会造成大量的浪费。
享元模式使系统使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。又称为轻量级模式。
使用场景:
- 系统中有大量的相似对象
- 细粒度的对象都具备较接近的外部状态,而且内部状态与环境无关,也就是说对象没有特定身份
- 需要缓冲池的场景
3.3 行为型模式 - 观摩(模)对(迭)策,责令解放(访),戒(介)忘台(态)
设计模式 | 简要说明 | 可改版的方面 |
---|---|---|
Chain of Responsibility 责任链模式 |
避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止 | 可满足请求的对象 |
Iterator 迭代器模式 |
提供一种方法顺序访问一个聚合对象中各个元素, 而又无须暴露该对象的内部表示。(电视换频道) | 如何访问、遍历聚集的元素 |
Mediator 中介者模式 |
将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。 | 与对象的接口 |
Memento 备忘录模式 |
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样就可以将该对象恢复到原先保存的状态 | 何时及哪些私有信息存储在对象之外 |
Observer 观察者模式 |
观察者模式定义了对象间的一种一对多依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新 | 信赖于另一对象的对象数量,信对象如何保持最新数据 |
State 状态模式 |
允许一个对象在其内部状态改变时改变它的行为。不同的状态下,拥有不同的行为 | 对象的状态 |
Strategy 策略模式 |
策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化 | 算法 |
Command 命令模式 |
将一个请求封装成一个对象,从而使得用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。(遥控器) | 何时及如何满足一个请求 |
Interpreter (类) 解释器模式 |
给定一个语言,定义它的语法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子 | 语言的语法和解释 |
Template Method(类) 模板方法 |
定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤 | 算法的步骤 |
Visitor 访问者模式 |
表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用与这些元素的新操作。即对于某个对象或者一组对象,不同的访问者,产生的结果不同,执行操作也不同 | 无须改变其类而可应用于对象的操作 |
3.3.1 责任链模式 Chain of Responsibility
切入故事:三从四德
书中用到了一个例子,古代的三从四德,未嫁从父,既嫁从夫,夫死从子。要求古代女性的生活琐事全部要请示到他人,依赖他人。例子中建立的几个类,其中父,夫,子有共同的字段,女性类塞值时候,给某个参数赋值,判断是某一参数,就执行某项操作。可以用case的形式,去区分。
又如工作中请假流程一样,需要一级一级审批走流程。或者申请报销费用,大致流程一般是,由申请人先填写申请单,然后交给领导审批,如果申请批准下来,领导会通知申请人审批通过,然后申请人去财务领取费用,如果没有批准下来,领导会通知申请人审批未通过,此事也就此作罢。
不同级别的领导,对于审批的额度是不一样的,比如,项目经理只能审批500元以内的申请;部门经理能审批1000元以内的申请;而总经理可以审核任意额度的申请。
负责链模式让多个对象都有可能接收请求,避免请求发送者与接收者耦合在一起,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。重点在这个“链”上。
责任链模式最显著的优点就是将请求和处理分开,请求者可以不用知道是谁处理的,处理者可以不用知道请求的全貌,两者解耦。避免超长链的出现。
3.3.2 迭代器模式 Iterator
切入故事:整理项目信息
描述:提供一种方法访问一个容器对象中各个元素,而又不暴露该对象的内部细节。
如看电视换频道,我们可以遍历所有的频道而不需要知道频道间是如何转换的。
典型的就是 Java中的迭代子,解决遍历问题,Java 自带的容器足够使用,不需要自己创建迭代器。
3.3.3 中介者模式 Mediator
切入故事:进销存管理是这个样子么?
中介者模式也叫调停模式,用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
比如系统和各个硬件,系统作为中介者,各个硬件作为同事者,当一个同事的状态发生改变的时候,不需要告诉其他每个硬件自己发生了变化,只需要告诉中介者系统,系统会通知每个硬件某个硬件发生了改变,其他的硬件会做出相应的变化;
使用场景:
- 调度中心,
- MVC的框架中(C 相当于中介者,减少了 M 和 C 之间的耦合)。
- 媒体网关,中介服务
中介者让各个系统之间可以独立,同时又可以交互。
3.3.4 备忘录模式 Memento
切入故事:如此追女孩子,你还不乐
原始状态的保留和恢复非常重要。每次尝试失败后都要恢复到原始状态。备忘录模式提供了一种弥补真实世界缺陷的方法,让“后悔药”在程序世界中真是可行。
定义:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样以后就可将该对象恢复到原先保存的状态。
通俗的说,备忘录模式就是一个对象的备份模式,提供一种程序数据的备份方法。
使用场景:
- 需要保存和恢复数据的相关状态场景
- 提供一个可回滚的操作,如 word 中的 Ctrl+Z 组合,退回按钮等等
- 需要监控的副本场景中
- 数据库连接的事务管理
3.3.5 观察者模式 Observer
切入故事:韩非子身边的卧底是谁派过来的
观察者模式也叫作发布订阅模式,定义对象间一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新。
使用场景:
- 文件系统/ATM取钱/广播收音机
- 关联行为场景。需要注意的是,关联行为是可拆分的,而不是“组合”关系
- 事件多级触发场景
- 跨系统的消息交换场景,如消息队列的处理机制
3.3.6 状态模式 State
切入故事:电梯
特定的状态:敞们状态,闭门状态,运行状态,停止状态。
状态模式定义当一个对象内在状态改变时,允许其改变行为,这个对象看起来像改变了类。状态模式的核心是封装,状态的变更引起了行为的变更。对于对象内部的状态,允许其在不同的状态下,拥有不同的行为,对状态单独封装成类。
使用场景:
- 行为随状态改变而改变的场景
- 条件、分支判断语句的替代者
3.3.7 策略模式 Strategy
切入故事:刘备江东娶妻,赵云容易么
解决问题的方案是,诸葛亮给了3个锦囊妙计。第一步,第二步,第三步最终解决问题。
策略模式,定义一组算法,将各个算法都封装起来,并使各个算法之间可以互相交换。策略模式让算法独立于使用它的客户而独立变化。
策略枚举是一个很好,方便的模式,但是它受到枚举类型的限制。比如四项运算,使用 if 判断出运算类型,可以使用对应的方法去做处理。
使用场景:
- 多个类只有在算法或行为上稍有不同的场景
- 算法需要自由切换的场景
- 需要屏蔽算法规则的场景
3.3.8 命令模式 Command
切入故事:项目经理的故事
命令模式是一个高内聚的模式,定义为将一个请求封装成一个对象,从而让你用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能。
有三种角色,Receive 接收者角色,Command 命令角色,Invoke调用者角色,Invoke调用者和Receive接收者分开了,扩展性也有很好保障。
命令模式将发出命令的责任和执行命令的责任分割开。请求的一方不必知道接收请求的一方的接口,也不必知道请求是怎么被接收的,以及操作是否被执行、何时被执行以及怎样被执行的。
这就好像以前电视没有遥控的时候,如果你想更换频道的话,必须跑到电视机前,手动按换台按钮,如果台很多的话,而你家的电视又比较古老,那么你就得更辛苦一点,得手动调出来其他频道。
现在有了遥控器,解放了我们,我们(相当于请求者),想换台,不用跑来跑去的调台,而是向电视机发出换台的请求,遥控器接收到我们的请求后,对电视发出具体的命令,是换台,关机,还是开机。这样,我们把请求传给遥控器,遥控器来具体执行需要哪些命令。这样,我们就和电视机解耦了。虽然结果是一样的,都是换了台,但是过程确截然不同。我们和电视之间没有任何联系了。而联系只存在于遥控器和具体的命令,以及命令和电视之间了。
使用场景:
- 只要你认为是命令的地方就可以采用命令模式,例如,在GUI开发中,一个按钮的点击是一个命令,可以采用命令模式;模拟DOS命令的时候,当然也要采用命令的模式;触发-反馈机制的处理等。
3.3.9 解释器模式 Interpreter
切入故事:四则运算
按照特定的语法进行解析的方案。
解释器定义给定一种语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。
例如:构造一个语言解释器,使得系统可以执行整数间的乘、除和求模运算。如用户输入表达式”2*3/4%5”。
就像JDK能把java语言按照语法语义,转换成机器可以理解的二进制流,处理后,返回给我们我们理解的数据流。
使用场景:
- 重复发生的问题可以使用解释器模式:多个应用服务器,每天产生大量的日志,需要对日志文件进行分析处理
- 一个简单语法需要解释的场景:解释器模式一般用来解析比较标准的字符集
3.3.10 模板方法 Template Method
切入故事:制造悍马
模板方法模式定义一个操作中的算法的框架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法结构即可重定义该算法的某些特定步骤。
在银行办理业务的时候,步骤一般是,排队取号、等待叫号、办理业务、为银行柜员打分,这个流程无论是办理存款、取款还是转账业务都是一样的。
为防止恶意操作,一般模板方法上都会加上final关键字不允许被覆盖。抽象模板中的基本方法计量设计为protected类型法则,不需要暴露的属性尽量不要暴露为protected类型。实现类若非必要,尽量不要扩大父类中的访问权限。
封装不变部分,扩展可变部分。提取公共部分代码,便于维护。行为由父类控制,子类实现。
使用场景为:
- 多个子类有公有的方法,并且实现逻辑基本相同时
- 重要、复杂核心的算法可以把核心算法设计为模板方法,周边的相关细节由子类实现
- 重构时,模板方法模式是一个经常的模式,把相同的代码抽取到父类中,然后通过钩子函数约束其行为
3.3.11 访问者模式 Visitor
切入故事:员工的隐私何在
之前有例子组合模式和迭代器模式,能够把一个公司的组织机构所有员工的信息。看看是否有员工“有人去世还拿退休金”,“拿高工资不干活的尸位素餐”等,现在需要把这些信息统计成报表,让领导看看有多严重。
访问者模式表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用与这些元素的新操作。即对于某个对象或者一组对象,不同的访问者,产生的结果不同,执行操作也不同。
应用场景:
- 一个对象结构包含很多类对象,它们有不同的接口,而你想对这些对象实施一些依赖于其具体类的操作,也就是说用迭代器模式已经不能胜任的情景
- 需要对一个对象结构中的对象进行很多不同并且不相关的操作,而你想避免让这些操作“污染”这些对象的类
四、设计模式PK --谁的地盘谁作主
4.1 创造类模式大PK
创造类模式包括工厂模式、建造者模式、抽象工厂模式、单例模式和原型模式,它们都能够提供对象的创建和管理职责。其中的单例模式和原型模式非常容易理解,单例模式是要保持在内存中只有一个对象,原型模式是要求通过复制的方式产生一个新的对象,这两个不容易混淆。剩下的就是工厂模式、抽象工厂模式和建造者模式,这三个之间有较多的相似性。
4.1.1 工厂模式 VS 建造者模式
工厂模式注重的是整体对象的创建方法,而建造者模式注重的是部件构建的过程,旨在通过一步一步地精确构造创建出一个复杂的对象。虽然最终执行结果一样,但是侧重点是不同的。在开发过程中如何选用工厂方法还是建造者模式,完全取决于设计时的意图,如果需要详细关注一个产品部件的生产、安装步骤,则选择建造者,否则选择工厂方法模式。
4.1.2 抽象工厂模式 VS 建造者模式
抽象工厂模式实现对产品家族的创建,一个产品家庭是这样的一系列产品:具有不同分类维度的产品组合,采用抽象工厂模式则是不需要关心构建过程,只关心什么产品由什么工厂生产即可。而建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品。
4.2 结构类模式大PK
结构类模式包括适配器模式、桥接模式、组合模式、装饰模式、外观模式、享元模式和代理模式。为什么叫结构类模式呢?因为它们都是通过组合类或对象产生更大结构以适应更高层次的逻辑需求。
4.2.1 代理模式 VS 装饰模式
装饰模式就是代理模式的一个特殊应用,两者的共同点是都具有相同的接口,不同点则是代理模式着重对代理过程的控制,而装饰模式则是对类的功能进行加强或减弱,它着重类的功能变化。
4.2.2 装饰模式 VS 适配器模式
装饰模式和适配器模式在通用类图上没有太多的相似点,差别比较大,但是它们的功能有相似的地方:都是包装作用,都是通过委托方式实现其功能。不同点是:装饰模式包装的是自已的兄弟类,隶属于同一个家族,适配器模式则修饰非血缘关系类,把一个非本家族的对象伪装成本家族的对象,注意是伪装,因此它的本质还是非相同接口的对象。
4.3 行为类模式大PK
4.3.1 命令模式 VS 策略模式
命令模式和策略模式的类图确实很相似,只是命令模式多了一个接收者角色。它们虽然同为行为类模式,但是两者的区别还是比较明显的。策略模式的意图是封装算法,算法之间没有交互,以达到算法可以自由切换的目的;而状态模式封装的是不同的状态,以达到状态切换行为随之发生改变的目的。
4.3.2 状态模式 VS 策略模式
状态模式和策略模式的类图非常相似,属亲兄弟,都提供了一个封装的方法,是高扩展性的设计模式。但是两者的区别还是明显的。策略模式封装的是不同的算法,它认为“算法”已经是一个完整的、不可拆分的原子业务,其意图是让这些算法独立,并且可以相互替换,让行为的变化独立于拥有行为的客户。而命令模式则是对动作的解耦,把一个动作的执行分为执行对象、执行行为,让两者相互独立而不相互影响。
4.4 跨战区PK
4.4.1 外观模式 VS 中介者模式
外观模式为复杂的子系统提供一个统一的访问界面,它定义的是一个高层接口,该接口使得子系统更加容易使用,避免外部模式深入到子系统内部而产生与子系统内部细节耦合的问题。中介者模式使用一个中介对旬来封装一系列同事对象的交互行为,它使用各对象之间不再显式引用,从而使其耦合松散,建立一个可扩展的应用架构。
五、设计模式混编 --完美世界
设计模式不是完美的,每个设计模式都或多或少的有一些缺陷,但是可以使用设计模式混编来避免这种缺陷。书中介绍了几种典型设计模式的混编,分别是:
- 命令模式+责任链模式+模板方法模式,实现UNIX命令移植到Windows系统
- 工厂方法模式+策略模式+门面模式,实现一个小型交易系统
- 观察者模式+中介者模式,讲解按钮OnClick的触发原理
作者使用设计模式组合,开发出了高内聚、低耦合、对修改关闭,对拓展开放等优点集一身的非常健壮的程序,说明了23种设计模式并不是固定写死、不能共存的,开发人员应该深入了解这些设计模式,对业务需求也要有深刻理解,这样才能拥抱变化,从容处理。
设计模式不是工具,它是软件开发的哲学,能知道你如何去设计一个优秀的架构、编写一段健壮的代码、解决一个负责的需求。因为它是指导思想,你可以在此基础上自由发挥,甚至是自己设计出一套设计模式。
六、总结
23种设计模式中有些可能有较多的相似性,但每个模式都有其特性,开发者需要根据业务需求选择,还可以对多个设计模式进行混合使用。设计模式只是提供了一个解决问题的意图,而没有具体定出一个设计模式必须这样实现,必须是这样的代码,灵活运用模式才是其根本。
有人说设计模式很好。错。设计模式其实是中性的,无所谓好坏。
有人说设计模式很复杂。错。有的模式其实非常简单,比如模板方法模式。有些比较复杂,比如访问者模式。平均而言,简单的模式比复杂的模式用的更多。
有人说小项目中没必要设计模式。多小的项目算小项目呢,其实只要经过良好的设计一个项目也可以不包含任何设计模式。但是也可能包含多个设计模式,但是跳过比较复杂的结构和行为模式,比如访问者模式或者责任链模式。
有人说使用设计模式需要熟练的开发人员。对于相对复杂的模式来说这个说法是部分成立的,但是绝对不是所有模式。
有人说使用设计模式会提升项目成本。不一定,如果设计模式使用的恰当的话。但是如果缺乏设计或者设计不良好,或者错误的(包括滥用)应用了设计模式,那肯定是会提升项目成本。
有人说设计模式会使得代码干净整洁。其实所有的代码都应该保持干净整洁,无论是否显式地应用了设计模式。 所以,设计模式不是万能的,它是中性的,不是所谓的灵丹妙药,如果不恰当的使用,反而会带来问题。