1. 单例模式
保证一个类只有一个实例,并且提供一个访问该实例的全局访问点。
优点:
– 由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要 比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动 时直接产生一个单例对象,然后永久驻留内存的方式来解决
– 单例模式可以在系统设置全局的访问点,优化环共享资源访问,例如可以设计 一个单例类,负责所有数据表的映射处理
1、懒加载(静态内部类)
public class Singleton{
private Singletion{
}
private static class T{///静态内部类在使用的时候才加载,且只加载一次
private static final Singleton t = new Singleton();
}
public static Singleton getInstance(){
return T.t;
}
}
– 外部类没有static属性,则不会像饿汉式那样立即加载对象。
– 只有真正调用getInstance(),才会加载静态内部类。加载类时是线程 安全的。 instance是static final 类型,保证了内存中只有这样一个实例存在,而且只能被赋值一次,从而保证了线程安全性.
– 兼备了并发高效调用和延迟加载的优势!
2、双重校验锁
package ss;
public class Singleton4 {
private Singleton4(){
}
private static volatile Singleton4 s = null;
public static Singleton4 getInstence(){
if(s==null){
synchronized (Singleton4.class) {
if(s==null){
s = new Singleton4();
}
}
}
return s;
}
}
现在想象一下有线程 A 和 B 在调用 getInstance(),线程 A 先进入,在 执行到 instance=new Singleton():的时候被踢出了 cpu。然后线程 B 进入,B 看到的是 instance 已经不是 null 了(内存已经分配),于是它开始放心地使用 instance,但这个是错误的,因为 A 还没有来得及完成 instance 的初始化,而线 程 B 就返回了未被初始化的 instance 实例(为了禁止指令重排序,就用 volatile 关键字)。
3、 饿汉式(单例对象立即加载)
public class Singleton{
private static Singleton s = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return s;
}
}
饿汉式单例模式代码中,static变量会在类装载时初始化,此时也不会涉及多个线程对象访问该对象的问 题。虚拟机保证只会装载一次该类,肯定不会发生并发访问的问题。因此,可以省略synchronized关键字。
4、懒汉式(单例对象延时加载)
public class Singleton{
private static Singleton s;
private Singleton(){};
public static synchronized Singleton getInstance(){
if(s==null){
s = new Singleton();
}
return s;
}
}
5、枚举实现
public enum Singleton{
/** * 定义一个枚举的元素,它就代表了Singleton的一个实例。 */
INSTANCE;
/*** 单例可以有自己的操作 */
public void singletonOperation(){
//功能处理
}
}
单例模式应用场景: 数据库连接池,多线程的线程池。 Windows 的任务管理器就是很典型的单 例模式。
2. 工厂模式
– 实现了创建者和调用者的分离。
– 详细分类:
-
简单工厂模式
-
工厂方法模式
-
抽象工厂模式
-核心本质:
-
实例化对象,用工厂方法代替new操作。
-
将选择实现类、创建对象统一管理和控制。从而将调用者跟我们的实 现类解耦。
1、简单工厂:
– 简单工厂模式也叫静态工厂模式,就是工厂类一般是使用静态方法, 通过接收的参数的不同来返回不同的对象实例。
– 对于增加新产品无能为力!不修改代码的话,是无法扩展的。
public class CarFactory{
public static Car createCar(String type){
Car c= null;
if("奥迪".equals(type)){
c = new Audi();
}else if("奔驰".equals(type)){
c= new Becz();
}
return c;
}
}
public class CarFactory{
public static Car createAudi(){
return new Audi();
}
public static Car creatBecz(){
return new Becz();
}
}
2、工厂方法模式
– 为了避免简单工厂模式的缺点,不完全满足OCP。
– 工厂方法模式和简单工厂模式最大的不同在于,简单工厂模式只有一个(对于一个项目 或者一个独立模块而言)工厂类,而工厂方法模式有一组实现了相同接口的工厂类。
3、抽象工厂模式
– 用来生产不同产品族的全部产品。(对于增加新的产品,无能为力; 支持增加产品族)
– 抽象工厂模式是工厂方法模式的升级版本,在有多个业务品种、业务 分类时,通过抽象工厂模式产生需要的对象是一种非常好的解决方式。
• 工厂模式要点:
– 简单工厂模式(静态工厂模式) • 虽然某种程度不符合设计原则,但实际使用最多。
– 工厂方法模式 • 不修改已有类的前提下,通过增加新的工厂类实现扩展。
– 抽象工厂模式 • 不可以增加产品,可以增加产品族!
• 应用场景
– JDK中Calendar的getInstance方法
– JDBC中Connection对象的获取
– Hibernate中SessionFactory创建Session
– spring中IOC容器创建管理bean对象
– XML解析时的DocumentBuilderFactory创建解析器对象
– 反射中Class对象的newInstance()
3. 建造者模式
建造模式的本质:
– 分离了对象子组件的单独构造(由Builder来负责)和装配(由Director负责)。 从而可以构造出复杂的对象。这个模式适用于:某个对象的构建过程复杂的情况下使用。
– 由于实现了构建和装配的解耦。不同的构建器,相同的装配,也可以做出不同的对象; 相同的构建器,不同的装配顺序也可以做出不同的对象。也就是实现了构建算法、装配 算法的解耦,实现了更好的复用。
• 开发中应用场景:
– StringBuilder类的append方法
– SQL中的PreparedStatement
– JDOM中,DomBuilder、SAXBuilder
4. 原型模式
原型模式:
– 通过new产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。
– 就是java中的克隆技术,以某个对象为原型,复制出新的对象。显然,新的对象具备原型对象的特点
– 优势有:效率高(直接克隆,避免了重新执行构造过程步骤) 。
– 克隆类似于new,但是不同于new。new创建新的对象属性采用的是默认值。克隆出的 对象的属性值完全和原型对象相同。并且克隆出的新对象改变不会影响原型对象。然后, 再修改克隆对象的值。
原型模式实现:
– Cloneable接口和clone方法
– Prototype模式中实现起来最困难的地方就是内存复制操作,所幸在Java中提供了 clone()方法替我们做了绝大部分事情。
浅克隆存在的问题
– 被复制的对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用都 仍然指向原来的对象。
深克隆如何实现?
– 深克隆把引用的变量指向复制过的新对象,而不是原有的被引用的对象。
– 深克隆:让已实现Clonable接口的类中的属性也实现Clonable接口
– 基本数据类型和String能够自动实现深度克隆(值的复制)
利用序列化和反序列化技术实现深克隆:
创建型模式总结
5. 适配器模式
什么是适配器模式?
– 将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原 本由于接口不兼容而不能一起工作的那些类可以在一起工作。
• 模式中的角色
– 目标接口(Target):客户所期待的接口。目标可以是具体的或抽象 的类,也可以是接口。
– 需要适配的类(Adaptee):需要适配的类或适配者类。
– 适配器(Adapter):通过包装一个需要适配的对象,把原接口转换成 目标接口。
1、类适配器
class Adapter extends Adaptee implements Target{
public void request(){
super.specificRequest();
}
}
2、对象适配器
class Adapter implements Target{
private Adaptee ad;
public Adapter(Adaptee ad){
this.ad = ad;
}
public void request(){
this.adaptee.specificRequest();
}
}
工作中的场景
– 经常用来做旧系统改造和升级
– 如果我们的系统开发之后再也不需要维护,那么很多模式都是没必要 的,但是不幸的是,事实却是维护一个系统的代价往往是开发一个系 统的数倍。
6. 代理模式
– 核心作用:
• 通过代理,控制对对象的访问! 可以详细控制访问某个(某类)对象的方法,在调用这个方法前做前置处理,调用这个方法后 做后置处理。(即:AOP的微观实现!)
– 核心角色:
• 抽象角色 – 定义代理角色和真实角色的公共对外方法
• 真实角色 – 实现抽象角色,定义真实角色所要实现的业务逻辑, 供代理角色调用。
– 关注真正的业务逻辑! • 代理角色
– 实现抽象角色,是真实角色的代理,通过真实角色 的业务逻辑方法来实现抽象方法,并可以附加
分类:
– 静态代理(静态定义代理类)
– 动态代理(动态生成代理类)
• JDK自带的动态代理
• javaassist字节码操作库实现
• CGLIB
• ASM(底层使用指令,可维护性较差)
动态代理相比于静态代理的优点 :
– 抽象角色中(接口)声明的所以方法都被转移到调用处理器一个集中的方 法中处理,这样,我们可以更加灵活和统一的处理众多的方法。
• 开发框架中应用场景:
– struts2中拦截器的实现
– 数据库连接池关闭处理
– Hibernate中延时加载的实现
– mybatis中实现拦截器插件
– AspectJ的实现
– spring中AOP的实现
7. 桥接模式
桥接模式核心要点:
– 处理多层继承结构,处理多维度变化的场景,将各个维度设计成独立 的继承结构,使各个维度可以独立的扩展在抽象层建立关联。
例如:商城系统中常见的商品分类,以电脑为类,如何良好的处理商品分类销售的问题?这个场景中有两个变化的维度:电脑类型、电脑品牌。
桥接模式总结:
– 桥接模式可以取代多层继承的方案。 多层继承违背了单一职责原则, 复用性较差,类的个数也非常多。桥接模式可以极大的减少子类的个 数,从而降低管理和维护的成本。
– 桥接模式极大的提高了系统可扩展性,在两个变化维度中任意扩展一 个维度,都不需要修改原有的系统,符合开闭原则。
桥接模式实际开发中应用场景
– JDBC驱动程序
– AWT中的Peer架构
– 银行日志管理:
• 格式分类:操作日志、交易日志、异常日志
• 距离分类:本地记录日志、异地记录日志
– 人力资源系统中的奖金计算模块:
• 奖金分类:个人奖金、团体奖金、激励奖金。
• 部门分类:人事部门、销售部门、研发部门。
– OA系统中的消息处理:
• 业务类型:普通消息、加急消息、特急消息
• 发送消息方式:系统内消息、手机短信、邮件
8. 组合模式
组合模式核心:
– 抽象构件(Component)角色: 定义了叶子和容器构件的共同点
– 叶子(Leaf)构件角色:无子节点
– 容器(Composite)构件角色: 有容器特征,可以包含子节点
组合模式工作流程分析:
– 组合模式为处理树形结构提供了完美的解决方案,描述了如何将容器和叶子进行递归组 合,使得用户在使用时可以一致性的对待容器和叶子。
– 当容器对象的指定方法被调用时,将遍历整个树形结构,寻找也包含这个方法的成员, 并调用执行。其中,使用了递归调用的机制对整个结构进行处理。
使用组合模式的场景:
– 把部分和整体的关系用树形结构来表示,从而使客户端可以使用统一的方式处理部分对象和整体对象。
开发中的应用场景:
– 操作系统的资源管理器
– GUI中的容器层次图
– XML文件解析
– OA系统中,组织结构的处理
– Junit单元测试框架
9. 装饰模式
功能:
-动态的为一个对象增加新的功能;
-是一种用于代替继承的技术,无需通过继承增加子类就能扩展对象的新功能。使用对象的关联关系代替继承关系,避免类型体系的快速膨胀。
实现细节:
– Component抽象构件角色:
• 真实对象和装饰对象有相同的接口。这样,客户端对象就能够以与真实对象相同的方式同装饰对象交互。
– ConcreteComponent 具体构件角色(真实对象):
• io流中的FileInputStream、FileOutputStream
– Decorator装饰角色:
• 持有一个抽象构件的引用。装饰对象接受所有客户端的请求,并把这些请求转发给真实的对象 。这样,就能在真实对象调用前后增加新的功能。
– ConcreteDecorator具体装饰角色:
• 负责给构件对象增加新的责任。
如下例所示:
IO流实现细节:
– Component抽象构件角色:
• io流中的InputStream、OutputStream、Reader、Writer
– ConcreteComponent 具体构件角色:
• io流中的FileInputStream、FileOutputStream
– Decorator装饰角色:
• 持有一个抽象构件的引用:io流中的FilterInputStream、FilterOutputStream
– ConcreteDecorator具体装饰角色:
• 负责给构件对象增加新的责任。Io流中的BufferedOutputStream、BufferedInputStream等。
总结:
– 装饰模式(Decorator)也叫包装器模式(Wrapper)
– 装饰模式降低系统的耦合度,可以动态的增加或删除对象的职责,并使得需要装饰的具体构建类和具体装饰类可以独立变化,以便增加新的具体构建类和具体装饰类。
优点
– 扩展对象功能,比继承灵活,不会导致类个数急剧增加
– 可以对一个对象进行多次装饰,创造出不同行为的组合,得到功能更加强大的对象
– 具体构建类和具体装饰类可以独立变化,用户可以根据需要自己增加新的具体构件子类和具体装饰子类。
缺点
– 产生很多小对象。大量小对象占据内存,一定程度上影响性能。
– 装饰模式易于出错,调试排查比较麻烦。
装饰模式和桥接模式的区别:
– 两个模式都是为了解决过多子类对象问题。但他们的诱因不一样。桥模式是对象自身现有机制沿着多个维度变化,是既有部分不稳定。装饰模式是为了增加新的功能。
10. 外观模式
– 为子系统提供统一的入口。封装子系统的复杂性,便于客户端调用。
开发中常见的场景
– 频率很高。哪里都会遇到。各种技术和框架中,都有外观模式的使用。如: • JDBC封装后的,commons提供的DBUtils类, Hibernate提供的工具类、Spring JDBC工具类等。
11. 享元模式
功能:
– 享元模式以共享的方式高效地支持大量细粒度对象的重用。
– 享元对象能做到共享的关键是区分了内部状态和外部状态。
• 内部状态:可以共享,不会随环境变化而改变
• 外部状态:不可以共享,会随环境变化而改变
享元模式实现:
– FlyweightFactory享元工厂类
• 创建并管理享元对象,享元池一般设计成键值对
– FlyWeight抽象享元类
• 通常是一个接口或抽象类,声明公共方法,这些方法可以向外界提供对象的内部状态,设置外部状态。
– ConcreteFlyWeight具体享元类
• 为内部状态提供成员变量进行存储
– UnsharedConcreteFlyWeight非共享享元类
• 不能被共享的子类可以设计为非共享享元类
例如:围棋软件设计
– 每个围棋棋子都是一个对象, 有如下属性:
颜色形状大小位置 (这些是可以共享的) 称之为:内部状态
位置 (这些不可以共享) 称之为:外部状态
享元模式开发中应用的场景:
– 享元模式由于其共享的特性,可以在任何“池”中操作, 比如:线程池、数据库连接池。
– String类的设计也是享元模式
优点
– 极大减少内存中对象的数量
– 相同或相似对象内存中只存一份,极大的节约资源,提高系统性能
– 外部状态相对独立,不影响内部状态
缺点
– 模式较复杂,使程序逻辑复杂化
– 为了节省内存,共享了内部状态,分离出外部状态,而读取外部状态 使运行时间变长。用时间换取了空间。
结构型模式总结
12. 责任链模式
功能:
将能够处理同一类请求的对象连成一条链,所提交的请求沿着链传递,链上的对象逐个判断是否有能力处理该请求,如果能则处理,如果不能则传递给链上的下一个对象。
例如下面的例子:
– 公司里面,报销个单据需要经过流程:
• 申请人填单申请,申请给经理
• 小于1000,经理审查。
• 超过1000,交给总经理审批。
• 总经理审批通过
添加新的处理对象:
– 由于责任链的创建完全在客户端,因此新增新的具体处理者对原有类库没有任何影响,只需添加新的类,然后在客户端调用时添加即可。 符合开闭原则。
开发中常见的场景:
– Java中,异常机制就是一种责任链模式。一个try可以对应多个catch, 当第一个catch不匹配类型,则自动跳到第二个catch.
– Javascript语言中,事件的冒泡和捕获机制。Java语言中,事件的处理采用观察者模式。
– Servlet开发中,过滤器的链式处理
– Struts2中,拦截器的调用也是典型的责任链模式
13. 迭代器模式
功能:
– 提供一种可以遍历聚合对象的方式。又称为:游标cursor模式
结构:
– 聚合对象:存储数据
– 迭代器:遍历数据
基本案例:
– 实现正向遍历的迭代器
– 实现逆向遍历的迭代器
开发中常见的场景:
– JDK内置的迭代器(List/Set)
14. 中介模式
功能:
当一个系统中对象之间的联系呈现网状结构,对象之间存在大量多对多关系,将导致关系及其复杂,这些对象是“同事对象”。因此可以引入一个中介者对象,使得各个同事对象只跟中介对象交流,将复杂的网络结构变为星型结构。
– 解耦多个同事对象之间的交互关系。每个对象都持有中介者对象的引用,只跟中介者对象打交道。我们通过中介者对象统一管理这些交互关系。
例如下面的例子:
假如没有总经理。下面三个部门:财务部、市场部、研发部。财务部要发工资,让大家 核对公司需要跟市场部和研发部都通气;市场部要接个新项目,需要研发部处理技术、 需要财务部出资金。市场部跟各个部门打交道。 虽然只有三个部门,但是关系非常乱。
– 实际上,公司都有总经理。各个部门有什么事情都通报到总经理这里,总经理再通知各 个相关部门。
– 这就是一个典型的“中介者模式” 总经理起到一个中介、协调的作用
开发中常见的场景:
– MVC模式(其中的C,控制器就是一个中介者对象。M和V都和他打交 道)
– 窗口游戏程序,窗口软件开发中窗口对象也是一个中介者对象
– 图形界面开发GUI中,多个组件之间的交互,可以通过引入一个中介者 对象来解决,可以是整体的窗口对象或者DOM对象
– Java.lang.reflect.Method#invoke()
15. 命令模式
功能:
将一个请求封装为一个对象,从而使我们可用不同的请求对客户进行参数化;队请求排队或者记录请求日志,以及支持可撤销的操作。称为动作action模式。
开发中常见的场景:
– Struts2中,action的整个调用过程中就有命令模式。
– 数据库事务机制的底层实现
– 命令的撤销和恢复
16. 解释器模式
– 用于描述如何构成一个简单的语言解释器,主要用于使用面向对象语言开发的 编译器和解释器设计。
– 当我们需要开发一种新的语言时,可以考虑使用解释器模式。
– 尽量不要使用解释器模式,后期维护会有很大麻烦。在项目中,可以使用 Jruby,Groovy、java的js引擎来替代解释器的作用,弥补java语言的不足。
开发中常见的场景:
– EL表达式的处理
– 正则表达式解释器
– SQL语法的解释器
– 数学表达式解析器
17.访问者模式
模式动机:
– 对于存储在一个集合中的对象,他们可能具有不同的类型(即使有一个公共的接 口),对于该集合中的对象,可以接受一类称为访问者的对象来访问,不同的访问者其访问方式也有所不同。
定义:
– 表示一个作用于某对象结构中的各元素的操作,它使我们可以在不改变个元素 的类的前提下定义作用于这些元素的新操作。
开发中的场景(应用范围非常窄,了解即可):
– XML文档解析器设计
– 编译器的设计
– 复杂集合对象的处理
18.策略模式
策略模式对应于解决某一个问题的一个算法族,允许用户从该算法族中任选一个算法解决某一问题,同时可以方便的更换算法或者增加新 的算法。并且由客户端决定调用哪个算法。
例如下面的例子:
– 某个市场人员接到单后的报价策略(CRM系统中常见问题)。报价策略 很复杂,可以简单作如下分类:
• 普通客户小批量报价
• 普通客户大批量报价
• 老客户小批量报价
• 老客户大批量报价
– 具体选用哪个报价策略,这需要根据实际情况来确定。这时候,我们 采用策略模式即可。
开发中常见的场景:
– JAVASE中GUI编程中,布局管理
– Spring框架中,Resource接口,资源访问策略
– javax.servlet.http.HttpServlet#service()
19. 模板方法模式
该方法定义了一个操作中的算法骨架,将某些步骤延迟到子类中实现。新的子类可以在不改变一个算法结构的前提下重新定义该算法的某些特定步骤。
处理某个流程的代码已经具备,但是其中某个节点的代码暂时不能确定。因此,我们使用工厂方法模式,将这个节点的代码实现转移到子类完成。即:处理步骤父类中定义好,具体实现延迟到子类中定义。
如下面例子:
客户在银行办理业务:
1、取号排队
2、办理具体现金/转账/企业/个人/理财业务
3、给银行工作人员评分
该方法使用的场景:
实现一个算法时,整体步骤很固定。但是,某些部分易变。易变部分可以抽象出来,供子类实现。
开发中常见的场景:
1、数据库访问的封装
2、Junit单元测试
3、servlet中关于doGet/doPost方法调用
4、Hibernate中模板程序
5、spring中jdbcTemplate等
20. 状态模式
用于解决系统中复杂对象的状态转换以及不同状态下行为的封装问题。
– Context环境类
• 环境类中维护一个State对象,他是定义了当前的状态。
– State抽象状态类
– ConcreteState具体状态类
• 每一个类封装了一个状态对应的行为
如下面的例子:
酒店系统中,房间的状态变化:已预订、已入住、空闲
各个状态之间的转换:
开发中常见的场景:
– 银行系统中账号状态的管理
– OA系统中公文状态的管理
– 酒店系统中,房间状态的管理
– 线程对象各状态之间的切换
21. 观察者模式
该方法主要用于1:N的通知。当一个对象(目标对象Subject)的状态变化时,需要及时告诉一系列对象(观察者对象Observer),让其做出反应。
通知观察者的方法:
1、推:每次都会把通知以广播的方式发送给所有观察者,所有观察者只能被动接受。
2、拉:观察者只要直到有情况即可。至于什么时候获取内容,获取什么内容,都可以自主决定。
例如下面的例子:
– 聊天室程序的创建。服务器创建好后,A,B,C三个客户端连上来公开聊天。A向服务器发送数据,服务器端聊天数据改变。我们希望将这些聊 天数据分别发给其他在线的客户。也就是说,每个客户端需要更新服 务器端得数据。
– 网站上,很多人订阅了”java主题”的新闻。当有这个主题新闻时,就 会将这些新闻发给所有订阅的人。 – 大家一起玩CS游戏时,服务器需要将每个人的方位变化发给所有的客 户。
22. 备忘录模式
保存某个对象内部状态的拷贝,这样以后就可以将该对象恢复到原先的状态。
负责人类CareTaker:
负责保存好的备忘录对象。
可以通过增加容器,设置多个“备忘点”
源发器类Originator
备忘录类Memento
开发中常见的应用场景:
– 棋类游戏中的,悔棋
– 普通软件中的,撤销操作
– 数据库软件中的,事务管理中的,回滚操作
– Photoshop软件中的,历史记录