设计模式使用场景优缺点汇总
Posted ConstXiong
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计模式使用场景优缺点汇总相关的知识,希望对你有一定的参考价值。
创建型
1、单例模式:
Singleton Design Pattern
一个类只允许创建一个对象(或者实例),这个类就是一个单例类
单例的实现:饿汉式、懒汉式、双重检查、静态内部类、枚举
作用:避免频繁创建和销毁系统全局使用的对象
应用场景:全局唯一类,如 系统配置类、系统硬件资源访问类;公共服务类:如 servlet、springmvc 中的 bean、数据库连接池;Web 计数器、序列号生成器
缺陷:对 OOP 特性的支持不友好、隐藏类之间的依赖关系、对代码的扩展性不友好、对代码的可测试性不友好、不支持有参数的构造函数
替代方案:程序员控制、工厂模式、IOC 容器
扩展:线程纬度的单例、集群环境下的单例、多例模式
2、工厂模式:
Factory Design Pattern
分为:简单工厂、工厂方法和抽象工厂。简单工厂可以看作是工厂方法的一种特例
作用:封装变化(创建逻辑有可能变化,封装成工厂类之后,创建逻辑的变更对调用者透明)、代码复用(创建代码抽离到独立的工厂类之后可以复用)、隔离复杂性(封装复杂的创建逻辑,调用者无需了解如何创建对象)、控制复杂度(将创建代码抽离出来,让原本的函数或类职责更单一,代码更简洁)
简单工厂适用场景:对象的创建逻辑比较简单,if-else 并不多
工厂方法适用场景:对象的创建逻辑比较复杂,不只是简单的 new 一下就可以,而是要组合其他类对象,做各种初始化操作;避免 if-else
抽象工厂适用场景:可以创建多种不同类型的工厂,能够创建一簇对象,减少了工厂类
简单工厂、工厂方法、抽象工厂之间的关系与选择:如果实例对象的创建逻辑并发不复杂、实例对象的种类并不多即 if 分支不多,且基本不会有太大变动,使用简单工厂模式;需要创建的实例化对象种类较多、变动可能性大、对象的创建逻辑比较复杂,使用工厂方法,可以去除 if 多分支、将复杂的对象创建逻辑封装到每个具体工厂中;抽象工厂的使用并不多,适用于实例对象的层级逻辑比较复杂,抽象出多个实例对象组,每个工厂创建一组实例对象
3、建造者:
Builder Design Pattern
应用场景:能够解决构造方法的参数很多传错参数的问题;可以保证类属性字段安全,不用提供 public setter 方法;可以进行属性字段间的校验、计算、填充等
缺点:创建对象前必须创建 Builder 对象,多一些性能开销,对性能要求极高的的场景下慎用;代码有点啰嗦
4、原型模式:
Prototype Design Pattern
作用:对象的创建成本比较大,同一个类的不同对象之间的大部分字段相同,可以对已有对象进行拷贝的方式创建新对象,达到节省新对象的创建时间的目的
方式:浅拷贝(仅复制基本类型、引用类型的属性变量、引用类型变量的指针;并不复制引用类型变量指向的堆内存中的对象,而是指向同一个对象)、深拷贝(复制基本类型、应用类型的属性变量、引用类型变量的指针、引用类型指向的堆内存中对象,获得一个完全独立的对象)
实现:浅拷贝(实现 Cloneable 接口,重写 clone 方法)、深拷贝(实现 Cloneable 接口,递归 clone 引用对象或 new 新对象;借助序列化完成深拷贝)
结构型
1、代理模式:
Proxy Design Pattern
作用:在不改变原始类(或叫被代理类)代码的情况下,通过引入代理类来给原始类附加功能。使用代理对象完成用户请求,屏蔽用户对真实对象的访问
方式:静态代理、动态代理(JDK 自带、cglib、Javassist 可实现)
应用场景:代理模式常用在业务系统中开发一些非功能性需求,比如:监控、统计、鉴权、限流、事务、幂等、日志。将这些附加功能与业务功能解耦,放到代理类统一处理,程序员只需要关注业务方面的开发;远程代理、虚拟代理、安全代理...;框架开发,如 Spring AOP 功能(JDK动态代理、cglib)
缺点与注意:静态代理需要一个代理类对应一个被代理类,容易造成类文件增加很多,增加大量的维护工作;JDK 动态代理,需要满足一个条件,就是被代理类需要实现接口,否则会抛出 java.lang.ClassCastException 异常;随着 JVM 的优化,JDK 1.8 开始 JDK 动态代理的性能赶上并超越 cglib;cglib 采用动态创建子类的方法,final 修饰的方法无法进行代理;cglib 创建代理对象时所花费的时间却比 JDK 动态代理要多
2、桥接模式:
Bridge Design Pattern
作用:将抽象和实现解耦,让它们可以独立变化;让一个类拥有两个或多个独立变化的维度,通过组合的方式,让这两个或多个维度可以独立进行扩展
实际应用:JDBC 驱动程序的加载、JVM 在多操作系统间的实现、GUI 在多操作系统间的实现
优点:分离抽象接口与实现、类似多继承但比多继承灵活、可扩展性强、功能对用户透明,隐藏了实现细节
缺点:需要抽象出变化的维度,对业务理解程度要求较高、对系统的设计能力要求较高、代码较难理解
3、装饰器模式:
Decorator Design Pattern
作用:能够动态地给一个对象添加额外的职责,灵活地生成子类;主要解决了原始类在多个方向上功能扩展的问题;原始类可以嵌套使用多个装饰器类,装饰器类需要跟原始类继承相同的抽象类或实现相同的接口;装饰器类具有 is-a 和 has-a 的双重关系
角色:被修饰对象的基类、具体被装饰的类、装饰者的抽象类、具体的装饰类
优点:可以动态扩展一个实现类的功能,装饰类和被装饰类可以独立扩展,不会相互耦合
缺点:层次关系比较复杂
4、适配器模式:
Adapter Design Pattern
作用:适配,将不兼容的接口转换为可兼容的接口,让原本由于接口不兼容而不能一起工作的类可以一起工作
应用场景:对有缺陷的接口设计进行再次封装、统一多个类的接口设计、替换依赖的系统、兼容老版本接口、适配不同格式的数据
实际例子:JDK Iterator 适配 Enumeration;slf4j 对不同的日志框架进行了适配
优点:适配器模式是一种补充模式,可以用来系统的后期扩展与修改
缺点:不适合过多使用,否则代码中的方法调用比较乱
5、门面模式:
Facade Design Pattern
门面模式为子系统提供一组统一的接口,定义一组高层接口让子系统更易用
作用:封装系统的底层实现,隐藏系统的复杂性,提供一组更加简单易用、更高层的接口;客户端原本对多个子系统接口的调用,改为调用门面的一个接口,较少交互,提升性能;解决事务问题
6、组合模式:
Composite Design Pattern
作用:将一组对象组织成树状结构,以表示部分和整体的层次结构,让客户端可以统一单个对象和组合对象的逻辑;更像是一种树形结构数据场景下的数据结构与算法的抽象
使用场景:主要用来处理树形结构的数据。如部门、人员组成树状结构,计算薪资
优点:灵活增加节点、调用简单、代码简洁
缺点:适用场景非常单一
7、享元模式:
Flyweight Design Pattern
作用:复用对象,节省内存。(前提是享元对象是不可变对象)
应用场景:当一个系统中存在大量重复对象的时候,可以利用享元模式,将对象设计成享元,在内存中只保留一份实例,供多处代码引用,减少内存中对象的数量,节省内存。如JDK 中 Integer 类就使用了享元模式缓存了 -128 ~ 127 范围的整数
实现:主要是通过工厂模式,在工厂类中,通过一个 Map 或者 List 来缓存已经创建好的享元对象,以达到复用的目的
行为型
1、观察者模式:
Observer Design Pattern
定义:在对象之间定义一个一对多的依赖,当一个对象状态改变的时候,所有依赖的对象都会自动收到通知
优点:降低了目标与观察者之间的耦合关系、目标与观察者之间建立了一套触发机制
缺点:当观察者对象很多时,通知的发布会花费很长时间
应用场景:对象间存在一对多关系,一个对象的状态发生改变会影响其他对象
实际应用:邮件订阅、RSS Feeds、MQ 消息发布与订阅
2、模板模式:
Template Method Design Pattern
定义:模板方法模式在一个方法中定义一个算法骨架,并将某些步骤推迟到子类中实现。模板方法模式可以让子类在不改变算法整体结构的情况下,重新定义算法中的某些步骤
实际应用:Java InputStream、Java AbstractList、Java Servlet、JUnit TestCase
作用:代码复用(子类可以复用父类中提供的模板方法的代码)和扩展(提供功能扩展点,可以在不修改框架源码的情况下,基于扩展点定制化框架的功能)
缺点:某种实现都需要子类,类的数量增多;代码可读性变差
3、策略模式:
Strategy Design Pattern
定义:定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户
理解:包含策略的定义、实现类的创建、静态或者动态使用不同策略
应用场景:替代根据类型进行多层 if -else 判断
优点:避免使用多重条件语句;避免重复代码;提供相同行为的不同实现;符合开闭原则,灵活增加新算法
缺点:策略实现类会比较多
4、职责链模式:
Chain of Responsibility Design Pattern
作用:将请求的发送和接收解耦,让多个接收对象都有机会处理这个请求。将这些接收对象串成一条链,并沿着这条链传递这个请求,直到链上的某个接收对象能够处理它为止
优点:将请求的发送者和接收者解耦;增强了系统的可扩展性,根据需要增加新的请求处理类,满足开闭原则;责任分担,每个类只需要处理自己该处理的工作,明确责任范围,符合单一职责原则
缺点:不能保证每个请求一定被处理
使用场景:可动态指定一组对象处理请求
实际应用:Servlet Filter、Spring Interceptor、Dubbo Filter、 Netty ChannelPipeline
5、状态模式:
State Design Pattern
定义:对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为
应用场景:当一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为;一个操作中含有庞大的分支结构,并且这些分支决定于对象的状态
实际应用:状态机、游戏中不同人物角色的切换
优点:封装状态变化、减少对象间的相互依赖、利于程序的扩展
缺点:结构与实现都较为复杂
6、迭代器模式:
Iterator Design Pattern
定义:提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示
实际应用:JDK 中 Iterator 接口
优点:封装集合内部的复杂数据结构,;将集合对象的遍历操作从集合类中拆分出来,职责单一;添加新的遍历算法更加容易,更符合开闭原则
7、访问者模式:
Visitor Design Pattern
定义:允许一个或者多个操作应用到一组对象上,解耦操作和对象本身
作用:在被访问的类里面加一个对外提供接待访问者的接口,解决稳定的数据结构和易变的操作耦合问题
应用场景:对象结构相对稳定,但其操作算法经常变化;对象提供多种不同且不相关的操作,避免对结构的影响;
优点:不修改对象结构,为对象结构中的元素添加新的功能,扩展性好;通过访问者来定义整个对象结构通用的功能,提高复用性;将数据结构与作用于结构上的操作解耦,灵活性好;访问者模式把相关的行为封装在一起,符合单一职责原则
缺点:增加新的元素类很困难,违反开闭原则;具体元素对访问者公布细节,这破坏了对象的封装性;依赖了具体类,违反了依赖倒置原则;难理解、难实现
8、备忘录模式:
Memento Design Pattern
定义:在不违背封装原则的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便之后恢复对象为先前的状态
作用:提供了恢复状态的机制;实现了内部状态的封装,满足单一职责原则
实际场景:游戏存档、编辑器和 IDE 的 ctrl+Z 回滚、数据库回滚
缺点:内容过多或者过频繁备份,资源消耗大;适用场景单一
9、命令模式:
Command Design Pattern
定义:将请求(命令)封装为一个对象,这样可以使用不同的请求参数化其他对象(将不同请求依赖注入到其他对象),并且能够支持请求(命令)的排队执行、记录日志、撤销等(附加控制)功能
作用:将调用操作的对象与实现该操作的对象解耦;增加或删除命令非常方便,不影响其他类,符合开闭原则
实际应用:游戏开发中将把请求包含的数据和处理逻辑封装为命令对象执行;与备忘录模式组合,实现 redo、undo 功能
缺点:产生大量具体命令类
10、解释器模式:
Interpreter Design Pattern
定义:为某个语言定义它的语法(或者叫文法)表示,并定义一个解释器用来处理这个语法
作用:构建固定文法的句子的解释器
缺点:通常使用大量的循环和递归调用,当要解释的句子较复杂时,其运行速度很慢;较难调试;包含的文法规则很多时会引起类膨胀;适用场景单一
11、中介模式:
Mediator Design Pattern
定义:定义了一个单独的(中介)对象,来封装一组对象之间的交互。将这组对象之间的交互委派给与中介对象交互,来避免对象之间的直接交互
作用:引入中介这个中间层,将对象间的一对多关联转变为一对一的关联,只需要跟中介对象交互即可
优点:降低了代码的复杂度,提高了代码的可读性和可维护性
缺点:中介者的职责很大时,代码将复杂,可维护性降低
建议点击 阅读原文 查看在线思维导图
以上是关于设计模式使用场景优缺点汇总的主要内容,如果未能解决你的问题,请参考以下文章
设计模式策略模式 ( 简介 | 适用场景 | 优缺点 | 代码示例 )