设计模式

这篇文章主要讲解设计模式的知识,同时通过查看类图来揭秘设计并进行debug

创建型模式

工厂方法模式

定义:定义一个创建对象的接口,但让实现这个接口的类来决定实例化哪个类,工厂方法让类的实例化推迟到子类中进行

适用场景:

  • 创建对象需要大量重复代码
  • 客户端(应用层)不依赖于产品类实例如何被创建、实现等细节
  • 一个类通过其子类来指定创建哪个对象

优点:

  • 用户只需关心所需产品对应的工厂,无须关心创建细节
  • 加入新的产品符合开闭原则,提高可扩展性

缺点:

  • 类的个数容易过多,增加复杂度
  • 增加系统的抽象性和理解难度

从以上图中,可看出,Video是一个产品对象的抽象,其子类有JavaVideo、PythonVideo、FEVideo等子类,但是还有以下VideoFactory的抽象工厂,其子类有PythonVideoFactory、JavaVideoFactory、FEVideoFactory等子类来创建Video。很方便的进行扩展和使用,只需要将一个同是VIdeo的对象继承Video,再创建其工厂继承VideoFactory即可。

抽象工厂模式

定义:抽象工厂模式提供了一个创建一些列相关或相互依赖对象的接口

使用场景:

  • 客户端(应用层)不依赖于产品类实例如何被创建、实现等细节
  • 强调一系列相关的产品对象(属于同一类产品族)一起使用创建对象需要大量重复的代码
  • 提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于具体实现

优点:

  • 具体产品在应用层代码隔离,无须关心创建细节
  • 将一个系列的产品族统一到一起创建

缺点:

  • 规定了所有可能被创建的产品集合,产品族中扩展新的产品困难,需要修改抽象工厂的接口
  • 增加了系统的抽象性和理解难度

抽象工厂模式:关注的是面向抽象编程,而且主要是针对那些固定的那种产品族进行设计

建造者模式

定义:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示

特征:用户只需指定需要建造的类型就可以得到它们,建造过程及细节不需要知道

适用场景:

  • 如果对象有一个非常复杂的内部结构(很多属性)
  • 想把复杂对象的创建和使用分离

优点:

  • 封装性好,创建和使用分离
  • 扩展性好、建造类之间独立、一定程度上解耦

缺点:

  • 产生多余的Builder对象
  • 产品内部发生变化,建造者都要修改,成本较大

单例模式

定义:保证一个类仅有一个实例,并提供一个全局访问点

使用场景:想确保任何情况下都绝对只要一个实例

优点:

  • 在内存里只有一个实例,减少了内存开销
  • 可以避免对资源的多重占用
  • 设置全局访问点,严格控制访问

缺点:

  • 没有接口,扩展困难

重点:

  • 私有构造器
  • 线程安全
  • 延迟加载
  • 序列化和反序列化安全
  • 反射攻击

使用技能:

  • 反编译
  • 内存原理
  • 多线程debug

单例和相关设计模式

  • 单例模式和工厂模式
  • 单例模式和享元模式

原型模式

定义:指原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象

特点:不需要知道任何创建的细节,不调用构造函数

使用场景:

  • 类初始化消耗较多的资源
  • new产生的一个对象需要非常繁琐的过程(数据准备、访问权限等)
  • 构造函数比较复杂
  • 循环体中产生大量对象

优点:

  • 性能上比直接new一个对象性能高
  • 简化创建过程

缺点:

  • 必须配备克隆方法
  • 对克隆复杂对象或对克隆出的对象进行复杂改造时,容易引入风险
  • 深拷贝、浅拷贝要运用得当

扩展:

  • 深克隆
  • 浅克隆
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.lzh.design.creational.prototype;

import lombok.Data;
import lombok.ToString;

/**
* 直接让目标类实现克隆接口
* @author Li
**/
@Data
@ToString
public class Mail implements Cloneable{
private String name;
private String emailAddress;
private String content;
public Mail() {
System.out.println("Mail Class Constructor");
}

@Override
protected Object clone() throws CloneNotSupportedException {
System.out.println("clone mail object");
return super.clone();
}
}

结构性模式

适配器模式

定义:将一个类的接口转换成客户期望的另一个接口,使原本接口不兼容的类可以一起工作

使用场景:

  • 已经存在的类,它的方法和需求不匹配时(方法结果相同或相似)
  • 不是软件设计阶段考虑的设计模式,是随着软件维护,由于不同产品、不同厂家造成功能类似而接口不相同情况下的解决方案

优点:

  • 能提高类的透明性和服用,现有的类服用但不需要改变
  • 目标类和适配器解耦,提高程序扩展性
  • 符合开闭原则

缺点:

  • 适配器编写过程需要全面考虑,可能会增加系统的复杂性
  • 增加系统代码可读的难度

扩展:

  • 对象适配器模式
  • 类适配器

装饰者模式

定义:在不改变原有对象的基础上,将功能附加到对象上,提供了比继承更有弹性的替代方案(扩展原有对象的功能)

使用场景:

  • 扩展一个类的功能或给一个类添加附加职责
  • 动态的给一个对象添加功能,这些功能可以动态的撤销

优点:

  • 继承的有力补充,比继承灵活,不改变原有对象的情况下给一个对象扩展功能
  • 通过使用不同装饰类以及这些装饰类的排列组合,可以实现不同的效果
  • 符合开闭原则

缺点:

  • 会出现更多的代码,更多的类,增加程序的复杂性
  • 动态修饰时,多层装饰会更加复杂

代理模式

外观模式

定义:门面模式,提供了一个统一的接口,用来访问子系统中的一群接口。定义了一个高层接口,让子系统访问更加容易使用

使用场景:

  • 子系统越来越复杂,增加外观模式提供简单调用接口
  • 构建多层系统结构,利用外观对象作为每层的入口,简化层间调用

优点:

  • 简化了调用过程,无需了解深入子系统,防止带来风险
  • 减少系统依赖、松散耦合
  • 更好的划分访问层次
  • 符合迪米特法则,即最少知道原则

缺点:

  • 增加子系统、扩展子系统行为容易引入风险
  • 不符合开闭原则

相关设计模式:

  • 外观模式和中介者模式
  • 外观模式和单例模式
  • 外观模式和抽象工厂模式

facade

桥接模式

定义:将抽象部分与它的具体实现部分分离,使它们都可以独立的变化,通过组合的方式建立两个类之间的关系,而不是继承

使用场景:

  • 抽象和场景之间增加更多的灵活性
  • 一个类存在两个(或多个)独立变化的维度,且这两个(或多个)维度都需要独立进行扩展
  • 不希望使用继承,或因为多层继承导致系统类的个数增加

优点:

  • 分离抽象部分和具体实现部分
  • 提高了系统的可扩展性
  • 符合开闭原则

缺点:

  • 增加了系统的理解与设计难度
  • 需要正确的识别出系统中两个独立变化的维度

组合模式

定义:将对象组合成树形结构以表示”部门-整体”的层次结构,使得客户端对单个对象和组合对象保持一致的方式处理

使用场景:

  • 希望客户端可以忽略组合对象与单个对象的差异
  • 处理一个树形结构

优点:

  • 清除地定义分层次的复杂对象,表示对象的全部或部分层次
  • 让客户端忽略层次的差异,方便对整个层次结构进行控制
  • 简化客户端代码
  • 符合开闭原则

缺点:

  • 限制类型时会较为复杂
  • 使设计变得更加抽象



享元模式

定义:提供了减少对象数量从而改善应用所需的对接结构的方式,运用共享技术有效的支持大量细粒度的对象

使用场景:

  • 常常应用于系统底层的开发,以便解决系统的性能问题
  • 系统有大量的相似对象、需要缓存池的场景

优点:

  • 减少对象的创建、降低内存中的对象的数量、降低系统的内存、提高效率
  • 减少内存之外的其他资源的占用

缺点:

  • 关注内部和外部状态、关注线程安全问题
  • 使系统程序的逻辑复杂化

扩展:

  • 内部状态
  • 外部状态

行为型模式

策略模式

观察者模式

责任链模式

备忘录模式

模板方法模式

迭代器模式

中介者模式

命令模式

访问者模式

解释器模式

状态模式