创建者模式 · 设计模式

单例模式

单例模式确保一个类在系统中只有一个实例,并且提供一个全局的访问点

换句话说,全系统共用同一个对象,比如:

  • 配置中心(Config)
  • 线程池(ThreadPoolExecutor)
  • 数据库连接池
  • Spring 的 Bean (默认就是单例作用域)

实现方式

饿汉式

在类内部创建好该类的对象, 对外提供获取实例的方法

  1. 私有化创建方法, 外界无法自己创建实例
  2. 由于类加载时就创建好对象(会造成一定浪费)
  3. 赋值给成员变量 / 静态代码块内赋值
代码块JAVA · 10 行收起展开
public class Singleton {
    private static final Singleton INSTANCE 
    = new Singleton(); //静态变量, 类加载时创建

    private Singleton() {} 

    public static Singleton getInstance() {
        return INSTANCE;
    }
}

best practice:

代码块JAVA · 11 行收起展开
public enum Singleton {
	INSTANCE; // 唯一实例
	
	public void doSomething() {
		System.out.println("做点事情");
	}
}

psvm(){
	Singleton.INSTANCE.doSomething();
}

饱汉式 (懒加载)

用类名.getInstance()时才创建

bug:

  • 多线程会出现并发创建多个实例的问题

1. volatile + double check + synchronized

代码块JAVA · 17 行收起展开
public class Singleton {
    private static volatile Singleton instance;

    private Singleton() {}//注意必须要private构造方法

    public static Singleton getInstance() {
	    //double check
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

2. 静态内部类

代码块JAVA · 13 行收起展开
public class Singleton {

    private Singleton() {}

    private static class Holder {
		private static final 
		Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return Holder.INSTANCE;
    }
}

静态内部类单例利用了JVM的类加载机制:

  1. 外部类 Singleton 被加载时, 并不会立即加载内部类 Holder
  2. 内部类 Holder 只有在第一次调用 getInstance()时才被加载, 静态变量INSTANCE才初始化

KEY

  1. 静态内部类属于外部类本身, 不依赖外部类实例 -> 同一个外部类的多个实例共享同一个静态内部类

  2. 静态内部类的类加载和静态成员初始化是延迟

    • 只有第一次访问静态内部类时, JVM 才会加载类并初始化静态字段(懒加载)
    • JVM 保证类加载和静态字段初始化的线程安全, 因此单例实例只会创建一次, 无需加锁

单例被破坏

反序列化

代码块JAVA · 11 行收起展开
//序列化
ObjectOutputStream out = 
new ObjectOutputStream(new FileOutputStream("singleton.obj"));
out.writeObject(instance1);

//反序列化
ObjectInputStream in = 
new ObjectInputStream(new FileInputStream("singleton.obj"));
Singleton instance2 = (Singleton) in.readObject();

(instance1==instance2).sout;   //false

原因:

  • 反序列化时, JVM会创建一个全新的对象, 而不是调用getInstance()

反射

  • 反射允许你访问类的私有构造器
  • 即使单例类把构造器 private, 反射也能绕过访问控制

解决方式:

  1. 加一个readObject()方法用以返回内部类定义的实例, 这样JVM会先看是否有readObject()方法
  2. 用enum

JDK

代码块JAVA · 12 行收起展开
public class RuntimeDemo{
	psvm{
		//获取Runtime的实例
		Runtime runtime = Runtime.getRuntime();
		
		//
		Process process = runtime.exec("ipconfig");
		
		InputStream is = process.getInputStream();
		......
	}
}

工厂模式

简单工厂模式

并非创建者模式:它只是把 new 集中在一个地方,没有“抽象创建规则”

代码块JAVA · 7 行收起展开
public class fac{
	Product;
	public static Product fac(args){
		new product(args);
		return product;
	}
}

工厂方法模式

只考虑生产同种类产品

主要角色

  1. 抽线工厂 <------------> 具体工厂
  2. 抽象产品 <------------> 具体产品

原先, 我们想要 添加产品种类需要变动 factory 源代码, 现在, 我们只需依赖接口 factory 创建新的 sonFactory 实现 factory 接口 再利用多态实现不修改源代码即可新增的目的 , 对拓展开放, 对修改关闭

缺点: 类爆炸

抽象工厂模式

多级别产品的生成, eg: 苹果工厂

同一个产品族 生产同一个品牌的产品 比具体工厂模式多的就是可以生产多个产品了

模式拓展

  1. 写好 bean.properties 文件, xxx=全类名
  2. 在simplefactory利用反射获得
代码块JAVA · 27 行收起展开
//1.定义容器对象存储对象
private static Map<String, Coffee> map = new HashMap<>();

//2.加载配置文件, 只需要加载一次
static {
	//创建Properties对象
	Properties p = new Properties();
	//调用p对象中的load方法进行配置文件的加载
	InputStream is = CoffeeFactory.class.getClassLoader().getResourceAsStream("bean.properties");
	try{
		p.load(is);
		//从p集合中获取全类名并通过反射创建对象
		Set<Object> keys = p.keySet();
		for(Object key : keys){
			String className = p.getProperties( (String) key ); // com.example.Xxxx
			
			//通过反射技术创建对象
			Class clazz = Class.forName(className);
			Coffee coffee = (Coffee) clazz.newInstance();
			
			//将名称和对象存储到容器中
			map.put( (String)key,  coffee);
		}
	} catch (Exception e){
		e.printStackTrace();
	}
}

由于是properties文件, 所以我们创建一个properties对象接收

这里的properties是java提供的一中kv对集合类型, 专门用来读取properties文件, 其中load的作用是拆解成kv对存入props

建造者模式

why

  1. 伸缩构造器: new User(a, b, c, d, e, f…) 参数爆炸, 顺序易错
  2. javaBean: 无参构造+一堆setXxxx(): 对象可变 + 无法对字段原子校验 但 JavaBean 是:对象是“逐步构建”的 , 先 new 一个“空壳” , 再一行一行 set
  3. 原型: 适合复制近似对象

主要角色

  • 抽象建造者-----具体建造者
  • 产品类: 要创建的复制对象
  • 指挥者类: 调用具体建造者来创建复杂对象的各个部分, 只负责对象的 完整创建 or 按某种顺序创建

简化

原先要把抽象建造者和指挥类进行结合, 但是太繁杂了, 没必要为了顺序之类专门写一个智慧类, 所以我们把该功能整合进抽象建造者

代码块JAVA · 13 行收起展开
public abstract class Builder{
	Bike mbike = new Bike();
	
	public abstract void buildFrame();
	public abstract void buildSeat();
	public abstract Bike createBike();
	
	public Bike construct(){
		this.buildFrame();
		this.buildSeat();
		return this.createBike();
	}
}

缺点:

  • 适合有较多的共同点+组成部分相似的类, 而非差异大的类

链式编程

代码块JAVA · 12 行收起展开
public static class Builder{
	String name;
	Integer age;
	String emal;
	Builder name(name){ this.name = name;  return this; }
	Builder age(age){ this.age = age;  return this; }
	Builder email(email){ this.email = email;  return this; }
	
	public User build(){
		return new User(this);
	}
}

class 内置Builder内部类, 然后 Builder 中有 build() 方法返回手机对象

代码块JAVA · 4 行收起展开
new Phone.Builder()
		.cpu("xxx")
		.screen("xxx")
		.build();

原型模式

按照原型直接copy一个新的

角色

  • 抽象原型------具体原型
  • 访问类: 调用具体原型中的clone()方法

本质: 浅拷贝

浅拷贝: 新的地址+旧的引用 也即: 克隆对象本身是新的, 但内部引用的对象没变

好处

  1. 数据量大的情况下直接克隆, 比new 对象性能更好
  2. 对象创建非常复杂, 可以使用原型模式更快创建
  3. 性能和安全要求比较高

使用方法

  • 实现Cloneable接口重写clone()

深克隆

如果我们用浅拷贝, 后续克隆的对象更改字段也会影响原型

深克隆的集中方法

  1. 不仅复制对象本身, 还复制其引用的对象
代码块JAVA · 3 行收起展开
User copy = (User) super.clone();  
copy.addr = new Address(this.addr.getCity()); // 手动 new
copy.name =(Name) this.name;
  1. 通过序列化/反序列化实现 (类也要实现序列化接口) (JVM反序列化会创建一个新对象—>也是单例模式被破坏的原因)

  2. 通过 JSON 序列化

代码块JAVA · 2 行收起展开
String  json = JSONUtil.toJsonStr(obj);
ClassRoom  copy = JSONUtil.toBean(json,ClassRoom.class); 
  1. 封装序列化/反序列化去深拷贝
  • Apache Commons Lang: SerializationUtils.clone(object)

  • Hutool: ObjectUtil.cloneByStream(object)

  • Spring: 某些BeanUtils工具类也能部分支持深拷贝(不过是属性级的)

总结

横向对比

模式核心目的客户端关心对象创建方式适用场景优势技巧 / Key
单例 Singleton保证唯一实例使用实例控制构造器 + 静态访问配置、日志、连接池节约资源、全局访问静态内部类 / 枚举安全、防反射 / 序列化
原型 Prototype快速复制对象复制对象clone / 序列化 / JSON初始化成本高、模板对象快速创建、可微调浅拷贝 vs 深拷贝,注意引用复制
工厂 Factory解耦创建逻辑使用对象提供接口或静态方法创建对象族多、扩展性高创建封装、扩展方便配置 + 反射解耦,工厂方法 / 抽象工厂
建造者 Builder分步构建复杂对象成品对象Builder 封装构建步骤,链式调用可选字段多、顺序敏感、不可变可读、可维护、校验集中必填字段 Builder 构造器,build() 校验,Director 可选

组合使用

  1. Builder + Prototype : 模板对象微调生成新实例
  2. Factory + Singleton : 工厂本身是单例, 提供全局对象创建服务
  3. Builder + Factory : Factory提供不同Builder实例

特征识别

  1. 看目的

    • Singleton —> 控制数量
    • Prototype —> 快速复制
    • Factory —> 解耦类型选择
    • Builder —> 分布构建/有序构建
  2. 看创建方式

    • Singleton—>只有一个实例
    • Prototype—>clone/复制
    • Factory—>根据条件选择不同实现
    • Builder—>链式/分布/可选字段
  3. 使用场景

    • 单例: 配置、日志、连接池
    • 原型: 模板对象、深克隆
    • 工厂: 对象族、可拓展系统
    • Builder: 参数多、顺序、不可变对象