设计模式GOF23
• 将设计者的思维融入大家的学习和工作中,更高层次的思考!
• 创建型模式:
单例模式、工厂模式、抽象工厂模式、建造者模式、原型模式。
• 结构型模式:
适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模 式。
• 行为型模式:
模版方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模 式、解释器模式、状态模式、策略模式、职责链模式、访问者模式。
单例模式
核心作用:
– 保证一个类只有一个实例,并且提供一个访问该实例的全局访问点。
常见应用场景:
- Windows的Task Manager(任务管理器)就是很典型的单例模式
- windows的Recycle Bin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
- 项目中,读取配置文件的类,一般也只有一个对象。没有必要每次使用配置文件数据,每次new一个对象去读取。
- 网站的计数器,一般也是采用单例模式实现,否则难以同步。
- 应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作 ,否则内容不好追加。
- 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。
- 操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。
- Application 也是单例的典型应用(Servlet编程中会涉及到)
- 在Spring中,每个Bean默认就是单例的,这样做的优点是Spring容器可以管理
- 在servlet编程中,每个Servlet也是单例
- 在spring MVC框架/struts1框架中,控制器对象也是单例
单例模式的优点:
- 由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要 比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动 时直接产生一个单例对象,然后永久驻留内存的方式来解决
- 单例模式可以在系统设置全局的访问点,优化环共享资源访问,例如可以设计 一个单例类,负责所有数据表的映射处理
常见的五种单例模式实现方式:
- 主要:
• 饿汉式(线程安全,调用效率高。 但是,不能延时加载。)
• 懒汉式(线程安全,调用效率不高。 但是,可以延时加载。) - 其他:
• 双重检测锁式(由于JVM底层内部模型原因,偶尔会出问题。不建议使用)
• 静态内部类式(线程安全,调用效率高。 但是,可以延时加载)
• 枚举单例(线程安全,调用效率高,不能延时加载)
饿汉式实现(单例对象立即加载)
饿汉式单例模式代码中,static变量会在类装载时初始化,此时也不会涉及多个线程对象访问该对象的问 题。虚拟机保证只会装载一次该类,肯定不会发生并发访问的问题。因此,可以省略synchronized关键字。
• 问题:如果只是加载本类,而不是要调用getInstance(),甚至永远没有调用,则会造成资源浪费!
懒汉式实现(单例对象延迟加载)
• 要点: – lazy load! 延迟加载, 懒加载! 真正用的时候才加载!
• 问题: – 资源利用率高了。但是,每次调用getInstance()方法都要同步,并发 效率较低。
双重检测锁实现
这个模式将同步内容下方到if内部,提高了执行的效率 不必每次获取对象时都进行同步,只有第一次才同步 创建了以后就没必要了。
• 问题:
• 由于编译器优化原因和JVM底层内部模型原因, 偶尔会出问题。不建议使用。
静态内部类实现方式(也是一种懒加载方式)
要点:
- 外部类没有static属性,则不会像饿汉式那样立即加载对象。
- 只有真正调用getInstance(),才会加载静态内部类。加载类时是线程 安全的。 instance是static final 类型,保证了内存中只有这样一个实例存在,而且只能被赋值一次,从而保证了线程安全性.
- 兼备了并发高效调用和延迟加载的优势!
问题: - 反射可以破解上面几种(不包含枚举式)实现方式!(可以在构造方法中手动 抛出异常控制)
- 反序列化可以破解上面几种((不包含枚举式))实现方式!
- 可以通过定义readResolve()防止获得不同对象。
- 反序列化时,如果对象所在类定义了readResolve(),(实际是一种回调), 定义返回哪个对象。
使用枚举实现单例模式
• 优点:
实现简单
枚举本身就是单例模式。由JVM从根本上提供保障!避免通过反射和反序列化的漏洞!
• 缺点:
无延迟加载
常见的五种单例模式在多线程环境下的效率测试
大家只要关注相对值即可。在不同的环境下不同的程序测得值完全不一样
CountDownLatch
同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一 个或多个线程一直等待。
• countDown() 当前线程调此方法,则计数减一(建议放在 finally里执行)
• await(), 调用此方法会一直阻塞当前线程,直到计时器的值为0
使用myeclipse的UML插件画出类图
大家也可以使用:rational rose 、 metamill等。
常见的五种单例模式实现方式
主要:
• 饿汉式(线程安全,调用效率高。 但是,不能延时加载。)
• 懒汉式(线程安全,调用效率不高。 但是,可以延时加载。)
其他:
• 双重检测锁式(由于JVM底层内部模型原因,偶尔会出问题。不建议使用)
• 静态内部类式(线程安全,调用效率高。 但是,可以延时加载)
• 枚举式(线程安全,调用效率高,不能延时加载。并且可以天然的防止反射和反序列 化漏洞!)
如何选用?
单例对象 占用 资源 少,不需要 延时加载:
• 枚举式 好于 饿汉式
单例对象 占用 资源 大,需要 延时加载:
• 静态内部类式 好于 懒汉式
工厂模式:
实现了创建者和调用者的分离。
详细分类:
• 简单工厂模式
• 工厂方法模式
• 抽象工厂模式
面向对象设计的基本原则:
- OCP(开闭原则,Open-Closed Principle):一个软件的实体应当对扩展开 放,对修改关闭。
- DIP(依赖倒转原则,Dependence Inversion Principle):要针对接口编程, 不要针对实现编程。
- LoD(迪米特法则,Law of Demeter):只与你直接的朋友通信,而避免和 陌生人通信。
核心本质:
- 实例化对象,用工厂方法代替new操作。
- 将选择实现类、创建对象统一管理和控制。从而将调用者跟我们的实 现类解耦。
工厂模式:
- 简单工厂模式
• 用来生产同一等级结构中的任意产品。(对于增加新的产品,需要修改已 有代码) - 工厂方法模式
• 用来生产同一等级结构中的固定产品。(支持增加任意产品) - 抽象工厂模式
• 用来生产不同产品族的全部产品。(对于增加新的产品,无能为力;支持 增加产品族)
不使用简单工厂的情况
在调用的时候 如果没有用工厂模式
简单工厂模式
• 要点:
简单工厂模式也叫静态工厂模式,就是工厂类一般是使用静态方法, 通过接收的参数的不同来返回不同的对象实例。
对于增加新产品无能为力!不修改代码的话,是无法扩展的。
工厂方法模式
工厂方法模式要点:
为了避免简单工厂模式的缺点,不完全满足OCP。
工厂方法模式和简单工厂模式最大的不同在于,简单工厂模式只有一个(对于一个项目 或者一个独立模块而言)工厂类,而工厂方法模式有一组实现了相同接口的工厂类。
简单工厂模式和工厂方法模式PK:
- 结构复杂度
从这个角度比较,显然简单工厂模式要占优。简单工厂模式只需一个工厂类,而工厂方法模式的工厂类随着产品类个 数增加而增加,这无疑会使类的个数越来越多,从而增加了结构的复杂程度。 - 代码复杂度
代码复杂度和结构复杂度是一对矛盾,既然简单工厂模式在结构方面相对简洁,那么它在代码方面肯定是比工厂方法 模式复杂的了。简单工厂模式的工厂类随着产品类的增加需要增加很多方法(或代码),而工厂方法模式每个具体工 厂类只完成单一任务,代码简洁。 - 客户端编程难度
工厂方法模式虽然在工厂类结构中引入了接口从而满足了OCP,但是在客户端编码中需要对工厂类进行实例化。而简 单工厂模式的工厂类是个静态类,在客户端无需实例化,这无疑是个吸引人的优点。 - 管理上的难度
这是个关键的问题。
我们先谈扩展。众所周知,工厂方法模式完全满足OCP,即它有非常良好的扩展性。那是否就说明了简单工厂模式就 没有扩展性呢?答案是否定的。简单工厂模式同样具备良好的扩展性——扩展的时候仅需要修改少量的代码(修改工 厂类的代码)就可以满足扩展性的要求了。尽管这没有完全满足OCP,但我们不需要太拘泥于设计理论,要知道, sun提供的java官方工具包中也有想到多没有满足OCP的例子啊。
然后我们从维护性的角度分析下。假如某个具体产品类需要进行一定的修改,很可能需要修改对应的工厂类。当同时 需要修改多个产品类的时候,对工厂类的修改会变得相当麻烦(对号入座已经是个问题了)。反而简单工厂没有这些 麻烦,当多个产品类需要修改是,简单工厂模式仍然仅仅需要修改唯一的工厂类(无论怎样都能改到满足要求吧?大 不了把这个类重写)。
• 根据设计理论建议:工厂方法模式。但实际上,我们一般都用简单工厂模式。
抽象工厂模式
• 抽象工厂模式
用来生产不同产品族的全部产品。(对于增加新的产品,无能为力; 支持增加产品族)
抽象工厂模式是工厂方法模式的升级版本,在有多个业务品种、业务 分类时,通过抽象工厂模式产生需要的对象是一种非常好的解决方式。
• 类图
工厂模式要点:
-
简单工厂模式(静态工厂模式)
• 虽然某种程度不符合设计原则,但实际使用最多。 -
工厂方法模式
• 不修改已有类的前提下,通过增加新的工厂类实现扩展。 -
抽象工厂模式
• 不可以增加产品,可以增加产品族! -
应用场景
JDK中Calendar的getInstance方法
JDBC中Connection对象的获取
Hibernate中SessionFactory创建Session
spring中IOC容器创建管理bean对象
XML解析时的DocumentBuilderFactory创建解析器对象
反射中Class对象的newInstance()
建造者模式
• 场景:
我们要建造一个复杂的产品。比如:神州飞船,Iphone。这个复杂的产品的创建。有这样 一个问题需要处理:
• 装配这些子组件是不是有个步骤问题?
实际开发中,我们所需要的对象构建时,也非常复杂,有很多步骤需要处理时。
• 建造模式的本质:
分离了对象子组件的单独构造(由Builder来负责)和装配(由Director负责)。 从而可以构 造出复杂的对象。这个模式适用于:某个对象的构建过程复杂的情况下使用。
由于实现了构建和装配的解耦。不同的构建器,相同的装配,也可以做出不同的对象; 相同的构建器,不同的装配顺序也可以做出不同的对象。也就是实现了构建算法、装配 算法的解耦,实现了更好的复用。
• 构建“尚学堂牌”神舟飞船的示例
• 开发中应用场景:
StringBuilder类的append方法
SQL中的PreparedStatement
JDOM中,DomBuilder、SAXBuilder
原型模式prototype
• 场景:
- 思考一下:克隆技术是怎么样的过程? 克隆羊多利大家还记得吗?
- javascript语言中的,继承怎么实现?那里面也有prototype,大家还记得吗?
• 原型模式:
- 通过new产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。
- 就是java中的克隆技术,以某个对象为原型,复制出新的对象。显然,新的对象具备原型对象的特点
- 优势有:效率高(直接克隆,避免了重新执行构造过程步骤) 。
- 克隆类似于new,但是不同于new。new创建新的对象属性采用的是默认值。克隆出的 对象的属性值完全和原型对象相同。并且克隆出的新对象改变不会影响原型对象。然后, 再修改克隆对象的值。
• 原型模式实现:
- Cloneable接口和clone方法
- Prototype模式中实现起来最困难的地方就是内存复制操作,所幸在Java中提供了 clone()方法替我们做了绝大部分事情。
• 注意用词:克隆和拷贝一回事!
• 浅克隆存在的问题
被复制的对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用都 仍然指向原来的对象。
• 深克隆如何实现?
- 深克隆把引用的变量指向复制过的新对象,而不是原有的被引用的对象。
- 深克隆:让已实现Clonable接口的类中的属性也实现Clonable接口
- 基本数据类型和String能够自动实现深度克隆(值的复制)
• 利用序列化和反序列化技术实现深克隆!
• 短时间大量创建对象时,原型模式和普通new方式效率测试
• 开发中的应用场景
原型模式很少单独出现,一般是和工厂方法模式一起出现,通过clone 的方法创建一个对象,然后由工厂方法提供给调用者。
• spring中bean的创建实际就是两种:单例模式和原型模式。(当然,原型 模式需要和工厂模式搭配起来)
创建型模式的总结
• 创建型模式:都是用来帮助我们创建对象的!
单例模式
• 保证一个类只有一个实例,并且提供一个访问该实例的全局访问点。
工厂模式
• 简单工厂模式
用来生产同一等级结构中的任意产品。(对于增加新的产品,需要修改已有代码)
• 工厂方法模式
用来生产同一等级结构中的固定产品。(支持增加任意产品)
• 抽象工厂模式
用来生产不同产品族的全部产品。(对于增加新的产品,无能为力;支持增加产品族)
建造者模式
• 分离了对象子组件的单独构造(由Builder来负责)和装配(由Director负责)。 从而可 以构造出复杂的对象。
原型模式
• 通过new产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式
结构模式
• 结构型模式:
核心作用:是从程序的结构上实现松耦合,从而可以扩大整体的类结 构,用来解决更大的问题。
分类:
• 适配器模式、代理模式、桥接模式、 装饰模式、组合模式、外观模式、享元模式
结构型模式汇总
适配器adapter模式
• 生活中的场景
• 什么是适配器模式?
将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原 本由于接口不兼容而不能一起工作的那些类可以在一起工作。
• 模式中的角色
目标接口(Target):客户所期待的接口。目标可以是具体的或抽象 的类,也可以是接口。
需要适配的类(Adaptee):需要适配的类或适配者类。
适配器(Adapter):通过包装一个需要适配的对象,把原接口转换成 目标接口。
• 下面的场景,如何解决?
• 适配器模式解决方案:
• 类适配器
• 对象适配器
• 工作中的场景
经常用来做旧系统改造和升级
如果我们的系统开发之后再也不需要维护,那么很多模式都是没必要 的,但是不幸的是,事实却是维护一个系统的代价往往是开发一个系 统的数倍。
• 我们学习中见过的场景
java.io.InputStreamReader(InputStream)
java.io.OutputStreamWriter(OutputStream)
代理模式
代理模式(Proxy pattern):
核心作用:
• 通过代理,控制对对象的访问! 可以详细控制访问某个(某类)对象的方法,在调用这个方法前做前置处理,调用这个方法后 做后置处理。(即:AOP的微观实现!)
AOP(Aspect Oriented Programming面向切面编程)的核心实现机制!
• 代理模式(Proxy pattern):
核心角色:
• 抽象角色
定义代理角色和真实角色的公共对外方法
• 真实角色
实现抽象角色,定义真实角色所要实现的业务逻辑, 供代理角色调用。
关注真正的业务逻辑!
• 代理角色
实现抽象角色,是真实角色的代理,通过真实角色 的业务逻辑方法来实现抽象方法,并可以附加 自己的操作。
将统一的流程控制放到代理角色中处理!
• 应用场景:
安全代理:屏蔽对真实角色的直接访问。
远程代理:通过代理类处理远程方法调用(RMI)
延迟加载:先加载轻量级的代理对象,真正需要再加载真实对象。
• 比如你要开发一个大文档查看软件,大文档中有大的图片,有可能一个图片有100MB,在打开文件时不可能将所有的图片都显示出来,这样就可以 使用代理模式,当需要查看图片时,用proxy来进行大图片的打开。
• 分类:
静态代理(静态定义代理类)
动态代理(动态生成代理类)
• JDK自带的动态代理
• javaassist字节码操作库实现
• CGLIB
• ASM(底层使用指令,可维护性较差)
静态代理(static proxy)
• 静态代理(静态定义代理类)
动态代理(dynamic proxy)
• 动态代理(动态生成代理类)
• JDK自带的动态代理
• javaassist字节码操作库实现
• CGLIB • ASM(底层使用指令,可维护性较差)
• 动态代理相比于静态代理的优点
抽象角色中(接口)声明的所以方法都被转移到调用处理器一个集中的方 法中处理,这样,我们可以更加灵活和统一的处理众多的方法。
动态代理(JDK自带的实现)
• JDK自带的动态代理
java.lang.reflect.Proxy
• 作用:动态生成代理类和对象
java.lang.reflect.InvocationHandler(处理器接口)
• 可以通过invoke方法实现对真实角色的代理访问。
• 每次通过Proxy生成代理类对象对象时都要指定对应的处理器对象
• 开发框架中应用场景:
- struts2中拦截器的实现
- 数据库连接池关闭处理
- Hibernate中延时加载的实现
- mybatis中实现拦截器插件
- AspectJ的实现
- spring中AOP的实现
• 日志拦截
• 声明式事务处理 - web service
- RMI远程方法调用
- …
- 实际上,随便选择一个技术框架都会用到代理模式!!
面向切面编程AOP介绍
• AOP(Aspect-Oriented Programming,面向切面的编程) – 它是可以通过预编译方式和运行期动态代理实现在不修改源代码的情 况下给程序动态统一添加功能的一种技术。它是一种新的方法论,它 是对传统OOP编程的一种补充。
常用术语:
- 切面(Aspect):其实就是共有功能的实现。
- 通知(Advice):是切面的具体实现。
- 连接点(Joinpoint):就是程序在运行过程中能够插入切面的地点。
- 切入点(Pointcut):用于定义通知应该切入到哪些连接点上。
- 目标对象(Target):就是那些即将切入切面的对象,也就是那些被通知的对象
- 代理对象(Proxy):将通知应用到目标对象之后被动态创建的对象。
- 织入(Weaving):将切面应用到目标对象从而创建一个新的代理对象的过程。
开源的AOP框架
AspectJ
桥接模式(bridge)
• 场景分析
商城系统中常见的商品分类,以电脑为类,如何良好的处理商品分类销售的问题? – 这个场景中有两个变化的维度:电脑类型、电脑品牌。
• 桥接模式核心要点:
处理多层继承结构,处理多维度变化的场景,将各个维度设计成独立 的继承结构,使各个维度可以独立的扩展在抽象层建立关联。
• 桥接模式总结:
桥接模式可以取代多层继承的方案。 多层继承违背了单一职责原则, 复用性较差,类的个数也非常多。桥接模式可以极大的减少子类的个 数,从而降低管理和维护的成本。
桥接模式极大的提高了系统可扩展性,在两个变化维度中任意扩展一 个维度,都不需要修改原有的系统,符合开闭原则。
• 桥接模式实际开发中应用场景
JDBC驱动程序
AWT中的Peer架构
银行日志管理:
• 格式分类:操作日志、交易日志、异常日志
• 距离分类:本地记录日志、异地记录日志
人力资源系统中的奖金计算模块:
• 奖金分类:个人奖金、团体奖金、激励奖金。
• 部门分类:人事部门、销售部门、研发部门。
OA系统中的消息处理:
• 业务类型:普通消息、加急消息、特急消息
• 发送消息方式:系统内消息、手机短信、邮件
组合模式(composite)
• 使用组合模式的场景:
把部分和整体的关系用树形结构来表示,从而使客户端可以使用统一的方式处理部分对 象和整体对象。
• 组合模式核心:
抽象构件(Component)角色: 定义了叶子和容器构件的共同点
叶子(Leaf)构件角色:无子节点
容器(Composite)构件角色: 有容器特征,可以包含子节点
• 组合模式工作流程分析:
组合模式为处理树形结构提供了完美的解决方案,描述了如何将容器和叶子进行递归组 合,使得用户在使用时可以一致性的对待容器和叶子。
当容器对象的指定方法被调用时,将遍历整个树形结构,寻找也包含这个方法的成员, 并调用执行。其中,使用了递归调用的机制对整个结构进行处理。
• 使用组合模式,模拟杀毒软件架构设计
• 开发中的应用场景:
操作系统的资源管理器
GUI中的容器层次图
XML文件解析
OA系统中,组织结构的处理
Junit单元测试框架
• Junit单元测试框架底层设计
底层设计就是典型的组合模式,TestCase(叶子)、TestUnite(容 器)、Test接口(抽象)
装饰模式(decorator)
• 实现细节:
Component抽象构件角色:
• 真实对象和装饰对象有相同的接口。这样,客户端对象就能够以与真实对象相同的方式同装饰 对象交互。
ConcreteComponent 具体构件角色(真实对象):
• io流中的FileInputStream、FileOutputStream
Decorator装饰角色: • 持有一个抽象构件的引用。装饰对象接受所有客户端的请求,并把这些请求转发给真实的对象 。这样,就能在真实对象调用前后增加新的功能。
ConcreteDecorator具体装饰角色:
• 负责给构件对象增加新的责任。
• 开发中使用的场景:
- IO中输入流和输出流的设计
- Swing包中图形界面构件功能
- Servlet API 中提供了一个request对象的Decorator设计模式的默认实 现类HttpServletRequestWrapper,HttpServletRequestWrapper 类,增强了request对象的功能。
- Struts2中,request,response,session对象的处理
• IO流实现细节:
Component抽象构件角色:
• io流中的InputStream、OutputStream、Reader、Writer
ConcreteComponent 具体构件角色:
• io流中的FileInputStream、FileOutputStream
Decorator装饰角色:
• 持有一个抽象构件的引用:io流中的FilterInputStream、FilterOutputStream
ConcreteDecorator具体装饰角色:
• 负责给构件对象增加新的责任。Io流中的BufferedOutputStream、BufferedInputStream等。
• 总结:
装饰模式(Decorator)也叫包装器模式(Wrapper)
装饰模式降低系统的耦合度,可以动态的增加或删除对象的职责,并 使得需要装饰的具体构建类和具体装饰类可以独立变化,以便增加新 的具体构建类和具体装饰类。
• 优点
扩展对象功能,比继承灵活,不会导致类个数急剧增加
可以对一个对象进行多次装饰,创造出不同行为的组合,得到功能更 加强大的对象
具体构建类和具体装饰类可以独立变化,用户可以根据需要自己增加 新的具体构件子类和具体装饰子类。
• 缺点
产生很多小对象。大量小对象占据内存,一定程度上影响性能。
装饰模式易于出错,调试排查比较麻烦。
• 装饰模式和桥接模式的区别:
两个模式都是为了解决过多子类对象问题。但他们的诱因不一样。桥 模式是对象自身现有机制沿着多个维度变化,是既有部分不稳定。装 饰模式是为了增加新的功能。
外观模式(facade)
• 迪米特法则(最少知识原则):
一个软件实体应当尽可能少的与其他实体发生相互作用。
• 外观模式核心:
为子系统提供统一的入口。封装子系统的复杂性,便于客户端调用。
• 基本案例
注册公司流程(不使用外观模式)
• 基本案例
注册公司流程(使用外观模式)
• 开发中常见的场景
频率很高。哪里都会遇到。各种技术和框架中,都 有外观模式的使用。如:
• JDBC封装后的,commons提供的DBUtils类, Hibernate提供的工具类、Spring JDBC工具类等
享元模式(FlyWeight)
• 场景:
内存属于稀缺资源,不要随便浪费。如果有很多个完全相同或相似的 对象,我们可以通过享元模式,节省内存。
• 核心:
享元模式以共享的方式高效地支持大量细粒度对象的重用。
享元对象能做到共享的关键是区分了内部状态和外部状态。
• 内部状态:可以共享,不会随环境变化而改变
• 外部状态:不可以共享,会随环境变化而改变
• 享元模式实现:
FlyweightFactory享元工厂类
• 创建并管理享元对象,享元池一般设计成键值对
FlyWeight抽象享元类
• 通常是一个接口或抽象类,声明公共方法,这些方法可以向外界提供对象 的内部状态,设置外部状态。
ConcreteFlyWeight具体享元类
• 为内部状态提供成员变量进行存储
UnsharedConcreteFlyWeight非共享享元类
• 不能被共享的子类可以设计为非共享享元类
• 享元模式实现的UML图
• 享元模式开发中应用的场景:
享元模式由于其共享的特性,可以在任何“池”中操作, 比如:线程池、数据库连接池。
String类的设计也是享元模式
• 优点
极大减少内存中对象的数量
相同或相似对象内存中只存一份,极大的节约资源,提高系统性能
外部状态相对独立,不影响内部状态
• 缺点
模式较复杂,使程序逻辑复杂化
为了节省内存,共享了内部状态,分离出外部状态,而读取外部状态 使运行时间变长。用时间换取了空间。
• 结构型模式汇总
行为型模式
责任链模式
• 场景:
公司里面,报销个单据需要经过流程:
• 申请人填单申请,申请给经理
• 小于1000,经理审查。
• 超过1000,交给总经理审批。
• 总经理审批通过
公司里面,请假条的审批过程:
• 如果请假天数小于3天,主任审批
• 如果请假天数大于等于3天,小于10天,经理审批
• 如果大于等于10天,小于30天,总经理审批
• 如果大于等于30天,提示拒绝
公司里面,SCM(Supply Chain Management供应链管理)系统中, 采购审批子系统的设计:
• 采购金额小于5万,主任审批
• 采购金额大于等于5万,小于10万,经理审批
• 采购金额大于等于10万,小于20万,副总经理审批
• 采购金额大于等于20万,总经理审批
• 添加新的处理对象:
由于责任链的创建完全在客户端,因此新增新的具体处理者对原有类 库没有任何影响,只需添加新的类,然后在客户端调用时添加即可。 符合开闭原则。
案例:
• 我们可以在请假处理流程中,增加新的“副总经理”角色,审批大于等于 10天,小于20天的请假。审批流程变为:
① 如果请假天数小于3天,主任审批
② 如果请假天数大于等于3天,小于10天,经理审批
③ 大于等于10天,小于20天的请假,副总经理审批
④ 如果大于等于20天,小于30天,总经理审批
⑤ 如果大于等于30天,提示拒绝
• 链表方式定义职责链(上一个案例)
• 非链表方式实现职责链
通过集合、数组生成职责链更加实用!实际上,很多项目中,每个具 体的Handler并不是由开发团队定义的,而是项目上线后由外部单位追 加的,所以使用链表方式定义COR链就很困难。
• 开发中常见的场景:
Java中,异常机制就是一种责任链模式。一个try可以对应多个catch, 当第一个catch不匹配类型,则自动跳到第二个catch.
Javascript语言中,事件的冒泡和捕获机制。Java语言中,事件的处理 采用观察者模式。
Servlet开发中,过滤器的链式处理
Struts2中,拦截器的调用也是典型的责任链模式
迭代器模式(iterator)
• 场景:
提供一种可以遍历聚合对象的方式。又称为:游标cursor模式
聚合对象:存储数据
迭代器:遍历数据
• 结构:
聚合对象:存储数据
迭代器:遍历数据
• 基本案例:
实现正向遍历的迭代器
实现逆向遍历的迭代器
• 开发中常见的场景:
JDK内置的迭代器(List/Set)
中介者模式(Mediator)
• 中介大家熟悉吗?
• 场景(中介大家熟悉吗?房产中介?):
假如没有总经理。下面三个部门:财务部、市场部、研发部。财务部要发工资,让大家 核对公司需要跟市场部和研发部都通气;市场部要接个新项目,需要研发部处理技术、 需要财务部出资金。市场部跟各个部门打交道。 虽然只有三个部门,但是关系非常乱。
实际上,公司都有总经理。各个部门有什么事情都通报到总经理这里,总经理再通知各 个相关部门。
这就是一个典型的“中介者模式” 总经理起到一个中介、协调的作用。
• 中介者模式的本质:
解耦多个同事对象之间的交互关系。每个对象都持有中介者对象的引 用,只跟中介者对象打交道。我们通过中介者对象统一管理这些交互 关系
• 开发中常见的场景:
MVC模式(其中的C,控制器就是一个中介者对象。M和V都和他打交 道)
窗口游戏程序,窗口软件开发中窗口对象也是一个中介者对象
图形界面开发GUI中,多个组件之间的交互,可以通过引入一个中介者 对象来解决,可以是整体的窗口对象或者DOM对象
Java.lang.reflect.Method#invoke()
命令模式(command)
• 开发中常见的场景:
Struts2中,action的整个调用过程中就有命令模式。
数据库事务机制的底层实现
命令的撤销和恢复
解释器模式(Interpreter)
• 介绍:
是一种不常用的设计模式
用于描述如何构成一个简单的语言解释器,主要用于使用面向对象语言开发的 编译器和解释器设计。
当我们需要开发一种新的语言时,可以考虑使用解释器模式。
尽量不要使用解释器模式,后期维护会有很大麻烦。在项目中,可以使用 Jruby,Groovy、java的js引擎来替代解释器的作用,弥补java语言的不足。
• 开发中常见的场景:
EL表达式式的处理
正则表达式解释器
SQL语法的解释器
数学表达式解析器
• 如现成的工具包:Math Expression String Parser、Expression4J等。
MESP的网址: http://sourceforge.net/projects/expression-tree/
Expression4J的网址: http://sourceforge.net/projects/expression4j/
访问者模式(Visitor)
• 模式动机:
对于存储在一个集合中的对象,他们可能具有不同的类型(即使有一个公共的接 口),对于该集合中的对象,可以接受一类称为访问者的对象来访问,不同的访 问者其访问方式也有所不同。
• 定义:
表示一个作用于某对象结构中的各元素的操作,它使我们可以在不改变个元素 的类的前提下定义作用于这些元素的新操作。
• 开发中的场景(应用范围非常窄,了解即可):
XML文档解析器设计 – 编译器的设计 – 复杂集合对象的处理
策略模式(strategy)
• 场景:
某个市场人员接到单后的报价策略(CRM系统中常见问题)。报价策略 很复杂,可以简单作如下分类:
• 普通客户小批量报价
• 普通客户大批量报价
• 老客户小批量报价
• 老客户大批量报价
具体选用哪个报价策略,这需要根据实际情况来确定。这时候,我们 采用策略模式即可。
• 策略模式
策略模式对应于解决某一个问题的一个算法族,允许用户从该算法族 中任选一个算法解决某一问题,同时可以方便的更换算法或者增加新 的算法。并且由客户端决定调用哪个算法。
• 本质:
分离算法,选择实现。
• 开发中常见的场景:
JAVASE中GUI编程中,布局管理
Spring框架中,Resource接口,资源访问策略
javax.servlet.http.HttpServlet#service()
模板方法模式(template method)
状态模式(state)
• 核心:
用于解决系统中复杂对象的状态转换以及不同状态下行为的封装问题
• 结构:
Context环境类
• 环境类中维护一个State对象,他是定义了当前的状态。
State抽象状态类
ConcreteState具体状态类
• 每一个类封装了一个状态对应的行为
• 开发中常见的场景:
银行系统中账号状态的管理
OA系统中公文状态的管理
酒店系统中,房间状态的管理
线程对象各状态之间的切换
观察者模式(Observer)
• 场景:
聊天室程序的创建。服务器创建好后,A,B,C三个客户端连上来公开聊 天。A向服务器发送数据,服务器端聊天数据改变。我们希望将这些聊 天数据分别发给其他在线的客户。也就是说,每个客户端需要更新服 务器端得数据。
网站上,很多人订阅了”java主题”的新闻。当有这个主题新闻时,就 会将这些新闻发给所有订阅的人。
大家一起玩CS游戏时,服务器需要将每个人的方位变化发给所有的客 户。
上面这些场景,我们都可以使用观察者模式来处理。我们 可以把多个订阅者、客户称之为观察者; 需要同步给多个订阅 者的数据封装到对象中,称之为目标。
备忘录模式(memento)
• 核心
就是保存某个对象内部状态的拷贝,这样以后就可以将该对象恢复到 原先的状态。
• 结构:
源发器类Originator
备忘录类Memento
负责人类CareTaker
设计模式汇总
GOF23中设计模式一览表
创建型模式的总结
• 创建型模式:都是用来帮助我们创建对象的!
单例模式
• 保证一个类只有一个实例,并且提供一个访问该实例的全局访问点。
工厂模式
• 简单工厂模式
用来生产同一等级结构中的任意产品。(对于增加新的产品,需要修改已有代码)
• 工厂方法模式
用来生产同一等级结构中的固定产品。(支持增加任意产品)
• 抽象工厂模式
用来生产不同产品族的全部产品。(对于增加新的产品,无能为力;支持增加产品族)
建造者模式
• 分离了对象子组件的单独构造(由Builder来负责)和装配(由Director负责)。 从而可 以构造出复杂的对象。
原型模式
• 通过new产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式
结构型模式总结
行为型模式总结
练习源码:https://gitee.com/cutelili/gof23
Servlet 和 Tomcat 底层源码分析