一个 new 就能创建对象,为什么还要拆出单例、工厂、建造者、原型四种模式?

「设计模式与范式」系列 Day08

写在前面

模块一花了七篇讲设计原则和思想,从这篇开始正式进入 23 种设计模式的具体内容,第一站是创建型模式。创建对象这件事,语言层面一个 new 关键字就能搞定,可现实中围绕它却衍生出单例、工厂、建造者、原型四种模式。它们到底是在解决同一个问题的四个方面,还是四个互不相干的问题?这篇先把全家福拍一张,后面四篇再逐个展开细讲。


一、是什么:四种模式各自解决的创建难题

模式 解决的问题
单例 创建一个全局唯一的对象,保证系统里任何地方拿到的都是同一份
工厂 创建一批不同但相关的对象,由传入的参数决定具体创建哪一种
建造者 创建参数很多、其中一些可选的复杂对象,按需定制化组装
原型 创建成本很高的对象,通过复制已有实例来创建新实例,省下重新构建的开销

四者共同的落脚点都是创建型模式这一个大类:

flowchart TB subgraph 创建型模式 解决创建与使用如何解耦 A[单例 保证全局只有一个实例] B[工厂 按参数创建不同类型对象] C[建造者 应对参数多且有可选项的复杂对象] D[原型 复制已有对象节省创建成本] end

四个模式面对的具体痛点不一样------全局唯一性、多类型选择、多参数组装、创建成本------但都是把"怎么创建对象"这件事从"怎么使用对象"里单独拎出来解决,这也是 Day05 提到过的:创建型模式的本质是解耦对象的创建和使用。


二、为什么:直接 new 到底差在哪

单个 new 语句本身没有任何问题,问题出在业务场景给"创建"这个动作加了额外的约束,而 new 关键字本身满足不了这些约束:

  • 单例要解决的不是"怎么造",而是"只能有一份" :直接 new 谁都能造一个新实例,没有任何机制能拦住"再 new 一次",需要单例模式在类内部把住这个口子。
  • 工厂要解决的是"该造哪一种" :如果调用方自己写 if (type.equals("a")) new A(); else new B(); 去判断该 new 哪个具体实现类,这段判断逻辑会散落在代码库的各个角落,调用方也被迫直接依赖具体实现类,换一个实现就要改所有调用点。
  • 建造者要解决的是"参数太多、有些还可选" :属性一多,构造函数要么退化成一长串参数(顺序全靠背,容易传错),要么退化成一堆重载构造函数排列组合;直接先 new 出对象再一个个 set,对象在设置完之前又是不完整的、多线程下还不安全。
  • 原型要解决的是"造一份的代价太大" :有些对象初始化要查数据库、要做复杂计算,每次都重新 new 一遍代价很高,不如基于已有的实例做一次复制。

四条痛点看似各不相同,其实都是同一个根源:当创建过程本身携带了额外的约束或者成本,直接暴露一个裸的构造函数,就不足以把这些约束表达清楚,于是才需要专门的模式把"创建"这一步封装起来。


三、怎么用:四种模式的最小样子

这一节只给每种模式最简的样子,建立第一印象,具体的实现方式、常见坑留到各自专属的一篇里展开。

单例:把构造方法私有化,类自己持有并返回唯一实例:

java 复制代码
public class Singleton {
    private static final Singleton INSTANCE = new Singleton();
    private Singleton() {}
    public static Singleton getInstance() { return INSTANCE; }
}

工厂 :调用方只传参数,不关心具体创建的是哪个类,JDK 里 Calendar.getInstance() 就是这个套路,内部按时区、地区参数返回不同的 Calendar 子类实例:

java 复制代码
public class ShapeFactory {
    public static Shape create(String type) {
        if ("circle".equals(type)) return new Circle();
        if ("square".equals(type)) return new Square();
        throw new IllegalArgumentException("unknown type: " + type);
    }
}

建造者 :把一堆可选参数拆成链式调用,Lombok 的 @Builder 生成的就是这种代码:

java 复制代码
User user = User.builder()
        .name("Alice")
        .age(20)
        .build();

原型 :基于已有对象复制,而不是重新构建,JDK 的 ArrayList 就实现了 Cloneable

java 复制代码
public class ConfigTemplate implements Cloneable {
    @Override
    public ConfigTemplate clone() throws CloneNotSupportedException {
        return (ConfigTemplate) super.clone();
    }
}

四、面试追问

Q1:单例、工厂、建造者、原型这四种创建型模式,本质上是在解决同一个问题吗?

是同一大类问题的四个不同侧面:都是围绕"怎么创建对象"展开,把对象的创建过程从使用过程中解耦出来。具体痛点不同------单例应对全局唯一性约束,工厂应对多类型选择,建造者应对参数多且可选的复杂构造,原型应对创建成本过高------但共同的落脚点都是让创建这一步不再是一个裸的 new

Q2:既然可以直接 new,为什么还要用单例模式和工厂模式?

new 本身没法表达额外约束。单例场景下,业务要求全局只能存在一个实例,new 关键字管不住"再造一个";工厂场景下,如果让调用方自己判断该创建哪个具体实现类,判断逻辑会散落在代码各处,调用方也会被迫直接依赖具体实现类,换实现要改所有调用点。这两种模式都是把这部分本该收敛的逻辑收进一个统一的入口。

Q3:建造者模式解决的是构造函数的什么问题?

解决参数多、其中一部分还是可选的这类复杂对象的构造问题。如果全塞进构造函数,要么退化成一长串参数(顺序全靠记,容易传错),要么退化成一堆重载构造函数的排列组合;如果先 new 出对象再逐个 set,对象在设置完整之前处于不完整状态,多线程下也不安全。建造者用链式调用把这些可选参数按需组装,构造过程本身也可以做校验。

Q4:原型模式相比直接 new 再赋值,节省的是什么成本?什么时候更适合用它?

节省的是重新构建对象要付出的初始化成本------比如要查数据库、做复杂计算才能得到的对象状态。原型模式基于已有实例做一次复制,跳过这些昂贵的初始化步骤。适用场景是"创建成本高但结构相对固定"的对象,比如某种预先加载好的配置模板、需要昂贵计算才能得到的中间结果对象。

Q5:创建型模式和后面要讲的结构型、行为型模式,核心区别是什么?

三大类模式都是在做解耦,只是解耦的对象不一样:创建型模式解耦的是对象的创建过程和使用过程;结构型模式解耦的是不同的功能模块,让它们能灵活组合;行为型模式解耦的是对象之间不同的行为/职责划分。创建型模式只管"对象怎么被造出来",一旦对象造好了,接下来该怎么组织、怎么协作,就交给结构型和行为型模式了。


下一篇预告

Day09 单例模式:饿汉、懒汉、双重检查锁、枚举实现,哪种才是真的安全。

相关推荐
怕浪猫1 天前
一行命令复刻爆款视频,我把 Hypit 从安装跑到了出片
人工智能·设计模式·程序员
Zane19941 天前
函数式编程里的函数,其实不是你天天写的那个函数——三大编程范式的边界在哪
设计模式
Zane19942 天前
策略模式现在该不该上?一次讲清楚过度设计和设计不足怎么找平衡
设计模式
她说..2 天前
常见设计模式-模板方法模式
java·spring·设计模式·springboot
xiaofeiyang1503 天前
第六章 · 桥接 — 三支毛笔,画出九种颜色
设计模式
Shadow(⊙o⊙)3 天前
OTOL设计模式 One Thread One Loop
服务器·网络·设计模式
sarasuki4 天前
如何让LLM 能在半夜偷偷打开网易云呢?
人工智能·设计模式·agent
小王师傅664 天前
【设计模式】装饰模式(四):框架源码实战——从 Java I/O 到 Spring 到 MyBatis
java·设计模式
执明wa4 天前
Android RecyclerView 多类型, 多种 Item
android·xml·开发语言·设计模式·android studio