设计模式GOF23
一 、创建型模式:
– 单例模式、工厂模式、抽象工厂模式、建造者模式、原型模式。
二 、结构型模式:
– 适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式。
三 、行为型模式:
– 模版方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式、访问者模式。
一 、创建型模式
1.1 什么单例模式?
就是整个程序有且仅有一个实例。该类负责创建自己的对象,同时确保只有一个对象被创建。在Java,一般常用在工具类的实现或创建对象需要消耗资源。
1.2 为什么需要代理模式
1.对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级对象而言,是非常可观的一笔系统开销。
2.由于new操作的次数减少,所以系统内存的使用评率也会降低,这将减少GC压力,缩短GC停顿时间。
1.3 单例模式的优点:
– 由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决
– 单例模式可以在系统设置全局的访问点,优化环共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理
1.4 常见的五种单例模式实现方式
– 主要:
• 饿汉式(线程安全,调用效率高。 但是,不能延时加载。)
• 懒汉式(线程安全,调用效率不高。 但是,可以延时加载。)
– 其他:
• 双重检测锁式(由于JVM底层内部模型原因,偶尔会出问题。不建议使用
• 静态内部类式(线程安全,调用效率高。 但是,可以延时加载)
• 枚举单例(线程安全,调用效率高,不能延时加载)
优点:
– 实现简单
– 枚举本身就是单例模式。由JVM从根本上提供保障!避免通过反射和反序列化的漏洞!
• 缺点:
– 无延迟加载
1.5 单例模式存在的漏洞
1. 反射可以破解上面几种(不包含枚举式)实现方式!
解决办法:(可以在构造方法中手动抛出异常控制)
2. 反序列化可以破解上面几种((不包含枚举式))实现方式!
解决办法:可以通过定义readResolve()防止获得不同对象。
如下:
1.6常见的五种单例模式在多线程环境下的效率测试
辅助类测试类:CountDownLatch类
– 同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一
个或多个线程一直等待。
• countDown() 当前线程调此方法,则计数减一(建议放在 finally里执行)
• await(), 调用此方法会一直阻塞当前线程,直到计时器的值为0
1.7 单例模式的选用?
– 单例对象 占用 资源 少,不需要 延时加载:
• 枚举式 好于 饿汉式
– 单例对象 占用 资源 大,需要 延时加载:
• 静态内部类式 好于 懒汉式
2.工厂模式:
2.1 面向对象设计的基本原则:
– OCP(开闭原则,Open-Closed Principle):一个软件的实体应当对扩展开
放,对修改关闭。
DIP(依赖倒转原则,Dependence Inversion Principle):要针对接口编程,
不要针对实现编程。
LoD(迪米特法则,Law of Demeter):只与你直接的朋友通信,而避免和
陌生人通信。
2.2 工厂模式分类:
• 简单工厂模式
• 工厂方法模式
• 抽象工厂模式
2.2.1 简单工厂模式
2.2.1.1 什么是简单工厂模式?
用来生产同一等级结构中的任意产品。(对于增加新的产品,需要修改已有代码)
要点:
– 简单工厂模式也叫静态工厂模式,就是工厂类一般是使用静态方法,
通过接收的参数的不同来返回不同的对象实例。
– 对于增加新产品无能为力!不修改代码的话,是无法扩展的
示例:类图如下:
2.2.2 工厂方法模式
• 用来生产同一等级结构中的固定产品。(支持增加任意产品) ,是简单工厂的升级版。
2.2.3简单工厂模式和工厂方法模式PK:
– 结构复杂度
从这个角度比较,显然简单工厂模式要占优。简单工厂模式只需一个工厂类,而工厂方法模式的工厂类随着产品类个
数增加而增加,这无疑会使类的个数越来越多,从而增加了结构的复杂程度。
– 代码复杂度
代码复杂度和结构复杂度是一对矛盾,既然简单工厂模式在结构方面相对简洁,那么它在代码方面肯定是比工厂方法
模式复杂的了。简单工厂模式的工厂类随着产品类的增加需要增加很多方法(或代码),而工厂方法模式每个具体工
厂类只完成单一任务,代码简洁。
– 客户端编程难度
工厂方法模式虽然在工厂类结构中引入了接口从而满足了OCP,但是在客户端编码中需要对工厂类进行实例化。而简
单工厂模式的工厂类是个静态类,在客户端无需实例化,这无疑是个吸引人的优点。
– 管理上的难度
这是个关键的问题。
我们先谈扩展。众所周知,工厂方法模式完全满足OCP,即它有非常良好的扩展性。那是否就说明了简单工厂模式就
没有扩展性呢?答案是否定的。简单工厂模式同样具备良好的扩展性——扩展的时候仅需要修改少量的代码(修改工
厂类的代码)就可以满足扩展性的要求了。尽管这没有完全满足OCP,但我们不需要太拘泥于设计理论,要知道,
sun提供的java官方工具包中也有想到多没有满足OCP的例子啊。
然后我们从维护性的角度分析下。假如某个具体产品类需要进行一定的修改,很可能需要修改对应的工厂类。当同时
需要修改多个产品类的时候,对工厂类的修改会变得相当麻烦(对号入座已经是个问题了)。反而简单工厂没有这些
麻烦,当多个产品类需要修改是,简单工厂模式仍然仅仅需要修改唯一的工厂类(无论怎样都能改到满足要求吧?大
不了把这个类重写)。
根据设计理论建议:工厂方法模式。但实际上,我们一般都用简单工厂模式。
2.2.4 抽象工厂模式
• 用来生产不同产品族的全部产品。(对于增加新的产品,无能为力;支持增加产品族)
,在有多个业务品种、业务
分类时,通过抽象工厂模式产生需要的对象是一种非常好的解决方式。
3.建造者模式
3.1 什么是建造者
– 分离了对象子组件的单独构造(由Builder来负责)和装配(由Director负责)。 从而可以构
造出复杂的对象。这个模式适用于:某个对象的构建过程复杂的情况下使用。
– 由于实现了构建和装配的解耦。不同的构建器,相同的装配,也可以做出不同的对象;
相同的构建器,不同的装配顺序也可以做出不同的对象。也就是实现了构建算法、装配
算法的解耦,实现了更好的复用。
示例:
4. 原型模式(克隆模式):
4.1什么是原型模式?
就是java中的克隆技术,以某个对象为原型,复制出新的对象。显然,新的对象具备原型对象的特点
克隆类似于new,但是不同于new。new创建新的对象属性采用的是默认值。克隆出的对象的属性值完全和原型对象相同。并且克隆出的新对象改变不会影响原型对象。然后,再修改克隆对象的值。
4.2 原型模式的分类
浅复制
深复制
4.3 浅复制
4.3.1 什么是浅复制?
被复制的对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用都仍然指向原来的对象。
示例1:
4.4.什么是深复制?
– 深克隆把引用的变量指向复制过的新对象,而不是原有的被引用的对象。
4.4.1 实现原理?
– 深克隆:让已实现Clonable接口的类中的属性也实现Clonable接口
– 基本数据类型和String能够自动实现深度克隆(值的复制)
示例:
创建型模型总结:
创建型模式:都是用来帮助我们创建对象的!
– 单例模式 • 保证一个类只有一个实例,并且提供一个访问该实例的全局访问点。 – 工厂模式 • 简单工厂模式 – 用来生产同一等级结构中的任意产品。(对于增加新的产品,需要修改已有代码)
• 工厂方法模式 – 用来生产同一等级结构中的固定产品。(支持增加任意产品) • 抽象工厂模式 – 用来生产不同产品族的全部产品。(对于增加新的产品,无能为力;支持增加产品族)
– 建造者模式 • 分离了对象子组件的单独构造(由Builder来负责)和装配(由Director负责)。 从而可以构造出复杂的对象
– 原型模式 • 通过new产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式
二 结构型模型?
1. 什么是结构性模型?
是从程序的结构上实现松耦合,从而可以扩大整体的类结构,用来解决更大的问题
2. 适配器(adapter)模式
2.1 什么是适配器模式
将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以在一起工作。
2.2 模式中的角色
– 目标接口(Target):客户所期待的接口。目标可以是具体的或抽象的类,也可以是接口。
– 需要适配的类(Adaptee):需要适配的类或适配者类。
– 适配器(Adapter):通过包装一个需要适配的对象,把原接口转换成目标接口。
2.3 适配器的实现:
2.4 工作中的场景
– 经常用来做旧系统改造和升级
– 如果我们的系统开发之后再也不需要维护,那么很多模式都是没必要
的,但是不幸的是,事实却是维护一个系统的代价往往是开发一个系
统的数倍。
我们学习中见过的场景
– java.io.InputStreamReader(InputStream)
– java.io.OutputStreamWriter(OutputStream)
3 .代理模式(Proxy pattern):
3.1 什么是代理模式?
代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗的来讲代理模式就是我们生活中常见的中介。
3.2 为什么需要代理模式?
1)在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口。
2)代理类除了是客户类和委托类的中介之外,我们还可以通过给代理类增加额外的功能来扩展委托类的功能,这样做我们只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则。代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后对返回结果的处理等。代理类本身并不真正实现服务,而是同过调用委托类的相关方法,来提供特定的服务。真正的业务功能还是由委托类来实现,但是可以在业务功能执行的前后加入一些公共的服务。例如我们想给项目加入缓存、日志这些功能,我们就可以使用代理类来完成,而没必要打开已经封装好的委托类。
3.3 代理模式好处:
通过代理,控制对对象的访问!可以详细控制访问某个(某类)对象的方法,在调用这个方法前做前置处理,调用这个方法做后置处理。(即:AOP的微观实现!)
3.4 代理模式 核心角色
• 抽象角色
– 定义代理角色和真实角色的公共对外方法
• 真实角色
– 实现抽象角色,定义真实角色所要实现的业务逻辑,
供代理角色调用。
– 关注真正的业务逻辑!
• 代理角色
– 实现抽象角色,是真实角色的代理,通过真实角色
的业务逻辑方法来实现抽象方法,并可以附加
自己的操作。
– 将统一的流程控制放到代理角色中处理!
3.5 代理模式的分类
– 静态代理(静态定义代理类)
– 动态代理(动态生成代理类)
• JDK自带的动态代理
• javaassist字节码操作库实现
• CGLIB
• ASM(底层使用指令,可维护性较差)
3.5.1 静态代理
3.5.1 什么是静态代理?
代理角色和真实角色都是程序员自己创建。
要求:真实角色,代理角色;真实角色和代理角色要实现同一个接口,代理角色要持有真实角色的引用。
类图如下:
结果
3.5.2 动态代理(动态生成代理类)
3.5.2.1 什么是动态代理?
代理类不需要程序员创建,系统自动创建
3.5.2.2 代理类和接口所在的包
– java.lang.reflect.Proxy • 作用:动态生成代理类和对象
– java.lang.reflect.InvocationHandler(处理器接口) • 可以通过invoke方法实现对真实角色的代理访问。 • 每次通过Proxy生成代理类对象对象时都要指定对应的处理器对象
示例:
结果:对真实角色处理方法前后做了前后置的处理
3.5.3 代理模式 开发框架中应用场景:
– struts2中拦截器的实现
– 数据库连接池关闭处理
– Hibernate中延时加载的实现
– mybatis中实现拦截器插件
– AspectJ的实现
– spring中AOP的实现
• 日志拦截
• 声明式事务处理
– web service
– RMI远程方法调用
三 、行为型模式:
用来识别对象之间的常用交流模式并加以实现。如此,可在进行这些交流活动时增强弹性。
1. 策略模式
1.1 什么是策略模式
策略模式对应于解决某一个问题的一个算法族,允许用户从该算法族中任选一个算法解决某一问题,同时可以方便的更换算法或者增加新的算法。并且由客户端决定调用哪个算法。当解决某一问题的情况很多的时候使用策略模式。
1.2 为甚么使用策略模式?
在有多种算法相似的情况下,使用 if...else 所带来的复杂和难以维护。如下图:
1.3 使用策略模式优缺点/
优点:1、算法可以自由切换。2、避免使用多重条件判断。3、扩展性良好。
缺点:1、策略类会增多。2、所有策略类都需要对外暴露。
4.4 策略模式的实现、
类图:
2. 模板方法模式
2.1 什么是模板方法模式
定义一个操作中的算法骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重新定义该算法的某些特定步骤。
2.2为什么使用模板算法/
有些程序是固定的套路,且步骤完全一致,浪废敲代码的时间
2.3 模板算法的实现
2.4什么是钩子函数?
1.是个函数,在系统消息触发时被系统调用 2、不是用户自己触发的
2.5什么是好莱坞原则?
把简历递交给演艺公司后就只有回家等待。由演艺公司对整个娱乐项的完全控制,演员只能被动式的接受公司的差使,在需要的环节中,完成自己的演出。
3. 观察者模式
3. 1 什么是观察者模式
观察者模式(又被称为发布-订阅(Publish/Subscribe)模式,属于行为型模式的一种,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态变化时,会通知所有的观察者对象,使他们能够自动更新自己。
3.2 特点是什么?
1.观察者模式在被观察者和观察者之间建立一个抽象的耦合。
2. 、观察者模式支持广播通讯。被观察者会向所有的登记过的观察者发出通知,
3.3 观察者的实现
结果:当主题对象状态改变,四个观察者的状态也发生改变