设计模式分类
设计模式根据应用场景的不同分为三类:创建型、结构型、行为型。
创建型解决对象创建问题,将对象的创建与对象的使用解耦;
结构型解决对象与对象之间的关系问题,将不同功能的代码解耦;
行为型解决对象之间的交互问题,将对象与对象之间的行为解耦。
在我们应用设计模式的时候,结合要解决的问题和设计模式分类,可以缩小设计模式选择范围。当然,我们学习设计模式不是为了生搬硬套,非要将设计模式套用到代码设计上。但是熟悉每种设计模式为什么这样设计、能够解决什么问题,知道灵活可扩展的代码长什么样,对我们设计和编码能起到很好的指引作用。首先建立编写可扩展、灵活易用、可测试代码的意识,再有意识的锻炼自己灵活应用设计原则、设计模式,我们也可以写出优秀的代码,彻底掌握设计模式。
创建型设计模式
创建型设计模式包括单例模式、工厂方法模式、抽象工厂模式、建造者模式、原型模式。
单例模式
单例模式是大家最熟悉的设计模式之一了,用来创建全局唯一的对象。
单例模式虽然看起来很简单,里面需要考虑的东西还是挺多的,需要考虑对象创建时的线程安全问题、是否支持延迟加载、创建时的性能如何。
定义
确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
实现方式
大致有以下5种创建方式,需要根据场景选择合适的创建方式:
- 饿汉式:在类加载的时候就创建并初始化好,不支持延迟加载,适合比较耗时的初始化操作;
- 懒汉式:在使用时延迟加载创建,不适合高并发、初始化比较耗时的场景;
- 双重检查:既支持延迟加载、又支持高并发的单例实现方式,双重检查需要注意指令重排序问题,加volatile关键字解决。
- 静态内部类:既保证了线程安全,又能做到延迟加载。
- 枚举:《Effective Java》中推荐使用枚举创建单例,实现简单,利用Java 枚举类型本身的特性,保证了实例创建的线程安全性和实例的唯一性
应用场景
当数据在系统中只应该有一份时,可以设计为单例类,比如配置文件等,我们用Spring Bean时,默认也是用的单例模式。单例模式还可以用来解决资源访问冲突问题。
补充
- 前面讲的单例类中的对象唯一性的作用范围是进程内的,可以通过HashMap、ThreadLocal实现线程类唯一单例,可以通过共享缓存实现集群唯一单例。
- 单例模式也被说成是反模式,对代码的可扩展性不好、可测试性不友好、不支持有参数的构造函数、会隐藏类之间的依赖关系。推荐使用静态工厂方法代替。
工厂模式
工厂模式用来封装复杂的对象创建过程,将对象的创建与使用分离。工厂模式的应用场景很好理解,不会产生混淆。
工厂模式还可以细分为简单工厂、工厂方法、抽象工厂。
简单工厂
简单工厂不在23种设计模式中,但在项目开发中经常用到,对于只需要new来创建的对象,没有复杂的创建逻辑,适合使用简单工厂模式。可以通过if else判断类型,创建不同的对象;也可以利用HashMap存储创建好的对象。
工厂方法模式
定义
一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
抽象工厂
定义
为创建一组相关或相互依赖的对象提供一个接口,而且无须指定它们的具体类。
工厂方法与抽象工厂的区别
- 工厂方法使用继承,把对象创建委托给子类,子类实现工厂方法类创建对象;
- 抽象工厂使用对象组合,对象的创建被实现在工厂接口所暴露出来的方法中。
何时使用
- 代码中存在if-else分支判断,动态的根据不同类型创建不同的对象;
- 对象创建需要组装多个其他类对象或者需要复杂的初始化过程。
建造者模式
建造者模式使用比较简单,比构造函数灵活很多,多了一些控制和校验能力。
定义
为创建一组相关或相互依赖的对象提供一个接口,而且无须指定它们的具体类。
应用场景
- 构造函数过长,并且需要校验时;
- 类的属性之间有一定的依赖关系或者约束条件;
原型模式
原型模式通过对象复制的方式创建对象,分为浅拷贝和深拷贝,原型模式是在内存二进制流的拷贝,要比直接new一个对象性能好很多,需要注意的是通过原型模式创建对象,它的构造函数不会被执行。
定义
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
应用场景
- 资源优化场景;
- 一个对象多个修改者的场景;
- 通过new产生一个对象需要非常繁琐的数据准备或访问权限;
- 在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过clone的方法创建一个对象,然后由工厂方法提供给调用者。
以上总结了几种创建型设计模式的定义和使用场景,创建型设计模式解决的问题很明确,很容易掌握,在后面两篇文章中总结结构型和行为型。