设计模式学习笔记
- 设计模式
-
- 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) |
步骤一
创建一个Singleton类:
步骤二
从singleton类获取唯一的对象:
结果
单例模式在多线程环境中需要特别注意的问题
假设此时需要实例化的类名称为,在进行实例化的过程中,期望的执行顺序如下:
- 分配内存空间
- 执行构造方法,初始化对象
- 把这个对象指向这个空间
? 期望的执行顺序是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件事情:
- 给Singleton的实例分配内存
- 调用Singleton()的 构造函数,初始化成员字段
- 将instance对象指向分配的内存空间(此时instance就不是null了)
? 但是,由于Java编译器运行处理器乱序执行,以及之前Java内存模型中Cache、寄存器到主内存会写顺序的规定,上面的第二和第三的顺序是无法保证的。也就是说,执行顺序可能是1-2-3也可能是1-3-2.如果是后者,并且在3执行完毕、2未执行之前,被切换到线程B上,这时候instance因为已经在线程A内执行3了,instance已经是非null,所有线程B直接取走instance,再使用时就会出错,这就是失效问题,而且这种难以跟踪难以重现的问题很可能会隐藏很久。
5、登记式/静态内部类
是否Lazy初
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!
App用户协议&隐私政策
上一篇
2022年2月18日
2022-2028全球指纹成像软件行业调研及趋势分析 告
下一篇
2022年2月18日
|
---|