设计模式学习笔记

设计模式学习笔记

  • 设计模式
    • OOP七大原则
    • 创建型模式
      • 单例模式
      • 建造者模式
      • 原型模式
      • 工厂模式
      • 抽象工厂模式
    • 结构型模式
      • 代理模式
      • 适配器模式
      • 桥接模式
      • 过滤器模式
      • 外观模式
      • 享元模式
      • 装饰器模式
      • 组合模式
    • 行为型模式
      • 备忘录模式
      • 策略模式
      • 迭代器模式
      • 访问者模式
      • 观察者模式
      • 解释器模式
      • 空对象模式
      • 命令模式
      • 模板模式
      • 责任链模式
      • 中介者模式
      • 状态模式
    • J2EE模式
      • MVC模式
      • 传输对象模式
      • 服务定位器模式
      • 拦截过滤器模式
      • 前端控制器模式
      • 数据访问对象模式
      • 业务代表模式
      • 组合实体模式

设计模式

  • 设计模式(Design Pattern)是前辈们们对代码开发经验得总结,是解决特定问题得一系列套路。它不是语法规定,而是一套用来提高代码可复用性、可维护性、可读性、稳健性以及安全性得解决方案。
  • 1995年,GoF(Gang of Four,四人组/四人帮)合作出版了《设计模式:可复用面向对象软件得基础》一书,共收录了23种设计模式,从此树立了软件设计模式领域得里程碑,人称“GoF设计模式
  • GoF 23:一种思维,一种态度,一种进步
  • 设计模式的本质是面向对象设计原则的实际运用,是对类的封装性、继承性和多态性以及类的关联关系和组合关系的充分理解。
  • 正确使用设计模式具有以下优点:
    • 可以提高程序员的思维能力、编程能力和设计能力
    • 是程序设计更加标准化、代码编制更加工程化,使软件开发效率大大提高,从而缩短软件的开发周期
    • 使设计的代码可重用性高、可读性强、可靠性高、灵活性好、可维护性

设计模式的基本要素:模式名称、问题、解决方案、效果

面向对象设计模式可分为以下三类:

  • 创建型模式:单例模式、工厂模式、抽象工厂模式、建造者模式、原型模式
    • 目标:将一个系统与其对象的创建、组合、表示分离开来
    • 目的:在哪个对象被创建、谁负责创建对象、怎样创建对象、何时创建对象方面增强灵活性
    • 主要任务:为客户程序创建对象,而不是由客户程序直接初始化对象
    • 好处:大量减少客户程序中对象创建的代码量
  • 结构型模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式
    • 作用:从程序的结构上实现松耦合,从而可以扩大整体的类结构,用来解决更大的问题。
  • 行为型模式:模板方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式、访问者模式
序号 模式 & 描述 包括
1 创建型模式 这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。 工厂模式(Factory Pattern) 抽象工厂模式(Abstract Factory Pattern) 单例模式(Singleton Pattern) 建造者模式(Builder Pattern) 原型模式(Prototype Pattern)
2 结构型模式 这些设计模式关注类和对象的组合。继承的概念被用来组合接口和定义组合对象获得新功能的方式。 适配器模式(Adapter Pattern) 桥接模式(Bridge Pattern) 过滤器模式(Filter、Criteria Pattern) 组合模式(Composite Pattern) 装饰器模式(Decorator Pattern) 外观模式(Facade Pattern) 享元模式(Flyweight Pattern) 代理模式(Proxy Pattern)
3 行为型模式 这些设计模式特别关注对象之间的通信。 责任链模式(Chain of Responsibility Pattern) 命令模式(Command Pattern) 解释器模式(Interpreter Pattern) 迭代器模式(Iterator Pattern) 中介者模式(Mediator Pattern) 备忘录模式(Memento Pattern) 观察者模式(Observer Pattern) 状态模式(State Pattern) 空对象模式(Null Object Pattern) 策略模式(Strategy Pattern) 模板模式(Template Pattern) 访问者模式(Visitor Pattern)
4 J2EE 模式 这些设计模式特别关注表示层。这些模式是由 Sun Java Center 鉴定的。 MVC 模式(MVC Pattern) 业务代表模式(Business Delegate Pattern) 组合实体模式(Composite Entity Pattern) 数据访问对象模式(Data Access Object Pattern) 前端控制器模式(Front Controller Pattern) 拦截过滤器模式(Intercepting Filter Pattern) 服务定位器模式(Service Locator Pattern) 传输对象模式(Transfer Object Pattern)

image-20200625165847438

步骤一

创建一个Singleton类:

步骤二

从singleton类获取唯一的对象:

结果

单例模式在多线程环境中需要特别注意的问题

假设此时需要实例化的类名称为,在进行实例化的过程中,期望的执行顺序如下:

  1. 分配内存空间
  2. 执行构造方法,初始化对象
  3. 把这个对象指向这个空间

? 期望的执行顺序是123,但也有可能是132,如果是在单线程环境下,单例模式是没有问题的,因为至始至终都是一个线程,返回的是就只有一个实例化的对象。

? 但如果在多线程环境下,需要注意:假如有线程A和线程B,在线程A没有执行完时,线程B进来了,读取到不为空,即判断已经实例化完成,线程B直接返回这个,但线程A还没有执行完成,此时还没有完成构造,此时就会出错,所以需要注意多线程环境下的处理,可以对对象加关键字,声明此变量为原子变量,也可以在方法加锁等…

假如有线程A和线程B,在线程A没有执行完时,线程B进来了,读取到lazyMan不为空,即判断lazyMan已经实例化完成,线程B直接返回这个lazyMan,但线程A还没有执行完成,此时lazyMan还没有完成构造,此时就会出错,所以需要注意多线程环境下的处理,可以对lazyMan对象加volatile关键字,声明此变量为原子变量,也可以在方法加synchronized锁等…

单例模式各种实现方式对比

实现方式 是否Lazy初始化 是否多线程安全 实现难度
懒汉式
懒汉式+
饿汉式
双检锁 较复杂
静态内部类 一般
枚举
volatile关键字 较复杂
较复杂
较复杂

单例模式的实现方式如下:

1、懒汉式单例,线程不安全

是否Lazy初始化 是否多线程安全 实现难度

描述:这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁synchronized,所以严格意义上它并不算单例模式。

这种lazy loading(延迟加载)很明显,不要求线程安全,在多线程不能正常工作。

代码示例

往下的几种实现方式都支持多线程,但是在性能上有所差异

2、懒汉式单例,线程安全

是否Lazy初始化 是否多线程安全 实现难度

描述:这种方式具备很好的lazy loading(延迟加载),能够在多线程中很好的工作,但是,效率很低,99%情况下不需要同步。

优点:第一次调用才初始化,避免内存浪费

缺点:必须加锁synchronized才能保证单例,但加锁会影响效率。

()的性能对应用程序不是很关键(该方法使用不太频繁)。

代码示例

3、饿汉式单例

是否Lazy初始化 是否多线程安全 实现难度

描述:这种方式比较常用,但容易产生垃圾对象。

优点:没有加锁,执行效率会提高。

缺点:类加载时就初始化,浪费内存。

它基于(类加载器)机制避免了多线程的同步问题,不过,singleton在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用方法,但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化singleton显然没有达到lazy loading(延迟加载)的效果。

代码示例

4、双检锁/双重校验锁(,即double-checked locking)

版本:1.5起

是否Lazy初始化 是否多线程安全 实现难度
较复杂

描述:这种方式采用双锁机制,安全且在多线程情况下保持高性能。

()的性能对应用程序很关键。

优点:线程安全;延迟加载;效率较高。

缺点:编译器的指令重排导致单例出现漏洞。

代码示例

? 在该方法中对instance进行了两次判空:第一层判断为了避免不必要的同步,第二层判断则是为了在null的情况下创建实例。对第六种单例的漏洞进行了弥补,但是还是有点小问题的,问题就在instance = new Singleton();语句上。

这语句在这里看起来是一句代码,但实际上它并不是一个原子操作,这句代码最终会被编译成多条汇编指令,它大致做了3件事情:

  1. 给Singleton的实例分配内存
  2. 调用Singleton()的 构造函数,初始化成员字段
  3. 将instance对象指向分配的内存空间(此时instance就不是null了)

? 但是,由于Java编译器运行处理器乱序执行,以及之前Java内存模型中Cache、寄存器到主内存会写顺序的规定,上面的第二和第三的顺序是无法保证的。也就是说,执行顺序可能是1-2-3也可能是1-3-2.如果是后者,并且在3执行完毕、2未执行之前,被切换到线程B上,这时候instance因为已经在线程A内执行3了,instance已经是非null,所有线程B直接取走instance,再使用时就会出错,这就是失效问题,而且这种难以跟踪难以重现的问题很可能会隐藏很久。

5、登记式/静态内部类

是否Lazy初

来源:T Head

声明:本站部分文章及图片转载于互联网,内容版权归原作者所有,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2022年2月18日
下一篇 2022年2月18日

相关推荐