四、创建型模式
1、单例模式
Singleton Pattern
1)模式介绍
所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)
比如 Hibernate的 SessionFactory,它充当数据存储源的代理,并负责创建 Session对象。SessionFactory并不是轻量级的,一般情况下,一个项目通常只需要一个 SessionFactory就够,这时就会使用到单例模式
2)八种方式
1、饿汉式(静态常量)
2、饿汉式(静态代码块)
优点:线程安全。在类装载的时候就完成实例化。避免了线程同步问题
缺点:没有达到 Lazy Loading 的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费
饿汉式单例模式可能造成内存浪费
3、懒汉式(线程不安全)
class Singleton {
// 静态变量
private static Singleton instance;
/**
* 私有构造器
*/
private Singleton() {}
/**
* 懒汉式
* 提供一个静态的公有方法,当使用到该方法时,才去创建 instance
*/
public static Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
起到了 Lazy Loading 的效果,但是只能在单线程下使用。
如果在多线程下,一个线程进入了 if (singleton == null) 判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式
在实际开发中,不要使用这种方式
4、懒汉式(线程安全,同步方法)
/**
* 懒汉式
* 提供一个静态的公有方法,当使用到该方法时,才去创建 instance
*/
public static synchronized Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
- 解决了线程安全问题
- 效率太低了,每个线程在想获得类的实例时候,执行 getInstance() 方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接 return 就行了。方法进行同步效率太低
- 在实际开发中,不推荐使用这种方式
5、懒汉式(线程不安全,同步代码块)
/**
* 懒汉式
* 提供一个静态的代码块
*/
public static Singleton getInstance() {
if(instance == null) {
synchronized (Singleton.class){
instance = new Singleton();
}
}
return instance;
}
if(instance == null) 线程不安全。不推荐
6、DCL 双重检查锁(推荐)
/**
* @author ajun
* Date 2021/7/8
* @version 1.0
* 单例模式:懒汉式(双重检测锁)
*/
public class DCLLazyDemo {
//先定义类变量,不创建
//加上volatile,禁止指令重排
private volatile static DCLLazyDemo dclLazyDemo = null;
/**
* 私有构造器
*/
private DCLLazyDemo(){}
/**
* 获取类对象的对外接口
*/
public static DCLLazyDemo getInstance(){
// 使用的时候,再用new创建
// 双重检测锁 DCL
if(dclLazyDemo == null){
synchronized (DCLLazyDemo.class){
if(dclLazyDemo == null){
dclLazyDemo = new DCLLazyDemo();
/**
* 由于对象创建不是原子性操作
* 1. 分配内存空间
* 2. 使用构造器创建对象
* 3. 将对象指向内存空间
*/
/**
* 可能会发生指令重排
* 123
*
* 132
*
* 这是就需使用volatile 关键字来防止指令重排
*/
}
}
}
return dclLazyDemo;
}
// 多线程下不加volatile,new时,虽然有双重检测,但仍有可能会发生指令重排,是不安全的。所以要加上 volatile
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
new Thread(() -> {
System.out.println(DCLLazyDemo.getInstance());
}).start();
}
}
}
DCL:Double Check Lock
volatile:保证可见性;不保证原子性;禁止指令重排
线程安全;延迟加载;效率较高;推荐使用
7、静态内部类(推荐)
package singleMode;
/**
* @author ajun
* Date 2021/7/8
* @version 1.0
* 单例模式:内部静态类
*/
public class InnerDemo {
// 私有构造器
private InnerDemo(){}
// 静态内部类
private static class InnerClass{
private static final InnerDemo INNER_DEMO = new InnerDemo();
}
// 获取类对象的对外接口
public static InnerDemo getInstance(){
return InnerClass.INNER_DEMO;
}
}
采用了类装载的机制来保证初始化实例时只有一个线程
静态内部类方式在 外部类 被装载时并 不会立即实例化,而是在需要实例化时,调用 getInstance 方法,才会装载内部类,从而通过内部类完成外部类的实例化
类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的
优点:线程安全,利用静态内部类特点实现延迟加载,效率高,代码结构清晰
结论:推荐使用
8、枚举(推荐)
package singleton;
/**
* @author ajun
* Date 2021/7/23
* @version 1.0
* 单例模式:枚举
*/
public class EnumDemo {
public static void main(String[] args) {
Singleton a = Singleton.A;
Singleton a1 = Singleton.A;
Singleton b = Singleton.B;
Singleton c = Singleton.C;
System.out.println(a.hashCode());
System.out.println(a1.hashCode());
System.out.println(b.hashCode());
System.out.println(c.hashCode());
System.out.println(b.name());
System.out.println(b.ordinal());
System.out.println(c.ordinal());
a.say();
b.say();
c.say();
}
}
enum Singleton{
// 枚举值默认为从0开始的有序数值
A, B, C;
public void say(){
System.out.println("OK!");
}
}
- 借助 JDK1.5 中添加的枚举来实现单例模式。线程安全,防止反序列化(反射)重新创建新的对象
- 这种方式是 Effective Java作者 Josh Bloch提倡的方式
- 结论:推荐使用
3)JDK案例
java.lang.Runtime 就是经典的 单例模式(饿汉式)
2、工厂模式
Factory Pattern
- 传统模式
优点:比较好理解,简单易操作
缺点:违反了设计模式的 ocp 原则,即对扩展开放,对修改关闭。扩展性差。
1)简单工厂模式
- 简单工厂模式是属于创建型模式,是工厂模式的一种。简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族中最简单实用的模式
- 定义了一个创建对象的类,由这个类来封装实例化对象的行为(代码)
- 在软件开发中,当大量的创建对象时,就会使用到工厂模式
2)工厂方法模式
定义了一个创建对象的抽象方法,由子类决定要实例化的类。工厂方法模式将对象的实例
化推迟到子类
3)抽象工厂模式
- 抽象工厂模式:定义了一个 interface 用于创建相关或有依赖关系的对象簇,而无需指明具体的类
- 抽象工厂模式可以将 简单工厂模式 和 工厂方法模式 进行整合
- 从设计层面看,抽象工厂模式就是对简单工厂模式的改进(或者称为进一步的抽象)
- 将工厂抽象成两层,AbsFactory(抽象工厂) 和具体实现的工厂子类。可以根据创建对象类型使用对应的工厂子类。这样将单个的简单工厂类变成了工厂簇,更利于代码的维护和扩展
4)工厂模式小结
- 工厂模式的意义
将实例化对象的代码提取出来,放到一个类中统一管理和维护,达到和主项目的依赖关系的解耦。从而提高项目的扩展和维护性
- 三种工厂模式
简单工厂模式、工厂方法模式、抽象工厂模式
工厂方法模式 是 简单工厂模式 的一种升级
- 设计模式的依赖倒置原则
面向接口编程
- 创建对象实例时,不要直接 new 类,而是把这个 new 类的动作放在一个工厂的方法中,并返回。有的书上说, 变量不要直接持有具体类的引用
- 不要让类继承具体类,而是继承抽象类或者是实现 interface(接口)
- 不要覆盖基类中已经实现的方法
3、原型模式
Prototype Pattern
1)基本介绍
- 原型模式(Prototype模式)是指:用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象
- 原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节
- 工作原理是:通过将一个原型对象传给那个要发起创建的对象,这个要发起创建的对象通过请求原型对象,拷贝它们自己来实施创建,即对象.clone()
- 被拷贝的类需要实现 Cloneable 接口;如果要序列化,就需要实现 Serializable 接口
- 形象的理解:孙大圣拔出猴毛,变出其它孙大圣
2)原理类图
UML 类图
- Prototype :原型类,声明一个克隆自己的接口
- ConcretePrototype:具体的原型类,实现一个克隆自己的操作
- Client:让一个原型对象克隆自己,从而创建一个新的对象(属性一样)
3)浅拷贝(默认)
-
对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象
-
对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值
-
浅拷贝是使用默认的 clone() 方法来实现
sheep = (Sheep) super.clone();
4)深拷贝
- 复制对象的所有基本数据类型的成员变量值
- 为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象。也就是说,对象进行深拷贝要对整个对象(包括对象的引用类型)进行拷贝
- 深拷贝实现方式1:重写 clone() 方法来实现深拷贝
// 深拷贝-方式1 使用重写 clone() 方法
@Override
protected Object clone() throws CloneNotSupportedException {
Object deep = null;
//这里完成对基本数据类型(属性)和String的克隆
deep = super.clone();
//对引用类型的属性,进行单独处理
DeepProtoType deepProtoType = (DeepProtoType)deep;
deepProtoType.deepCloneableTarget = (DeepCloneableTarget)deepCloneableTarget.clone();
return deepProtoType;
}
- 深拷贝实现方式2:通过 对象序列化 实现深拷贝(推荐)
被拷贝对象及对象中的引用类型,都需要实现 Serializable 接口
//深拷贝-方式2 通过对象的序列化实现(推荐)
public Object deepClone() {
//创建流对象
ByteArrayOutputStream bos = null;
ObjectOutputStream oos = null;
ByteArrayInputStream bis = null;
ObjectInputStream ois = null;
try {
//序列化
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
oos.writeObject(this); //当前这个对象以对象流的方式输出
//反序列化
bis = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bis);
DeepProtoType copyObj = (DeepProtoType)ois.readObject();
return copyObj;
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
// 关闭流
try {
bos.close();
oos.close();
bis.close();
ois.close();
} catch (Exception e2) {
System.out.println(e2.getMessage());
}
}
}
5)注意事项和细节
- 创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能够提高效率
- 不用重新初始化对象,而是动态地获得对象运行时的状态
- 如果原始对象发生变化(增加或者减少属性),其它克隆的对象也会发生相应的变化,无需修改代码
- 在实现深克隆的时候可能需要比较复杂的代码
- 缺点:需要为每一个类配备一个克隆方法,这对全新的类来说不是很难,但对已有的类进行改造时,需要修改其源代码,违背了 ocp 原则
4、建造者模式
Builder Pattern
1)基本介绍
- 建造者模式(Builder Pattern)又叫生成器模式,是一种对象构建模式
- 可以将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实现方法可以构造出不同表现(属性)的对象
- 建造者模式是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,
用户不需要知道内部的具体构建细节
2)四个角色
- 产品角色(Product):具体的产品对象
- 抽象建造者(Builder):创建 Product 对象的各个部件指定的接口/抽象类
- 具体建造者(ConcreteBuilder):实现接口,构建和装配各个部件
- 指挥者/经理(Director):构建使用 Builder 接口的对象。主要是用于创建复杂的对象
- 隔离了客户与产品(对象)的生产过程;
- 负责控制产品(对象)的生产过程
3)原理类图
4)案例
- 需要建房子:过程为打桩、砌墙、封顶
- 房子有各种各样的,比如普通房,高楼,别墅,各种房子的建造过程虽然一样,但是细节不同
1、房子
产品:Product
package builder;
/**
* @author ajun
* Date 2021/7/24
* @version 1.0
* 产品:房子
* Product(产品角色): 一个具体的产品对象
*/
public class House {
// 地基
private String baise;
// 墙
private String wall;
// 房顶
private String roof;
public String getBaise() {
return baise;
}
public void setBaise(String baise) {
this.baise = baise;
}
public String getWall() {
return wall;
}
public void setWall(String wall) {
this.wall = wall;
}
public String getRoof() {
return roof;
}
public void setRoof(String roof) {
this.roof = roof;
}
}
2、施工队
抽象建造者:Builder
package builder;
/**
* @author ajun
* Date 2021/7/24
* @version 1.0
* 抽象建造者
* Builder(抽象建造者):创建一个 Product 对象的各个部件指定的 接口 / 抽象类
*/
public abstract class HouseBuilder {
// 把产品组合进来
protected House house = new House();
/**
* 抽象方法
* 房子的建造过程
*/
abstract void buildBasic();
abstract void buildWall();
abstract void buildRoof();
/**
* 建造完成
* @return
*/
public House completeHouse(){
return house;
}
}
3、普通房子施工队
具体建造者:ConcreteBuilder
package builder;
/**
* @author ajun
* Date 2021/7/24
* @version 1.0
* 具体建造者:普通房子
* ConcreteBuilder(具体建造者):实现接口,构建和装配各个部件
*/
public class CommonHouseBuilder extends HouseBuilder {
@Override
public void buildBasic() {
System.out.println("普通房子打地基:2米");
}
@Override
public void buildWall() {
System.out.println("普通房子彻墙:5米");
}
@Override
public void buildRoof() {
System.out.println("普通房子建房顶");
}
}
4、高楼施工队
具体建造者:ConcreteBuilder
package builder;
/**
* @author ajun
* Date 2021/7/24
* @version 1.0
* 具体建造者:高楼
* ConcreteBuilder(具体建造者):实现接口,构建和装配各个部件
*/
public class HighHouseBuilder extends HouseBuilder{
@Override
public void buildBasic() {
System.out.println("高楼打地基:20米");
}
@Override
public void buildWall() {
System.out.println("高楼彻墙:50米");
}
@Override
public void buildRoof() {
System.out.println("高楼建房顶");
}
}
5、包工头
指挥者/经理:Director
package builder;
/**
* @author ajun
* Date 2021/7/24
* @version 1.0
* 指挥者(经理)
* Director(指挥者/经理):构建一个使用 Builder 接口的对象
* 主要是用于创建一个复杂的产品(对象)
* 主要作用:一、隔离了客户与产品(对象)的生产过程,二、负责控制产品(对象)的生产过程
*/
public class HouseDirector {
// 把建造者聚合进来
HouseBuilder houseBuilder;
/**
* 构造器注入
* @param houseBuilder
*/
public HouseDirector(HouseBuilder houseBuilder){
this.houseBuilder = houseBuilder;
}
public House constructHouse(){
if(houseBuilder == null){
System.out.println("请先指定建造者");
return null;
}else{
houseBuilder.buildBasic();
houseBuilder.buildWall();
houseBuilder.buildRoof();
return houseBuilder.completeHouse();
}
}
/**
* setter注入
* @param houseBuilder
*/
public void setHouseBuilder(HouseBuilder houseBuilder) {
this.houseBuilder = houseBuilder;
}
}
- 客户
package builder;
/**
* @author ajun
* Date 2021/7/24
* @version 1.0
* 客户:需要产品(房子)
*/
public class Client {
public static void main(String[] args) {
// 需要普通房子
// 普通房子建造者
CommonHouseBuilder chb = new CommonHouseBuilder();
// 指挥者
HouseDirector hd = new HouseDirector(chb);
// 得到房子
hd.constructHouse();
System.out.println("-------------");
// 需要高楼
// 高楼建造者
HighHouseBuilder hhb = new HighHouseBuilder();
// 把高楼建造者赋给指挥者管理
hd.setHouseBuilder(hhb);
// 得到房子
hd.constructHouse();
}
}
// 运行结果
普通房子打地基:2米
普通房子彻墙:5米
普通房子建房顶
-------------
高楼打地基:20米
高楼彻墙:50米
高楼建房顶
5)注意事项和细节
-
客户端(使用程序)不必知道产品内部组成的细节,将 产品本身 与 产品的创建过程 解耦,使得相同的创建过程可以创建不同的产品对象
-
每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具 体建造者,用户使用不同的具体建造者即可得到不同的产品对象
-
可以更加精细地控制产品的创建过程。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰, 也更方便使用程序来控制创建过程
-
增加具体建造者无须修改原有代码,指挥者类针对抽象建造者类编程,系统扩展方便,符合“开闭原则”
-
建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使 用建造者模式,因此其使用范围受到一定的限制
-
如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大,因此在这种情况下,要考虑是否选择建造者模式
-
抽象工厂模式 VS 建造者模式
-
抽象工厂模式
实现对产品家族的创建,一个产品家族是这样的一系列产品:具有不同分类维度的产品组合,采用抽象工厂模式不需要关心构建过程,只关心什么产品由什么工厂生产即可
-
建造者模式
重视建造过程。要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品
-