设计模式大全(六大原则、创建型、结构型、行为型)
设计模式
UML类图
概念
类图共三层,分别是:
- 类的名称,如果是抽象类,则为斜体显示
- 类的特性,通常是字段或属性
- 类的操作,通常是方法或行为
- 其中“+”表示public,“-”表示private,“#”表示protected
- 如果是接口的话,在类名上方有<>字样
关系
- 继承:空心三角形+实线
- 实现:空心三角形+虚线
- 关联:实线箭头,表示了解,知道
- 依赖:虚线箭头,表示强需要
- 组合(合成):实心菱形+实线箭头,表示强拥有关系,体现了严格的部分与整体的关系,生命周期相同
- 聚合:空心菱形+实线箭头,箭头指向个体。表示弱拥有关系,提现A可以包含B,但B不是A对象的一部分
设计模式六大原则
- 单一职责:应该有且仅有一个原因引起类的变更,这是模块内聚性在类和类的职责中的体现
- 开闭原则:一个软件实体如类、模块和函数应该对扩展开放,对修改封闭
- 里氏替换原则:所有引用基类(父类)的地方必须能透明地使用其子类的对象
- 依赖倒置原则:高层模块不应该依赖于低层模块,二者都应该依赖于抽象。抽象不应该依赖于细节,细节应该依赖于抽象。可以这样说:要针对接口编程,不应该针对实现编程
- 迪米特原则:又称为最少知识法则LKP。一个对象应当对其他对象尽可能少的了解;一个软件实体应当尽可能少地与其他实体发生相互作用
- 接口隔离原则:一个类对另一个类的依赖应该建立在最小的接口上。应当为客户端提供尽量小的单独的接口,而不是提供大的接口;使用多个专门的接口比使用单一的总接口要好。
- 组合重用原则(书中有,部分blog中没有):要尽量使用组合,而不是继承关系达到重用的目的
设计模式分类
创建型-5
- 工厂方法模式、抽象工厂模式、建造者模式、单例模式、原型模式
结构型-7
- 适配器、装饰、代理、外观、桥接、组合、享元
行为型-11
- 策略、模版方法、观察者、访问者、中介者、迭代器、责任链、备忘录、命令、状态、解释器
简单工厂模式
-
概念
又称为静态工厂方法模式,在简单工厂模式中,可以根据参数的不同返回不同类的实例。简单工厂专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。简单工厂不属于GOF设计模式
-
优点
客户端可以免除直接创建产品对象的责任,而仅仅是“消费”产品
-
缺点
不符合开闭原则,系统扩展困难
-
使用场景 ★★★★☆
- 工厂类负责创建的对象比较少。由于创建的对象较少,不会造成工厂方法中的业务逻辑太过复杂
- 客户端只知道传入工厂类的参数,对于如何创建对象不关心,更不关心创建的细节
创建型
工厂方法模式
-
概念
定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法是一个类的实例化延迟到其子类
-
优点
- 工厂方法用来创建客户所需的产品,向客户隐藏了那个具体产品类将被实例化这一细节。用户只需关心产品对应的工厂,无需关心创建细节
- 它能够使工厂可以自主确定创建何种产品对象,而如何创建这个对象的细节则完全封装在具体工厂内部
- 符合开闭原则,新增产品时,无需修改抽象工厂和抽象产品提供的接口
-
缺点
- 新增产品时,需要编写新的具体产品类,还需要提供与之对应的具体工厂类。系统中类的个数将成对增加,在一定程度上增加了系统的复杂度
- 由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象度和理解难度
-
使用场景 ★★★★★
- 一个类不知道他所需要的对象的类:客户端需要知道创建具体产品的工厂类
- 一个类通过其子类来指定创建哪个对象:在工厂方法模式中,对于抽象类只需要提供一个创建产品的接口,而由其子类来确定具体要创建的对象,利用面向对象的多态性和里氏替换原则,在程序运行时,子类对象将覆盖父类对象,从而使系统更容易扩展
- 将创建对象的任务委托给多个工厂子类中的某一个,客户端在使用时可以无需关心是哪一个工厂子类创建产品子类,需要时再动态指定,可将具体工厂类的类名存储在配置文件或数据库中
-
类图
抽象工厂模式
-
概念
提供一个创建一系列相关或相互依赖对象的接口,而无需指定他们具体的类
-
优点
- 易于交换产品系列:由于具体工厂类在一个应用中只需要被实例化一次,这使得改变一个应用的具体工厂变得非常容易,它只需要改变具体工厂即可使用不同的产品配置
- 它让具体的创建实例过程与客户端分离,客户端是通过它们的抽象接口操纵实例,产品的具体类名也被具体工厂的实现分离,不会出现在客户代码中
-
缺点
- 在添加新的产品对象时,难以扩展抽象工厂来生产新种类的产品,这是因为在抽象工厂角色中规定了所有可能被创建的产品集合,要支持新种类的产品就意味着要对该接口进行扩展,而这将涉及对抽象工厂角色及其所有子类的修改,显然会带来较大的不变
-
使用场景★★★★★
- 系统中有多于一个的产品组,而每次只使用其中某一产品组。可以通过配置文件动态改变产品组,如DB
-
类图
建造者模式
-
概念
将一个复杂对象的构建和它的表示分离,使得同样的构建过程可以创建不同的表示。个人理解:类似于KFC套餐的概念,不同的套餐就是具体的建造实现类
-
优点
- 客户端不必知道产品内部组成的细节,将产品本身和产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象
- 每一个具体建造者都相互独立,与其他具体建造者无关,可以方便的替代或新增
- 可以更加精细的控制产品的创建过程
- 新增建造者无需修改原有的代码,符合开闭原则
-
缺点
- 建造者模式创建的产品一般具有较多的共同点,其组成部分相似。如果产品之间的差异很大,则不适合使用建造者模式
- 如果产品内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化
-
使用场景★★☆☆☆
- 需要生成的产品对象的属性相互依赖,需要指定其生成顺序
- 对象的创建过程独立于创建该对象的类
- 隔离复杂对象的创建和使用
-
类图
单例模式
-
概念
保证一个类仅有一个实例,并提供一个访问它的全局访问点
-
优点
- 提供了对唯一实例的受控访问,它可以严格控制客户怎样以及何时访问它
- 节约系统资源
-
缺点
- 由于单例模式中没有抽象层,因此单例类的扩展有很大困难
- 单例类职责过重,一定程度上违背了”单一职责原则“。因为单例类既充当了工厂角色,提供工厂方法,同时又充当了产品角色,包含了一些业务方法,将产品的创建和产品本身的功能融合到一起
- 滥用会带来负面问题,如为了节省资源将数据库连接池对象设计为单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出
-
使用场景★★★★☆
- 系统只需要一个实例对象。如系统要求提供一个唯一的***生成器
- 客户调用类的单个实例只允许使用一个公共访问点,除了该访问点,不能通过其他路径访问
-
类图
原型模式
-
概念
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象
-
优点
- 简化复杂对象的创建过程
- 可以动态增加或减少产品类
- 可以使用深克隆的方式保存对象的状态
-
缺点
- 需要为每一个类配备一个克隆方法,这个克隆方法需要对类的功能通盘考虑
- 实现深克隆时需要编写较为复杂的代码
-
使用场景★★★☆☆
- 创建对象成本较大的场景
-
类图
结构型
适配器模式
-
概念
将一个类的接口转换为客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作
-
优点
- 将目标类和和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,无需修改原有代码
- 增加了代码的透明性和复用性,将具体的实现封装在适配者类中,对于客户端来说是透明的,而且提高了适配者的复用性
- 灵活性和扩展性都比较好,通过配置文件,可以方便的更换适配器,也可以在不修改原有代码的基础上增加新的适配器,符合开闭原则
-
缺点
- 对于java、C#等不支持多重继承的语言,一次最多只能适配一个适配者类,而且目标只能是接口,不能是类,其使用有一定的局限性,不能将一个适配者类和它的子类同时适配到目标接口
-
使用场景★★★★☆
- 需要使用现有的类,而这些类的接口不符合系统的需要
- 想要建立一个可以重复使用的类,用于一些彼此没有太大关联的类一起工作
-
类图
组合模式
-
概念
将对象组合成树形结构以表示’部分-整体‘的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性
-
优点
- 组合模式可以清楚地定义分层次的复杂对象,表示对象的全部或不分层次,使得增加新构件也变得容易
- 客户端调用简单,客户端可以一致的使用组合结构或其中单个对象
-
缺点
- 使设计变得更加抽象,不是所有方法与叶子对象子类都有关联
- 增加新构件可能会产生一些问题。有时候我们希望一个容器中只能有某些特定类型的对象,使用组合模式时不能依赖类型系统来施加这些约束,因为他们都来自相同的抽象层,必须通过在运行时进行类型检查来实现,实现过程会变得复杂
-
使用场景★★★★☆
- 需要表示一个对象整体和部分层次。在这种层次结构中,希望忽略整体和部分的差异,可以一致对待
-
类图
代理模式
-
概念
为其他对象提供一种代理以控制对这个对象的访问
-
优点
- 代理模式能够协调调用者和被调用者,在一定程度上降低了系统的耦合度
-
缺点
- 在客户端和真是主题之间增加了代理对象,因此有些类型的代理可能会造成请求处理变慢
- 实现代理需要额外的工作,有些代理模式实现特别复杂
-
使用场景★★★★☆
远程代理、虚拟代理、保护代理等
-
类图
装饰模式
-
概念
动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更加灵活
-
优点
- 可以提供比继承关系更多的灵活性
- 可以使用不同的装饰类排列组合,创造出很多不同的行为组合
- 具体构件类和具体装饰类可以独立变化,用户可以灵活新增并自由组合,符合开闭
-
缺点
- 会产生许多小对象,增加系统复杂度
- 比继承更灵活,也就意味着比继承更容易出错,排错也困难
-
使用场景★★★☆☆
- 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加指责
- 不能继承或者继承不利于系统扩展和维护时
-
类图
外观模式
-
概念
为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用
-
优点
- 完美地体现了依赖倒置原则和迪米特法则的思想
- 对客户屏蔽子系统组件。客户端代码变得较少,与之关联的对象也很少
- 实现了子系统与客户端的松耦合,降低了编译依赖性
-
缺点
- 在不引入抽象外观类的情况下,新增子系统时可能需要修改外观类或客户端的源代码
-
使用场景★★★★★
- 为复杂系统提供简单接口
- 客户程序与多个子系统之间存在很大的依赖性
- 在层次化结构中,可以使用外观模式定义系统中每一层的入口,层与层之间通过外观类建立联系,降低层之间的耦合度
-
类图
桥接模式
-
概念
将抽象部分与它的实现分离,使他们可以独立地变化
-
优点
- 分离抽象接口及其实现部分,使其可以沿着各自的维度来变化
- 桥接模式提高了系统的可扩展性,在两个变化维度人以扩展,都不需要修改原系统
-
缺点
- 增加系统的理解和设计难度
- 桥接模式要求正确识别出系统中两个独立变化的维度,因此其适用范围有一定的局限性
-
使用场景★★★☆☆
- 一个类存在两个独立变化的维度,且这两个维度都需要扩展
- 如果系统想要在抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承关系
-
类图
享元模式
-
概念
运用共享技术有效地支持大量细粒度的对象
-
优点
- 它可以极大减少内存中对象的数量,使得相同或相似对象在内存中只保存一份
- 享元模式外部状态相互独立,而且不会影响内部状态,,从而享元模式可以被不同环境共享
-
缺点
- 享元模式使得系统更加复杂,需要分离出内部状态和外部状态,逻辑复杂化
- 为了是对象可以共享,享元模式需要将享元对象的状态外部化,而读取外部状态使得运行时间变长
-
使用场景★☆☆☆☆
- 一个系统拥有大量相同或相似的对象,由于这些对象的大量使用,造成内存的大量耗费
- 对象的大部分状态都可以外部化,可以将这些外部状态传入对相爱那个中
- 需要维护一个存储享元对象的享元池,这需要耗费资源,因此当多次重复使用享元对象时,才值得用享元模式
-
类图
行为型
策略模式
-
概念
它定义了算法家族,分别封装起来,让它们之间可以相互替换,此模式让算法的变化,不会影响到使用算法的客户
-
优点
- 完美符合开闭原则,可以在不修改原系统的基础上选择算法和行为,也可以灵活的新增
- 可以避免使用多重条件转移语句,即if else
-
缺点
- 客户端必须知道所有的策略,并自行决定使用哪一种策略
- 策略模式将造成很多策略类和对象,可以通过使用享元模式在一定程度上减少对象的数量
-
使用场景★★★★☆
- 如果一个系统中有许多类,他们的区别仅限于他们的行为,可以使用策略模式动态地让一个对象在许多行为中选择一种行为
- 系统需要在几种算法中选择一种
- 规避多重的条件选择语句的场景
- 个人理解:策略模式即使与简单工厂模式组合,还是需要必要的if或switch判断。可以把这些判断条件加入到各个算法中,让算法自己匹配
-
类图
状态模式
-
概念
当一个对象的内在状态改变时,允许改变其行为,这个对象看起来就像是改变了其类
-
优点
- 封装了转换规则,不需要通过条件判断语句来进行状态的判断和转移,提高了代码的可维护性
- 将所有与某个状态有关的行为封装到一个类中,可以方便的增加新的状态
-
缺点
- 状态模式的使用必然会增加系统类和对象的个数
- 结果和实现都比较困难,实现不当会导致代码混乱
-
使用场景★★★☆☆
- 对象的行为依赖于它的状态,并且可以根据它的状态改变,改变它的行为。例如银行账号,不同状态时,行为有差异
- 代码中包含大量与对象状态有关的条件语句时
-
类图
观察者模式
-
概念
定义了一种一对多的关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有的观察者对象,使他们能够自动更新自己
-
优点
- 可以实现表现层和数据逻辑层的分离,并定义了稳定的消息更新传递机制,抽象了更新接口
- 观察者模式在观察目标和观察者之间建立了一个抽象耦合,使他们属于不同的抽象化层次
- 简化了一对多的设计难度,符合开闭原则
-
缺点
- 如果观察者和被观察者有循环依赖,可能会触发循环调用,导致系统崩溃
-
使用场景★★★★★
- 一个抽象模型有两个方面,其中一个方面依赖另一个方面。将这些对象封装在独立的对象中使它们可以各自独立地改变和复用
- 需要在用户中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象,可以使用观察者模式创建一种链式触发机制
-
类图
责任链模式
-
概念
使多个对象都有机会处理请求,从而避免请求的发送者与请求的接受者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,知道有一个对象处理它为止。
-
优点
- 降低耦合度:接收者和发送者都没有对方的明确信息,且链中对象不需要知道链的结构,由客户端负责链的创建
- 简化对象之间的相互连接,增强给对象指派职责的灵活性
- 增加新的请求处理类很方便:在系统中增加一个新的具体请求处理者无需修改原有系统的代码,只需要在客户端重新建链即可,从这一点来看符合开闭原则。
-
缺点
- 不能保证请求一定会被接收。既然请求没有了明确的接收者,那么就不能保证它一定会被处理
- 对于较长的职责链,请求的处理可能涉及到多个处理对象,系统性能受到一定影响,而且代码调试不方便
-
使用场景★★☆☆☆
- 有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时刻自动确定
- 可动态指定一组对象处理请求
- 个人理解:可以和观察者模式结合,每个对象处理完,指定下一个处理的对象,然后放到EventBus中,post后,下一个对象自动处理请求
-
类图
模版方法模式
-
概念
定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模版方法使得子类可以不改变一个算法的结构即可重新定义该算法的某些特定步骤
-
优点
- 把不变的行为搬移到超类,去除子类中的重复代码
- 模板方法是一种代码复用的基本技术,它在类库设计中非常重要,提取了类库中的公共行为,将公共行为放到父类中,而通过其子类来实现不同的行为
- 是一种反向的控制结构,通过一个父类调用其子类的操作,通过对子类的扩展增加新的行为,符合开闭
-
缺点
- 每个而不同的实现都需要定义一个子类,这回导致类的个数增加,系统更加庞大,设计也更加抽象,但是更加符合”单一职责原则“,使得类的内聚性更高
-
使用场景★★★☆☆
- 一次性实现一个算法的不变部分,将可变性为留给子类来实现
- 对一些复杂的算法进行分割,将其算法中固定不变的部分设计为模板方法和父类具体方法,而一些可以改变的细节由其子类实现
- 广泛应用于框架设计(Spring、Struts等)中,以确保父类控制处理流程的逻辑顺序(如框架的初始化)
-
类图
迭代器模式
-
概念
提供一个方法,顺序访问一个聚合对象中的各个元素,而又不暴露对象的内部表示
-
优点
- 简化了聚合类
- 新增聚合类和迭代器类都很方便,无需修改原有代码,满足”开闭原则“的要求
-
缺点
- 由于迭代器模式将存储数据和遍历数据的职责分开,增加新的聚合类需要对应增加新的迭代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性
-
使用场景★★★★★
- 为遍历不同的聚合结构提供统一的接口
- 需要为聚合对象提供多种遍历方法
- JDK1.2引入的Collections就是迭代器模式
-
类图
命令模式
-
概念
将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作
-
优点
- 降低系统耦合度,请求者和接收者之间不存在直接引用,因而实现完全解耦。相同的请求者可以对应不同的接收者,相同的接收者可以供不同的请求者使用
- 新的命令可以很容易地加入到系统中
- 可以比较容易地设计一个命令队列和宏命令
- 可以方便地实现对请求的Undo和Redo
-
缺点
- 可能会导致系统有过多的命令类
-
使用场景★★★★☆
- 系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互
- 系统需要在不同的时间指定请求、将请求排队和执行请求
- 系统需要支持命令的撤销Undo和恢复Redo
- 系统需要将一组操作组合在一起,即支持宏命令
-
类图
备忘录模式
-
概念
在不破坏封闭性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态
-
优点
- 提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤,当心的状态无效或者存在问题时,可以使用先前存储起来的备忘录将状态复原
-
缺点
- 资源消耗过大,如果类的成员变量太多,就不可避免占用大量的内存
-
使用场景★★☆☆☆
- 保存一个对象在某一时刻的状态或部分状态,这样以后需要时,它能够恢复到先前的状态
-
类图
中介者模式
-
概念
用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显示地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互
-
优点
- 简化了对象之间的交互,将各同事解耦,减少子类的生成
-
缺点
- 具体d 中介者类中包含了同事之间的交互细节,可能导致终中介者类变得复杂,难以维护
-
使用场景★★☆☆☆
- 系统中对象之间存在复杂的引用关系,相互依赖关系结构混乱且难以理解
- 一个对象由于引用了其他很多对象并且直接和这些对象通信,导致难以复用该对象
-
类图
访问者模式
-
概念
表示一个作用于某对象中各元素的操作。它使你可以在不改变各元素类的前提下定义作用于这些元素的新操作
-
优点
- 使得新增访问操作变得容易
- 将有关元素对象的访问行为集中到一个访问者对象中,而不是分散到一个个的元素类中
-
缺点
- 增加新元素类比较困难
- 破坏封装
-
使用场景★☆☆☆☆
- 一个对象结构包含很多类型的对象,希望对这些对象实施一些依赖其具体类型的操作
- 对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作
-
类图
解释器模式
-
概念
给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子
-
优点
- 易于改变和扩展文法,易于实现文法
- 增加了新的解释表达式的方式
-
缺点
- 对于复杂文法难以维护
- 执行效率较低
- 应用场景有限
-
使用场景★☆☆☆☆
- 可以将一个需要解释执行的语言中的句子表示为一个抽象语法树
- 一些重复出现的问题可以用一种简单的语言来进行表达
- 文法较为简单
- 效率不是关键问题
-
类图
参考文献
- 如何写出优秀的代码?设计模式六大原则告诉你
- 《大话设计模式》-程杰
- 《设计模式》-刘伟