23种设计模式
参考: http://c.biancheng.net/design_pattern
设计模式类型
- 创建型: 在创建对象的同时隐藏创建逻辑,不使⽤ new 直接实例化对象,程序在判断需要创建哪些对象时更灵活。
包括⼯⼚/抽象⼯⼚/单例/建造者/原型模式。 - 结构型: 通过类和接⼝间的继承和引⽤实现创建复杂结构的对象。
包括适配器/桥接模式/过滤器/组合/装饰器/外观/享元/代理模式。 - ⾏为型: 通过类之间不同通信⽅式实现不同⾏为。
包括责任链/命名/解释器/迭代器/中介者/备忘录/观察者/状态/策略/模板/访问者模式。
单例模式
- 某个类只能生成一个实例,该类提供了一个全局访问方法供外部获取该实例
- 懒汉式:dubbo check
- 饿汉式:枚举:天然安全,可以避免反序列化破坏单例
优缺点:
- 保证内存里只有一个实例,减少了内存的开销
- 可以避免对资源的多重占用
- 单例模式设置全局访问点,可以优化和共享资源的访问
缺点:
- 扩展困难。如果需要扩展,只能修改原来的代码,,违背开闭原则
- 在并发测试中,单例模式不利于代码调试。在调试过程中,如果单例中的代码没有执行完,也不能模拟生成一个新的对象
- 单例模式的功能代码通常写在一个类中,如果功能设计不合理,则很容易违背单一职责原则
应用场景:
- 某些类创建实例时占用资源较多,或实例化耗时较长,且经常使用
- 某类需要频繁实例化,而创建的对象又频繁被销毁的时候,如多线程的线程池、网络连接池等
简单工厂模式
- 创建产品的逻辑全在一个静态方法中,又叫作静态工厂方法模式,违背了“开闭原则”
- 简单工厂模式不在 GoF 23 种设计模式之列
优缺点:
- 工厂和产品的职责区分明确。
- 客户端无需知道所创建具体产品的类名,只需知道参数即可
缺点:
- 负责所有产品的创建,职责过重,一旦异常,整个系统将受影响。且工厂类代码会非常臃肿,违背高聚合原则
- 使用简单工厂模式会增加系统中类的个数(引入新的工厂类),增加系统的复杂度和理解难度
- 系统扩展困难,一旦增加新产品不得不修改工厂逻辑,在产品类型较多时,可能造成逻辑过于复杂
应用场景:
工厂方法模式
- 每新增一个产品,需要为其添加一个工厂类
优缺点:
- 用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程
- 灵活性增强,对于新产品的创建,只需多写一个相应的工厂类
- 典型的解耦框架。高层模块只需要知道产品的抽象类,无须关心其他实现类,满足迪米特法则、依赖倒置原则和里氏替换原则
缺点:
- 类的个数容易过多,增加复杂度
- 增加了系统的抽象性和理解难度
- 抽象产品只能生产一种产品,此弊端可使用抽象工厂模式解决
应用场景:
- 创建对象的任务由多个具体子工厂中的某一个完成,而抽象工厂只提供创建产品的接口
- 客户不关心创建产品的细节,只关心产品的品牌
抽象工厂模式
- 一个工厂可创建同一产品族的多类产品
优缺点:
- 可以在类的内部对产品族中相关联的多等级产品共同管理,而不必专门引入多个新的类来进行管理
- 当需要产品族时,抽象工厂可以保证客户端始终只使用同一个产品的产品组
- 抽象工厂增强了程序的可扩展性,当增加一个新的产品族时,不需要修改原代码,满足开闭原则
缺点:
- 当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改。增加了系统的抽象性和理解难度
应用场景:
- 当需要创建的对象是一系列相互关联或相互依赖的产品族时,如电器工厂中的电视机、洗衣机、空调等
- 系统中有多个产品族,但每次只使用其中的某一族产品
- 系统中提供了产品的类库,且所有产品的接口相同,客户端不依赖产品实例的创建细节和内部结构
代理模式
- 给某对象提供一个代理以控制对目标对象的访问
- 访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介
结构:
代理模式的主要角色如下。
- 抽象主题类:通过接口或抽象类声明真实主题和代理对象实现的业务方法
- 真实主题类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象
- 代理类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能
优缺点:
- 代理对象可以扩展目标对象的功能
- 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度,增加了程序的可扩展性
缺点:
- 代理模式会造成系统设计中类的数量增加
- 在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢
- 增加了系统的复杂度
应用场景:
- 远程代理,这种方式通常是为了隐藏目标对象存在于不同地址空间的事实,方便客户端访问。例如,用户申请某些网盘空间时,会在用户的文件系统中建立一个虚拟的硬盘,用户访问虚拟硬盘时实际访问的是网盘空间。
- 虚拟代理,这种方式通常用于要创建的目标对象开销很大时。例如,下载一幅很大的图像需要很长时间,因某种计算比较复杂而短时间无法完成,这时可以先用小比例的虚拟代理替换真实的对象,消除用户对服务器慢的感觉。
- 安全代理,这种方式通常用于控制不同种类客户对真实对象的访问权限。
智能指引,主要用于调用目标对象时,代理附加一些额外的处理功能。例如,增加计算真实对象的引用次数的功能,这样当该对象没有被引用时,就可以自动释放它。 - 延迟加载,指为了提高系统的性能,延迟对目标的加载。例如,Hibernate 中就存在属性的延迟加载和关联表的延时加载
装饰器模式
-指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式,它属于对象结构型模式
结构:
装饰器模式主要包含以下角色。
- 抽象构件(Component)角色:定义一个抽象接口以规范准备接收附加责任的对象。
- 具体构件(ConcreteComponent)角色:实现抽象构件,通过装饰角色为其添加一些职责。
- 抽象装饰(Decorator)角色:继承抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。
- 具体装饰(ConcreteDecorator)角色:实现抽象装饰的相关方法,并给具体构件对象添加附加的责任
优缺点:
- 装饰器是继承的有力补充,比继承灵活,在不改变原有对象的情况下,动态的给一个对象扩展功能,即插即用
- 通过使用不同装饰类及这些装饰类的排列组合,可以实现不同效果
- 装饰器模式完全遵守开闭原则
缺点:
- 装饰器模式会增加许多子类,过度使用会增加程序得复杂性
应用场景:
- 当需要给一个现有类添加附加职责,而又不能采用生成子类的方法进行扩充时。例如,该类被隐藏或者该类是终极类或者采用继承方式会产生大量的子类。
- 当需要通过对现有的一组基本功能进行排列组合而产生非常多的功能时,采用继承关系很难实现,而采用装饰器模式却很好实现。
- 当对象的功能要求可以动态地添加,也可以再动态地撤销时
适配器模式
- 将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作
- 适配器模式分为类结构型模式和对象结构型模式两种,前者类之间的耦合度比后者高,且要求程序员了解现有组件库中的相关组件的内部结构,所以应用相对较少些
- 对象结构型,同装饰者模式
优缺点:
- 客户端通过适配器可以透明地调用目标接口
- 复用了现存的类,程序员不需要修改原有代码而重用现有的适配者类
- 将目标类和适配者类解耦,解决了目标类和适配者类接口不一致的问题
缺点:
- 可能会增加系统的复杂性
- 过多使用适配器会使系统代码变得凌,增加代码阅读难度,降低代码可读性,
应用场景:
- 以前开发的系统存在满足新系统功能需求的类,但其接口同新系统的接口不一致。
- 使用第三方提供的组件,但组件接口定义和自己要求的接口定义不同
策略模式
- 定义了策略族,各策略都是独立的类,实现于相同的接口
- 不提供策略的选择,由客户端进行选择
优缺点:
- 完美符合开闭原则,可以灵活的增加策略
- 策略可以自由切换,避免多重条件判断
缺点:
- 策略类增多
- 所有策略类需要对外暴露
应用场景:
- 在多种粗略相似的情况下,避免使用if else 带来的复杂和难以维护
观察者模式
- 又称发布-订阅模式
- 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,主动发出通知,观察它的对象将会收到通知
优缺点:
- 可以让类之间隔离,定义了稳定的消息更新传递机制,抽象了更新接口,让各个被通知的类具体处理自己的更新操作
- 观察者支持广播通信
缺点:
- 如果一个对象有很多的观察者,那么通知所有的观察者会花费很多时间
- 如果观察者和目标之间有循环依赖,那么就会导致系统崩溃
应用场景:
- 一个对象必须要通知其它对象,但是不知道其它对象有哪些
原型模式
- 用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象
- 浅克隆:新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址
- 深克隆:属性中引用的其他对象也会被克隆,不再指向原有对象地址
优缺点:
- Java 自带的原型模式基于内存二进制流的复制,在性能上比直接 new 一个对象更好
- 简化了创建对象的过程
- 可以使用深克隆方式保存对象的状态,使用原型模式将对象复制一份,并将其状态保存起来
缺点:
- 需要为每一个类都配置一个 clone 方法
- clone 方法位于类的内部,当对已有类进行改造的时候,需要修改代码,违背了开闭原则。
- 当实现深克隆时,需要编写较为复杂的代码,而且当对象之间存在多重嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来会比较麻烦。因此,深克隆、浅克隆需要运用得当
应用场景:
- 对象之间相同或相似,即只是个别的几个属性不同的时候。
- 创建对象成本较大,例如初始化时间长,占用CPU太多,或者占用网络资源太多等,需要优化资源。
- 创建一个对象需要繁琐的数据准备或访问权限等,需要提高性能或者提高安全性。
- 系统中大量使用该类对象,且各个调用者都需要给它的属性重新赋值
建造者模式
建造者(Builder)模式和工厂模式的关注点不同:建造者模式注重零部件的组装过程,而工厂方法模式更注重零部件的创建过程,但两者可以结合使用
- 将一个复杂对象的构造与它的表示分离,使同样的构建过程可以创建不同的表示
- 将一个复杂的对象分解为多个简单的对象,然后一步一步构建而成
- 产品角色:包含多个组成部件的复杂对象(由具体建造者来创建其各个零部件)
- 抽象构建者:包含创建产品各个子部件的抽象方法的接口,通常还包含一个返回复杂产品的方法
- 具体构建者:实现 Builder 接口,完成复杂产品的各个部件的具体创建方法
- 指挥者:调用建造者对象中的部件构造与装配方法完成复杂对象的创建
优缺点:
- 封装性好,构建和表示分离。
- 扩展性好,各个具体的建造者相互独立,有利于系统的解耦。
- 客户端不必知道产品内部组成的细节,建造者可以对创建过程逐步细化,而不对其它模块产生任何影响,便于控制细节风险
缺点:
- 产品的组成部分必须相同,这限制了其使用范围。
- 如果产品的内部变化复杂,如果产品内部发生变化,则建造者也要同步修改,后期维护成本较大
应用场景:
- 相同的方法,不同的执行顺序,产生不同的结果
- 多个部件或零件,都可以装配到一个对象中,但是产生的结果又不相同
- 产品类非常复杂,或者产品类中不同的调用顺序产生不同的作用
- 初始化一个对象特别复杂,参数多,而且很多参数都具有默认值
享元模式
享元模式其实是工厂方法模式的一个改进机制,享元模式同样要求创建一个或一组对象,并且就是通过工厂方法模式生成对象的,只不过享元模式为工厂方法模式增加了缓存这一功能
- 运用共享方式来有效地支持大量细粒度对象的复用
- 具体享元需要实现的公共接口,非享元的外部状态以参数的形式通过方法传入
- 抽象享元角色:所有的具体享元类的基类
- 具体享元角色:现抽象享元角色中所规定的接口
- 非享元角色:不可以共享的外部状态,以参数的形式注入具体享元的相关方法中
- 享元工厂:负责创建和管理享元角色。
当客户对象请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象,
如果存在则提供给客户;
如果不存在的话,则创建一个新的享元对象。 - 通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似类的开销,从而提高系统资源的利用率
优缺点:
- 相同对象只要保存一份,通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似类的开销,从而提高系统资源的利用率
缺点:
- 为了使对象可以共享,需要将一些不能共享的状态外部化,这将增加程序的复杂性
- 读取享元模式的外部状态会使得运行时间稍微变长
应用场景:
- 系统中存在大量相同或相似的对象,这些对象耗费大量的内存资源
- 大部分的对象可以按照内部状态进行分组,且可将不同部分外部化,这样每一个组只需保存一个内部状态
- 由于享元模式需要额外维护一个保存享元的数据结构,所以应当在有足够多的享元实例时才值得使用享元模式
外观模式
- 隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口
- 建立一个外观角色类,定义一个方法,将所有被引用子系统中的方法整理
- 对客户端提供外观角色类,供客户端调用
优缺点:
- 实现子系统与客户端系统之间的松耦合
- 客户端屏蔽了子系统,减少了对各个子系统的引用与维护,使得客户端使用起来更加方便
缺点:
- 不能很好地限制客户使用子系统类
- 增加新的子系统需要修改外观类或者客户端的源码,违背了 “开闭原则”
应用场景:
- 设计初期将不同层分离,层与层之间建立外观模式
- 多个子系统,对外提供一个简单的调用接口
组合模式
- 又称部分整体模式
- 用于把一组相似对象当作一个单一的对象
- 采用集合等方式包含叶子节点(如:类别包含具体的产品对象;组长管理的员工)
优缺点:
- 使得客户端代码可以一致地处理单个对象和组合对象,无需关心自己处理的是单个对象还是组合对象,简化客户端代码
- 更容易在组合体内加入新的对象,客户端不会因为加入新的对象而更改源代码,满足开闭原则
缺点:
- 客户端需要花更多的时间理清类之间的层次关系
- 不容易限制容器中的构建
- 不容易用继承的方法来增加构建的新功能
- 比较难限制组合中的组件类型
应用场景:
- 表示对象的部分-整体结构层次(树形)
- 希望用户忽略组合对象与单个对象的不同,统一的使用组合结构中的所有对象时
桥接模式
- 将抽象与实现分离,使它们可以独立变化
- 用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度
优缺点:
- 抽象和实现的分离,扩展性强
- 实现细节对客户端透明
- 减少了因为继承带来的类爆炸
缺点:
- 聚合关系建立在抽象层,要求开发者针对抽象化设计与编程,增加了系统理解与设计难度
应用场景:
- 有多个角度分类,每一种角度都可能变化
如:图形,图形类型抽象、图形颜色抽象 - 需要在构件的抽象化角色和具体化角色之间增加更多的灵活性
- 不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统