创建者模式 · 设计模式
单例模式
单例模式确保一个类在系统中只有一个实例,并且提供一个全局的访问点
换句话说,全系统共用同一个对象,比如:
- 配置中心(Config)
- 线程池(ThreadPoolExecutor)
- 数据库连接池
- Spring 的 Bean (默认就是单例作用域)
实现方式
饿汉式
在类内部创建好该类的对象, 对外提供获取实例的方法
- 私有化创建方法, 外界无法自己创建实例
- 由于类加载时就创建好对象(会造成一定浪费)
- 赋值给成员变量 / 静态代码块内赋值
代码块收起展开
public class Singleton {
private static final Singleton INSTANCE
= new Singleton(); //静态变量, 类加载时创建
private Singleton() {}
public static Singleton getInstance() {
return INSTANCE;
}
}best practice:
代码块收起展开
public enum Singleton {
INSTANCE; // 唯一实例
public void doSomething() {
System.out.println("做点事情");
}
}
psvm(){
Singleton.INSTANCE.doSomething();
}饱汉式 (懒加载)
用类名.getInstance()时才创建
bug:
- 多线程会出现并发创建多个实例的问题
1. volatile + double check + synchronized
代码块收起展开
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. 静态内部类
代码块收起展开
public class Singleton {
private Singleton() {}
private static class Holder {
private static final
Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return Holder.INSTANCE;
}
}静态内部类单例利用了JVM的类加载机制:
- 外部类
Singleton被加载时, 并不会立即加载内部类Holder - 内部类
Holder只有在第一次调用getInstance()时才被加载, 静态变量INSTANCE才初始化
KEY
-
静态内部类属于外部类本身, 不依赖外部类实例 -> 同一个外部类的多个实例共享同一个静态内部类
-
静态内部类的类加载和静态成员初始化是延迟的
- 只有第一次访问静态内部类时,
JVM才会加载类并初始化静态字段(懒加载) - JVM 保证类加载和静态字段初始化的线程安全, 因此单例实例只会创建一次, 无需加锁
- 只有第一次访问静态内部类时,
单例被破坏
反序列化
代码块收起展开
//序列化
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, 反射也能绕过访问控制
解决方式:
- 加一个
readObject()方法用以返回内部类定义的实例, 这样JVM会先看是否有readObject()方法 - 用enum
JDK
代码块收起展开
public class RuntimeDemo{
psvm{
//获取Runtime的实例
Runtime runtime = Runtime.getRuntime();
//
Process process = runtime.exec("ipconfig");
InputStream is = process.getInputStream();
......
}
}工厂模式
简单工厂模式
并非创建者模式:它只是把 new 集中在一个地方,没有“抽象创建规则”
代码块收起展开
public class fac{
Product;
public static Product fac(args){
new product(args);
return product;
}
}工厂方法模式
只考虑生产同种类产品
主要角色
- 抽线工厂 <------------> 具体工厂
- 抽象产品 <------------> 具体产品
原先, 我们想要 添加产品种类需要变动 factory 源代码,
现在, 我们只需依赖接口 factory 创建新的 sonFactory 实现 factory 接口
再利用多态实现不修改源代码即可新增的目的 , 对拓展开放, 对修改关闭
缺点: 类爆炸
抽象工厂模式
多级别产品的生成, eg: 苹果工厂
同一个产品族 生产同一个品牌的产品 比具体工厂模式多的就是可以生产多个产品了
模式拓展
- 写好
bean.properties文件, xxx=全类名 - 在simplefactory利用反射获得
代码块收起展开
//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
- 伸缩构造器: new User(a, b, c, d, e, f…) 参数爆炸, 顺序易错
- javaBean: 无参构造+一堆setXxxx(): 对象可变 + 无法对字段原子校验 但 JavaBean 是:对象是“逐步构建”的 , 先 new 一个“空壳” , 再一行一行 set
- 原型: 适合复制近似对象
主要角色
- 抽象建造者-----具体建造者
- 产品类: 要创建的复制对象
- 指挥者类: 调用具体建造者来创建复杂对象的各个部分, 只负责对象的 完整创建 or 按某种顺序创建
简化
原先要把抽象建造者和指挥类进行结合, 但是太繁杂了, 没必要为了顺序之类专门写一个智慧类, 所以我们把该功能整合进抽象建造者
代码块收起展开
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();
}
}缺点:
- 适合有较多的共同点+组成部分相似的类, 而非差异大的类
链式编程
代码块收起展开
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() 方法返回手机对象
代码块收起展开
new Phone.Builder()
.cpu("xxx")
.screen("xxx")
.build();原型模式
按照原型直接copy一个新的
角色
- 抽象原型------具体原型
- 访问类: 调用具体原型中的clone()方法
本质: 浅拷贝
浅拷贝: 新的地址+旧的引用 也即: 克隆对象本身是新的, 但内部引用的对象没变
好处
- 数据量大的情况下直接克隆, 比new 对象性能更好
- 对象创建非常复杂, 可以使用原型模式更快创建
- 性能和安全要求比较高
使用方法
- 实现Cloneable接口并 重写clone()
深克隆
如果我们用浅拷贝, 后续克隆的对象更改字段也会影响原型
深克隆的集中方法
- 不仅复制对象本身, 还复制其引用的对象
代码块收起展开
User copy = (User) super.clone();
copy.addr = new Address(this.addr.getCity()); // 手动 new
copy.name =(Name) this.name;-
通过序列化/反序列化实现 (类也要实现序列化接口) (JVM反序列化会创建一个新对象—>也是单例模式被破坏的原因)
-
通过
JSON序列化
代码块收起展开
String json = JSONUtil.toJsonStr(obj);
ClassRoom copy = JSONUtil.toBean(json,ClassRoom.class); - 封装序列化/反序列化去深拷贝
-
Apache Commons Lang: SerializationUtils.clone(object)
-
Hutool: ObjectUtil.cloneByStream(object)
-
Spring: 某些BeanUtils工具类也能部分支持深拷贝(不过是属性级的)
总结
横向对比
| 模式 | 核心目的 | 客户端关心 | 对象创建方式 | 适用场景 | 优势 | 技巧 / Key |
|---|---|---|---|---|---|---|
| 单例 Singleton | 保证唯一实例 | 使用实例 | 控制构造器 + 静态访问 | 配置、日志、连接池 | 节约资源、全局访问 | 静态内部类 / 枚举安全、防反射 / 序列化 |
| 原型 Prototype | 快速复制对象 | 复制对象 | clone / 序列化 / JSON | 初始化成本高、模板对象 | 快速创建、可微调 | 浅拷贝 vs 深拷贝,注意引用复制 |
| 工厂 Factory | 解耦创建逻辑 | 使用对象 | 提供接口或静态方法创建 | 对象族多、扩展性高 | 创建封装、扩展方便 | 配置 + 反射解耦,工厂方法 / 抽象工厂 |
| 建造者 Builder | 分步构建复杂对象 | 成品对象 | Builder 封装构建步骤,链式调用 | 可选字段多、顺序敏感、不可变 | 可读、可维护、校验集中 | 必填字段 Builder 构造器,build() 校验,Director 可选 |
组合使用
- Builder + Prototype : 模板对象微调生成新实例
- Factory + Singleton : 工厂本身是单例, 提供全局对象创建服务
- Builder + Factory : Factory提供不同Builder实例
特征识别
-
看目的
- Singleton —> 控制数量
- Prototype —> 快速复制
- Factory —> 解耦类型选择
- Builder —> 分布构建/有序构建
-
看创建方式
- Singleton—>只有一个实例
- Prototype—>clone/复制
- Factory—>根据条件选择不同实现
- Builder—>链式/分布/可选字段
-
使用场景
- 单例: 配置、日志、连接池
- 原型: 模板对象、深克隆
- 工厂: 对象族、可拓展系统
- Builder: 参数多、顺序、不可变对象