Java设计模式-原型模式
模式概述
原型模式简介
定义:通过复制(克隆)一个已存在的对象(原型)来创建新对象,而不是通过传统的new
关键字调用构造方法生成。原型模式通过封装对象的复制逻辑,避免了重复初始化的复杂操作,尤其适用于对象创建成本较高(如涉及数据库查询、IO读取或复杂计算)的场景。
模式类型:创建型模式(Creational Pattern)。
作用:
- 降低对象创建成本:通过克隆已有对象,避免重复执行高开销的初始化逻辑(如数据库查询、资源加载);
- 简化对象创建流程:将复杂对象的构造逻辑封装在原型类中,客户端只需调用克隆方法即可获得新对象;
- 支持动态生成对象:通过修改原型对象的属性,可以快速生成不同配置的新对象;
- 避免继承冗余:相比通过子类扩展对象(如
SubClass extends BaseClass
),原型模式通过克隆实现更灵活的动态扩展。
典型应用场景:
- 对象创建成本高(如需要从数据库加载配置的
User
对象、需要初始化大型资源的GameCharacter
); - 需要动态生成对象(如根据用户输入实时创建不同参数的
ImageFilter
); - 避免使用子类扩展对象(如系统需要支持多种类型的
Document
,但通过继承扩展会导致类爆炸); - 需要保证对象初始化的一致性(如所有
Report
对象需包含相同的默认标题和页眉,通过克隆原型保证一致性)。
我认为:原型模式就像“对象复印机”——用一个已有的对象当模板,直接“复印”出多个相同或相似的新对象,既省去了重新排版的麻烦(避免重复初始化),又能快速得到结果。
课程目标
- 理解原型模式的核心思想和经典应用场景
- 识别应用场景,使用原型模式解决功能要求
- 了解原型模式的优缺点
核心组件
角色-职责表
角色 | 职责 | 示例类名 |
---|---|---|
抽象原型(Prototype) | 定义克隆自身的抽象方法(如clone() ),通常是接口或抽象类。 |
Shape (图形接口) |
具体原型(ConcretePrototype) | 实现抽象原型的clone() 方法,完成自身的深拷贝或浅拷贝。 |
Circle (圆形)、Rectangle (矩形) |
客户端(Client) | 持有原型对象,通过调用clone() 方法创建新对象。 |
ShapeFactory (图形工厂) |
类图
下面是一个简化的类图表示,展示了原型模式中的主要角色及其交互方式:
传统实现 VS 原型模式
案例需求
案例背景:设计一个图形绘制系统,需要频繁创建不同颜色和半径的圆形(Circle
)。每个圆形对象需要初始化颜色(需从配置文件读取)、半径(用户输入),并记录创建时间(系统时间戳)。传统方式通过new
关键字创建对象,但初始化逻辑复杂且重复。
传统实现(痛点版)
代码实现:
// 传统方式:通过工厂方法创建对象(高开销)
public class CircleFactory {
// 模拟高开销操作:从配置文件读取默认颜色(假设需要IO)
private static String getDefaultColor() {
try {
Thread.sleep(100); // 模拟IO延迟
return "RED"; // 实际从config.properties读取
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
// 模拟高开销操作:获取系统时间戳
private static long getCurrentTimestamp() {
try {
Thread.sleep(50); // 模拟系统调用延迟
return System.currentTimeMillis();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
// 创建圆形对象(每次需重复执行默认颜色和时间戳获取)
public static Circle createCircle(double radius) {
String color = getDefaultColor(); // 高开销(IO)
long createTime = getCurrentTimestamp(); // 高开销(系统调用)
return new Circle(radius, color, createTime);
}
}
// 圆形对象
public class Circle {
private double radius;
private String color;
private long createTime;
public Circle(double radius, String color, long createTime) {
this.radius = radius;
this.color = color;
this.createTime = createTime;
}
// Getter/Setter...
public double getRadius() { return radius; }
public void setRadius(double radius) { this.radius = radius; }
public String getColor() { return color; }
public void setColor(String color) { this.color = color; }
public long getCreateTime() { return createTime; }
}
// 客户端使用
public class Client {
public static void main(String[] args) {
// 创建第一个圆形(耗时:100ms IO + 50ms 系统调用)
Circle circle1 = CircleFactory.createCircle(10.0);
// 创建第二个圆形(同样需要重复执行100ms IO + 50ms 系统调用)
Circle circle2 = CircleFactory.createCircle(20.0);
}
}
痛点总结:
- 高开销重复操作:每次创建对象都需执行
getDefaultColor()
(IO)和getCurrentTimestamp()
(系统调用),当需要创建大量对象时(如1000次),总耗时会线性增长(100+50)*1000=150,000ms=150秒); - 代码冗余:初始化逻辑(如读取配置、时间戳)封装在工厂方法中,但每次创建仍需重复调用,无法复用已初始化的对象;
- 灵活性差:若需要修改默认颜色(如从
RED
改为BLUE
),需修改工厂方法的getDefaultColor()
逻辑,并影响所有新创建的对象。
原型模式 实现(优雅版)
代码实现:
// 1. 抽象原型:定义克隆方法
public interface Prototype {
Prototype clone(); // 浅拷贝或深拷贝
}
// 2. 具体原型:圆形(实现克隆逻辑)
public class Circle implements Prototype {
private double radius;
private String color;
private long createTime;
// 构造方法(仅初始化一次,高开销操作在此完成)
public Circle(double radius, String color) {
this.radius = radius;
this.color = color;
try {
Thread.sleep(100); // 模拟IO读取颜色(仅执行一次)
this.color = color;
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
this.createTime = System.currentTimeMillis(); // 系统时间戳(仅执行一次)
}
// 克隆方法(深拷贝:复制所有属性)
@Override
public Circle clone() {
// 直接创建新对象,复制当前实例的属性(无需重复IO或系统调用)
Circle cloned = new Circle(this.radius, this.color);
// 若需要独立修改时间戳,可重置(可选)
cloned.createTime = System.currentTimeMillis();
return cloned;
}
// Getter/Setter...
public double getRadius() { return radius; }
public void setRadius(double radius) { this.radius = radius; }
public String getColor() { return color; }
public void setColor(String color) { this.color = color; }
public long getCreateTime() { return createTime; }
}
// 3. 客户端:使用原型克隆对象
public class Client {
public static void main(String[] args) {
// 初始化原型对象(仅执行一次高开销操作:IO读取颜色)
Circle prototype = new Circle(10.0, "RED");
// 克隆原型生成新对象(无需重复IO,仅需复制属性)
Circle circle1 = prototype.clone();
circle1.setRadius(15.0); // 修改半径
Circle circle2 = prototype.clone();
circle2.setColor("BLUE"); // 修改颜色
// 输出结果
System.out.println("circle1: radius=" + circle1.getRadius() + ", color=" + circle1.getColor() + ", createTime=" + circle1.getCreateTime());
System.out.println("circle2: radius=" + circle2.getRadius() + ", color=" + circle2.getColor() + ", createTime=" + circle2.getCreateTime());
System.out.println("prototype: radius=" + prototype.getRadius() + ", color=" + prototype.getColor() + ", createTime=" + prototype.getCreateTime());
}
}
优势:
- 降低开销:原型对象仅在首次创建时执行高开销操作(如IO读取颜色),后续克隆仅需复制属性,总耗时从150秒降至约100ms(仅首次)+ 2 * 0ms(克隆)≈100ms;
- 灵活扩展:通过修改原型对象的属性(如
prototype.setColor("GREEN")
),可快速生成不同配置的新对象; - 代码简洁:克隆逻辑封装在原型类中,客户端只需调用
clone()
方法,无需关心初始化细节; - 一致性保证:所有克隆对象的基础属性(如颜色)与原型一致,避免因重复初始化导致的配置错误。
局限:
- 深拷贝复杂度:若对象包含引用类型属性(如
List
、其他对象),需实现深拷贝(重写clone()
方法并递归克隆引用对象),否则可能导致多个克隆对象共享同一引用,引发意外修改; - 原型管理成本:若需要管理多个原型(如不同颜色、尺寸的原型),需引入原型管理器(
PrototypeManager
)来存储和查找原型,增加系统复杂度; - 不适合动态变化对象:若对象的状态在运行时频繁变化(如实时更新的传感器数据),克隆可能导致旧状态被保留,需谨慎使用。
模式变体
- 浅拷贝原型:仅复制基本类型属性和引用类型地址(默认
clone()
方法行为),适用于引用类型属性不可变(如String
)的场景; - 深拷贝原型:递归复制所有引用类型属性(如通过序列化/反序列化实现),适用于引用类型属性可变的场景(如
List
、自定义对象); - 不可变原型:原型对象创建后不可修改(属性为
final
),克隆时直接返回新对象,避免共享状态问题; - 缓存原型(原型管理器):通过
Map
缓存常用原型(如Map<String, Prototype> prototypeCache
),客户端通过键名获取原型并克隆,减少重复创建; - 懒加载原型:原型对象在首次使用时克隆(而非提前创建),适用于资源敏感的场景(如内存受限的系统)。
最佳实践
建议 | 理由 |
---|---|
优先使用深拷贝 | 若对象包含可变引用类型属性(如List 、其他对象),深拷贝可避免克隆对象与原型共享状态,防止意外修改。 |
明确克隆语义 | 在文档中说明克隆是深拷贝还是浅拷贝(如Java的ArrayList.clone() 是浅拷贝),避免客户端误解。 |
原型对象不可变 | 若原型对象创建后无需修改(如配置模板),将其属性设为final ,确保克隆结果的一致性和线程安全。 |
使用原型管理器 | 当需要管理多个原型时(如不同类型、配置的原型),通过PrototypeManager 统一存储和查找,提高灵活性。 |
避免过度克隆 | 若对象创建成本不高(如仅包含基本类型属性),直接使用new 关键字更简单,无需引入原型模式。 |
一句话总结
原型模式通过克隆已有对象高效创建新对象,避免了重复初始化的高开销操作,尤其适用于对象创建复杂或需要动态生成多配置对象的场景。
如果关注Java设计模式内容,可以查阅作者的其他Java设计模式系列文章。😊