1. 简介

设计模式 是我们在编写软件时常用的一些最佳实践总结。这些模式随着时间的推移逐渐形成,能够帮助我们确保代码结构良好、易于维护。

创建型模式 专注于对象实例的创建方式。通常是指如何构造一个类的新实例,但有时也包括获取已构造好的实例供使用。

在本文中,我们将回顾一些常见的创建型设计模式,看看它们在 JVM 或其他核心库中是如何体现的。

2. 工厂方法(Factory Method)

工厂方法模式将对象的创建过程与实际使用的类解耦。这样我们可以隐藏具体类型,让客户端代码以接口或抽象类的形式进行操作:

class SomeImplementation implements SomeInterface {
    // ...
}
public class SomeInterfaceFactory {
    public SomeInterface newInstance() {
        return new SomeImplementation();
    }
}

这里客户端代码不需要知道 SomeImplementation 的存在,只需要面向 SomeInterface 编程。更重要的是,我们可以随时更换工厂返回的具体类型,而无需修改客户端代码,甚至可以在运行时动态选择。

2.1. JVM 中的应用示例

JVM 中最典型的例子是 Collections 类提供的一系列集合构建方法,比如:

  • singleton()
  • singletonList()
  • singletonMap()

这些方法都返回对应类型的集合(Set, List, Map),但具体的实现类并不重要。此外,像 Stream.of()Set.of()List.of()Map.ofEntries() 等新方法也体现了同样的思想。

还有一些类似的例子,如:

  • Charset.forName():根据名称返回不同的字符集实例。
  • ResourceBundle.getBundle():根据名称加载不同的资源文件。

并不是所有工厂方法都需要返回不同类型的实例。有些只是为了封装内部细节。例如:

  • Calendar.getInstance()
  • NumberFormat.getInstance()

这两个方法每次都返回相同实例,但客户端并不关心具体细节。

3. 抽象工厂(Abstract Factory)

抽象工厂 模式更进一步,它不仅将对象的创建抽象化,还把工厂本身也抽象出来。我们可以通过抽象工厂接口来编写代码,并在运行时选择具体的工厂实现。

首先定义接口和具体实现:

interface FileSystem {
    // ...
}
class LocalFileSystem implements FileSystem {
    // ...
}
class NetworkFileSystem implements FileSystem {
    // ...
}

然后定义工厂接口及其实现:

interface FileSystemFactory {
    FileSystem newInstance();
}
class LocalFileSystemFactory implements FileSystemFactory {
    // ...
}
class NetworkFileSystemFactory implements FileSystemFactory {
    // ...
}

最后通过一个方法获取具体的工厂实例:

class Example {
    static FileSystemFactory getFactory(String fs) {
        FileSystemFactory factory;
        if ("local".equals(fs)) {
            factory = new LocalFileSystemFactory();
        else if ("network".equals(fs)) {
            factory = new NetworkFileSystemFactory();
        }
        return factory;
    }
}

在这个例子中,FileSystemFactory 接口有两个具体实现。我们可以在运行时选择具体工厂,而客户端代码对此无感知。这些工厂各自返回不同的 FileSystem 实例,客户端也不需要知道具体是哪个。

通常我们还会用另一个工厂方法来获取抽象工厂本身。上述例子中的 getFactory() 就是一个工厂方法,返回抽象工厂,再由该工厂创建实际对象。

3.1. JVM 中的应用示例

JVM 中大量使用了这种设计模式。最常见的例子是 XML 处理相关的类:

  • DocumentBuilderFactory
  • TransformerFactory
  • XPathFactory

这些类都有一个特殊的 newInstance() 方法用于获取抽象工厂实例。

这个方法内部会尝试多种机制来决定使用哪个具体实现:

这使得我们可以替换默认的 XML 实现库,而对使用方透明。

比如,默认情况下你会得到 Xerces 实现的类:

com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl

但如果替换了实现,调用 newInstance() 后会自动返回新的实现类。

4. 建造者模式(Builder)

建造者模式适用于需要灵活构造复杂对象的场景。它通过一个独立的 Builder 类来逐步设置对象属性,最后统一构建目标对象:

class CarBuilder {
    private String make = "Ford";
    private String model = "Fiesta";
    private int doors = 4;
    private String color = "White";

    public Car build() {
        return new Car(make, model, doors, color);
    }
}

这种方式允许客户端逐个设置属性,最终通过 build() 方法生成完整的对象。

4.1. JVM 中的应用示例

JVM 中有很多建造者模式的经典应用:

StringBuilderStringBuffer:用来构建长字符串。 ✅ Stream.Builder:用于构建 Stream 对象。

示例代码如下:

Stream.Builder<Integer> builder = Stream.builder<Integer>();
builder.add(1);
builder.add(2);
if (condition) {
    builder.add(3);
    builder.add(4);
}
builder.add(5);
Stream<Integer> stream = builder.build();

5. 懒加载(Lazy Initialization)

懒加载模式用于延迟计算某些值,直到真正需要时才执行。它可以应用于单个数据项,也可以用于整个对象。

常见于以下场景:

❌ 构造对象需要访问数据库或网络,但可能永远用不到。 ❌ 计算大量可能不会被使用的结果,浪费内存。

典型实现方式是使用一个包装对象,在首次调用 getter 时才计算并缓存结果:

class LazyPi {
    private Supplier<Double> calculator;
    private Double value;

    public synchronized Double getValue() {
        if (value == null) {
            value = calculator.get();
        }
        return value;
    }
}

在这个例子中,计算 π 是一个昂贵的操作,只在第一次调用 getValue() 时才会发生。

5.1. JVM 中的应用示例

虽然 JVM 中直接使用懒加载的例子不多,但 Java 8 引入的 Streams API 是个很好的例子。

✅ 所有流操作都是懒加载的,只有在终端操作触发时才会真正执行。

例如:

Stream.generate(new BaeldungArticlesLoader())
  .filter(article -> article.getTags().contains("java-streams"))
  .map(article -> article.getTitle())
  .findFirst();

这段代码中,generate() 只会在需要下一个元素时才会调用 Supplier,从而避免不必要的网络请求。

6. 对象池(Object Pool)

当我们频繁创建代价高昂的对象时,可以考虑使用对象池模式。与其每次都新建对象,不如预先创建一批对象放入池中,按需复用。

对象池的作用就是管理这些共享对象,确保同一时间每个对象只被一个地方使用。

6.1. JVM 中的应用示例

✅ 最典型的例子就是线程池。

使用 ExecutorService 可以管理一组线程,避免每次异步任务都要新建线程带来的开销:

ExecutorService pool = Executors.newFixedThreadPool(10);

pool.execute(new SomeTask()); // 使用池中的线程
pool.execute(new AnotherTask()); // 再次使用池中的线程

两个任务可能会使用相同的线程,也可能使用不同的线程,但客户端无需关心。

7. 原型模式(Prototype)

原型模式用于创建与现有对象完全相同的新实例。原始对象作为“原型”,通过克隆生成新的副本。

Java 提供了部分支持:实现 Cloneable 接口并重写 clone() 方法即可:

public class Prototype implements Cloneable {
    private Map<String, String> contents = new HashMap<>();

    public void setValue(String key, String value) {
        // ...
    }
    public String getValue(String key) {
        // ...
    }

    @Override
    public Prototype clone() {
        Prototype result = new Prototype();
        this.contents.entrySet().forEach(entry -> result.setValue(entry.getKey(), entry.getValue()));
        return result;
    }
}

⚠️ 注意:Object.clone() 默认是浅拷贝,如果字段是引用类型,则两个对象会共享同一个引用。必要时需手动深拷贝。

7.1. JVM 中的应用示例

JVM 中有不少类实现了 Cloneable 接口,如:

  • PKIXCertPathBuilderResult
  • PKIXBuilderParameters
  • PKIXParameters
  • PKIXCertPathBuilderResult
  • PKIXCertPathValidatorResult

还有一个经典例子是 java.util.Date 类,它重写了 clone() 方法,连同 transient 字段一起复制。

8. 单例模式(Singleton)

单例模式用于确保某个类只有一个实例,并且全局可访问。通常通过静态变量和静态方法来实现:

public class Singleton {
    private static Singleton instance = null;

    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

根据需求不同,可以有多种变体:

  • 实例是否在启动时创建?
  • 是否线程安全?
  • 是否每个线程一个实例?

8.1. JVM 中的应用示例

JVM 中有不少单例类,代表 JVM 核心组件:

  • Runtime.getRuntime()
  • Desktop.getDesktop()
  • SecurityManager.getSecurityManager()

另外,Java 反射 API 中的 Class 实例也是单例。无论你通过 Class.forName()String.class 还是其他方式获取,同一个类始终返回相同的 Class 实例。

类似地,当前线程的 Thread.currentThread() 返回的也是当前线程唯一的 Thread 实例。

9. 总结

本文介绍了多种创建型设计模式,并展示了它们在 JVM 核心库中的实际应用。理解这些模式不仅能提升代码质量,还能让我们更好地利用已有框架和工具。


原始标题:Creational Design Patterns in Core Java