23种设计模式之原型模式

120 阅读6分钟

1. 什么是原型模式

原型模式(Prototype Pattern)是 GoF 23种设计模式中对象创建型设计模式之一,其核心思想是通过复制一个已有的对象(原型)来创建新的对象,而不是通过 new 关键字和构造函数来创建。

2.为什么需要原型模式

当对象的创建过程成本高昂结构复杂依赖于运行时动态状态时,直接通过构造函数或工厂方法频繁地从头创建实例会导致:

  • 性能开销大:每次创建都需要执行耗时的操作(如数据库连接、文件读取、网络请求、复杂计算等),影响系统效率。
  • 重复初始化逻辑:多个相似对象需要执行相同的初始化流程,造成资源浪费和代码冗余。
  • 难以动态复制已有状态:无法便捷地基于一个已配置好的对象生成副本,尤其是在对象包含大量可变状态或深层配置的情况下。
  • 对具体类的强依赖:客户端需明确知道类名并使用 new 创建实例,导致代码耦合度高,扩展性差。

原型模式通过提供一个 clone() 方法,允许对象自行复制自身,从而避免了上述问题。它使得:

  • 可以基于现有对象快速生成新实例,无需重复昂贵的构建过程;
  • 支持在运行时动态决定对象的类型和结构,提升系统的灵活性;
  • 客户端与具体类解耦,只需操作原型接口即可创建对象;
  • 复杂对象的复用变得更加简单和安全。

因此,原型模式特别适用于需要高频创建相似对象对象初始化代价较高的场景,是提升性能与可维护性的重要手段。

3. 角色与UML

classDiagram

    %% 定义接口
    class Prototype {
        <<interface>>
        +clone(): Prototype
    }

    %% 定义具体类
    class Document {
        -title: String
        -content: String
        -author: String
        +Document(title: String, content: String, author: String)
        +clone(): Document
        +getTitle(): String
        +setTitle(title: String): void
        +getContent(): String
        +setContent(content: String): void
        +getAuthor(): String
        +setAuthor(author: String): void
        +toString(): String
    }

    %% 实现 Cloneable 接口(Java 内置)
    class Cloneable {
        <<interface>>
    }

    %% 客户端示例类
    class Client {
        +main(args: String[]): void
    }

    %% 关系定义
    Prototype <|.. Document : implements
    Cloneable <|.. Document : implements
    Client --> Document : uses
类 / 接口模式中的角色作用与职责
Prototype抽象原型接口声明克隆方法 clone(),定义所有可克隆对象的通用接口,支持多态克隆。
Document具体原型类实现 Prototype 接口和 Cloneable 接口,具体实现 clone() 方法,完成对象的浅拷贝。
CloneableJava 标记接口标记该类允许使用 Java 原生的 Object.clone() 机制,避免抛出 CloneNotSupportedException
Client客户端(Client)使用原型模式的客户端代码,创建原始对象并通过 clone() 方法快速复制新对象,无需重新构造。

Cloneable 是为了让 super.clone() 能正常工作(语言机制)。因为Object.clone()protected 方法,只有实现了 Cloneable 接口的类才能安全地调用它而不抛异常。Prototype 是为了实现设计模式的抽象和多态(架构设计)。Prototype不是强制要求,但定义一个克隆接口是良好实践,表现了它在软件设计和架构层面的重要意义,它明确了该类支持克隆操作。Cloneable 是 Java 的底层克隆机制要求,Prototype 是设计模式的抽象需求。两者协同工作,一个负责“能克隆”,一个负责“规范克隆”。

4. Java代码示例

1、原型接口(可选)

public interface Prototype {
    Prototype clone();
}

2、具体原型类

public class Document implements Prototype, Cloneable {
    private String title;
    private String content;
    private String author;

    public Document(String title, String content, String author) {
        this.title = title;
        this.content = content;
        this.author = author;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    @Override
    public Document clone() {
        try {
            return (Document) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public String toString() {
        return "Document{" +
                "title='" + title + ''' +
                ", content='" + content + ''' +
                ", author='" + author + ''' +
                '}';
    }
}

3、客户端

public class Client {
    public static void main(String[] args) {
        Document originalDoc = new Document("设计模式详解", "原型模式是一种创建型模式...", "Alice");
        System.out.println("原始文档: " + originalDoc);

        // 使用原型模式克隆对象
        Document clonedDoc = (Document) originalDoc.clone();
        System.out.println("克隆文档: " + clonedDoc);

        // 验证两者是不同的对象实例
        System.out.println("是否为同一对象? " + (originalDoc == clonedDoc)); // false

        // 修改克隆对象,不影响原始对象(浅拷贝下,基本类型安全)
        clonedDoc.setTitle("修改后的标题");
        clonedDoc.setAuthor("Bob");

        System.out.println("修改后原始文档: " + originalDoc);
        System.out.println("修改后克隆文档: " + clonedDoc);
    }
}

运行结果

原始文档: Document{title='设计模式详解', content='原型模式是一种创建型模式...', author='Alice'}
克隆文档: Document{title='设计模式详解', content='原型模式是一种创建型模式...', author='Alice'}
是否为同一对象? false
修改后原始文档: Document{title='设计模式详解', content='原型模式是一种创建型模式...', author='Alice'}
修改后克隆文档: Document{title='修改后的标题', content='原型模式是一种创建型模式...', author='Bob'}

5. 优缺点

维度优点(Pros)缺点(Cons)
性能避免重复执行复杂的对象创建过程(如数据库连接、文件读取、复杂计算),直接复制已有对象,提升创建效率。浅拷贝可能带来对象间状态共享问题;深拷贝实现复杂,可能涉及递归复制,影响性能。
封装性客户端无需了解对象创建的内部细节,只需调用 clone() 方法,符合封装原则。如果 clone() 实现不当(如未正确处理引用字段),会破坏封装,导致外部意外修改原型状态。
灵活性可在运行时动态添加或替换可克隆的原型对象,支持插件化和配置化系统。需要每个具体类都正确实现 clone() 方法,缺乏编译期强制约束,易遗漏或实现错误。
多态支持通过 Prototype 接口统一操作不同类型的可克隆对象,支持多态克隆,便于集合管理。必须定义接口或抽象类,增加了类层次结构的复杂性,小项目中可能显得冗余。
简化对象创建无需通过 new 调用构造函数,尤其适用于构造参数复杂或难以获取的场景。对于简单对象(如 POJO),使用构造函数更直观,原型模式反而增加复杂度。
语言机制依赖利用 Java 的 Object.clone()(本地方法),性能较好。必须实现 Cloneable 接口,否则抛异常;Cloneable 是“标记接口”,语义不清晰,易被误用。
深拷贝挑战支持通过重写 clone() 实现深拷贝,精确控制对象复制行为。深拷贝需手动处理所有引用类型字段(如集合、自定义对象),实现繁琐且易出错。

6. 典型应用

  • Spring 框架@Scope("prototype") 每次从 Spring 容器获取该 Bean 时(如 applicationContext.getBean("myBean")),容器都会返回一个全新的实例。这个实例不是通过反射调用构造函数重新创建的,而是在容器内部基于原型模板动态生成或克隆而来。

一句话总结:原型模式就是复制已有对象当模板,直接复印新对象,不用重新初始化。