设计模式手册007:原型模式 - 通过复制创建对象的艺术
本文是「设计模式手册」系列第007篇,我们将深入探讨原型模式,这种模式通过复制现有对象来创建新对象,避免了昂贵的创建成本,是性能优化的利器。
1. 场景:我们为何需要原型模式?
在软件开发中,有些对象的创建成本很高,比如:
- 数据库查询结果:查询耗时,但需要多个副本
- 复杂配置对象:初始化复杂,但结构相似
- 游戏中的敌人:大量相似对象需要快速创建
- 文档模板:基础结构相同,内容不同
- 图形对象:复杂图形需要复制修改
传统做法的困境:
java
public class DatabaseConfig {
private String url;
private String username;
private String password;
private int maxConnections;
private int timeout;
private Properties connectionProperties;
// 复杂的初始化过程
public DatabaseConfig() {
// 从配置文件加载
loadFromConfigFile();
// 环境变量覆盖
applyEnvironmentOverrides();
// 验证配置
validateConfig();
// 初始化连接属性
initConnectionProperties();
}
private void loadFromConfigFile() {
// 模拟耗时的配置文件加载
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.url = "jdbc:mysql://localhost:3306/mydb";
this.username = "admin";
this.password = "password";
this.maxConnections = 20;
this.timeout = 30;
}
private void applyEnvironmentOverrides() {
// 应用环境变量覆盖
// 复杂的环境处理逻辑...
}
private void validateConfig() {
// 配置验证逻辑
if (url == null || username == null) {
throw new IllegalArgumentException("数据库配置不完整");
}
}
private void initConnectionProperties() {
this.connectionProperties = new Properties();
connectionProperties.setProperty("useSSL", "false");
connectionProperties.setProperty("serverTimezone", "UTC");
// 更多属性初始化...
}
// getter和setter方法
// ...
}
// 问题:每次创建新配置都要执行完整的初始化过程
public class Application {
public void start() {
// 每次创建都要执行复杂的初始化
DatabaseConfig config1 = new DatabaseConfig();
DatabaseConfig config2 = new DatabaseConfig(); // 重复初始化!
DatabaseConfig config3 = new DatabaseConfig(); // 重复初始化!
// 浪费了大量时间和资源在重复的初始化上
}
}
这种实现的痛点:
- 创建成本高:重复执行昂贵的初始化操作
- 资源浪费:相同的数据被多次加载和处理
- 性能瓶颈:大量对象创建时影响系统性能
- 代码重复:相同的初始化逻辑被多次执行
2. 原型模式:定义与本质
2.1 模式定义
原型模式(Prototype Pattern):用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
2.2 核心要素
java
// 原型接口
public interface Prototype<T> {
T clone();
}
// 具体原型类
public class DatabaseConfig implements Prototype<DatabaseConfig> {
private String url;
private String username;
private String password;
private int maxConnections;
private int timeout;
private Properties connectionProperties;
// 私有构造器,用于克隆
private DatabaseConfig(DatabaseConfig source) {
this.url = source.url;
this.username = source.username;
this.password = source.password;
this.maxConnections = source.maxConnections;
this.timeout = source.timeout;
// 深拷贝Properties
this.connectionProperties = new Properties();
this.connectionProperties.putAll(source.connectionProperties);
}
// 公开构造器,用于初始创建
public DatabaseConfig() {
initialize();
}
private void initialize() {
// 复杂的初始化逻辑
loadFromConfigFile();
applyEnvironmentOverrides();
validateConfig();
initConnectionProperties();
System.out.println("执行了昂贵的初始化过程");
}
@Override
public DatabaseConfig clone() {
return new DatabaseConfig(this);
}
// 其他方法...
private void loadFromConfigFile() {
try { Thread.sleep(100); } catch (InterruptedException e) {}
this.url = "jdbc:mysql://localhost:3306/mydb";
this.username = "admin";
this.password = "password";
this.maxConnections = 20;
this.timeout = 30;
}
private void applyEnvironmentOverrides() {
// 环境变量处理逻辑
}
private void validateConfig() {
if (url == null || username == null) {
throw new IllegalArgumentException("数据库配置不完整");
}
}
private void initConnectionProperties() {
this.connectionProperties = new Properties();
connectionProperties.setProperty("useSSL", "false");
connectionProperties.setProperty("serverTimezone", "UTC");
}
// getter和setter
public String getUrl() { return url; }
public void setUrl(String url) { this.url = url; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
// 其他getter/setter...
}
3. 深入理解:原型模式的多维视角
3.1 第一重:浅拷贝与深拷贝
核心概念:理解拷贝的深度对原型模式至关重要
java
public class ShallowCopyExample implements Cloneable {
private String name;
private List<String> items;
public ShallowCopyExample(String name, List<String> items) {
this.name = name;
this.items = items;
}
// 浅拷贝 - 默认的clone()方法
@Override
public ShallowCopyExample clone() {
try {
return (ShallowCopyExample) super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
// 深拷贝 - 手动实现
public ShallowCopyExample deepClone() {
List<String> clonedItems = new ArrayList<>(this.items);
return new ShallowCopyExample(this.name, clonedItems);
}
// 测试
public static void main(String[] args) {
List<String> originalList = new ArrayList<>();
originalList.add("item1");
originalList.add("item2");
ShallowCopyExample original = new ShallowCopyExample("test", originalList);
ShallowCopyExample shallowCopy = original.clone();
ShallowCopyExample deepCopy = original.deepClone();
// 修改原始列表
originalList.add("item3");
System.out.println("Original items: " + original.getItems());
System.out.println("Shallow copy items: " + shallowCopy.getItems()); // 也被修改了!
System.out.println("Deep copy items: " + deepCopy.getItems()); // 不受影响
}
public List<String> getItems() {
return items;
}
}
3.2 第二重:Java的Cloneable接口
java
public class JavaCloneableExample implements Cloneable {
private String data;
private int number;
private List<String> list;
public JavaCloneableExample(String data, int number, List<String> list) {
this.data = data;
this.number = number;
this.list = list;
}
// 重写clone方法实现深拷贝
@Override
public JavaCloneableExample clone() {
try {
JavaCloneableExample cloned = (JavaCloneableExample) super.clone();
// 对引用类型进行深拷贝
if (this.list != null) {
cloned.list = new ArrayList<>(this.list);
}
return cloned;
} catch (CloneNotSupportedException e) {
throw new RuntimeException("克隆失败", e);
}
}
// getter方法
public String getData() { return data; }
public int getNumber() { return number; }
public List<String> getList() { return list; }
}
3.3 第三重:原型管理器
java
// 原型管理器
public class PrototypeManager {
private static final Map<String, Prototype<?>> prototypes = new HashMap<>();
static {
// 预加载常用原型
prototypes.put("defaultConfig", new DatabaseConfig());
prototypes.put("testConfig", createTestConfig());
prototypes.put("productionConfig", createProductionConfig());
}
public static void registerPrototype(String key, Prototype<?> prototype) {
prototypes.put(key, prototype);
}
@SuppressWarnings("unchecked")
public static <T> T getPrototype(String key) {
Prototype<?> prototype = prototypes.get(key);
if (prototype == null) {
throw new IllegalArgumentException("未知的原型: " + key);
}
return (T) prototype.clone();
}
private static DatabaseConfig createTestConfig() {
DatabaseConfig config = new DatabaseConfig();
config.setUrl("jdbc:mysql://test:3306/testdb");
return config;
}
private static DatabaseConfig createProductionConfig() {
DatabaseConfig config = new DatabaseConfig();
config.setUrl("jdbc:mysql://prod:3306/proddb");
config.setMaxConnections(100);
return config;
}
}
4. 实战案例:完整的图形编辑器
java
// 图形接口
public interface Shape extends Prototype<Shape> {
void draw();
void setColor(String color);
String getColor();
void setPosition(int x, int y);
String getInfo();
}
// 具体图形 - 圆形
public class Circle implements Shape {
private String color;
private int x;
private int y;
private int radius;
public Circle(String color, int x, int y, int radius) {
this.color = color;
this.x = x;
this.y = y;
this.radius = radius;
System.out.println("创建圆形对象,执行复杂初始化...");
// 模拟复杂初始化
try { Thread.sleep(50); } catch (InterruptedException e) {}
}
// 拷贝构造器
private Circle(Circle source) {
this.color = source.color;
this.x = source.x;
this.y = source.y;
this.radius = source.radius;
System.out.println("克隆圆形对象,跳过复杂初始化");
}
@Override
public Circle clone() {
return new Circle(this);
}
@Override
public void draw() {
System.out.printf("在(%d,%d)位置绘制%s颜色的圆形,半径%d\n", x, y, color, radius);
}
@Override
public void setColor(String color) {
this.color = color;
}
@Override
public String getColor() {
return color;
}
@Override
public void setPosition(int x, int y) {
this.x = x;
this.y = y;
}
@Override
public String getInfo() {
return String.format("圆形[颜色=%s, 位置=(%d,%d), 半径=%d]", color, x, y, radius);
}
}
// 具体图形 - 矩形
public class Rectangle implements Shape {
private String color;
private int x;
private int y;
private int width;
private int height;
public Rectangle(String color, int x, int y, int width, int height) {
this.color = color;
this.x = x;
this.y = y;
this.width = width;
this.height = height;
System.out.println("创建矩形对象,执行复杂初始化...");
try { Thread.sleep(50); } catch (InterruptedException e) {}
}
private Rectangle(Rectangle source) {
this.color = source.color;
this.x = source.x;
this.y = source.y;
this.width = source.width;
this.height = source.height;
System.out.println("克隆矩形对象,跳过复杂初始化");
}
@Override
public Rectangle clone() {
return new Rectangle(this);
}
@Override
public void draw() {
System.out.printf("在(%d,%d)位置绘制%s颜色的矩形,尺寸%dx%d\n", x, y, color, width, height);
}
@Override
public void setColor(String color) {
this.color = color;
}
@Override
public String getColor() {
return color;
}
@Override
public void setPosition(int x, int y) {
this.x = x;
this.y = y;
}
@Override
public String getInfo() {
return String.format("矩形[颜色=%s, 位置=(%d,%d), 尺寸=%dx%d]", color, x, y, width, height);
}
}
// 图形管理器
public class ShapeManager {
private final Map<String, Shape> shapePrototypes = new HashMap<>();
public void registerPrototype(String key, Shape shape) {
shapePrototypes.put(key, shape);
}
public Shape createShape(String key) {
Shape prototype = shapePrototypes.get(key);
if (prototype == null) {
throw new IllegalArgumentException("未知的形状类型: " + key);
}
return prototype.clone();
}
public List<Shape> createMultipleShapes(String key, int count) {
List<Shape> shapes = new ArrayList<>();
Shape prototype = shapePrototypes.get(key);
if (prototype == null) {
throw new IllegalArgumentException("未知的形状类型: " + key);
}
for (int i = 0; i < count; i++) {
shapes.add(prototype.clone());
}
return shapes;
}
}
// 使用示例
public class GraphicsEditor {
public static void main(String[] args) {
ShapeManager manager = new ShapeManager();
// 注册原型
manager.registerPrototype("redCircle", new Circle("红色", 0, 0, 10));
manager.registerPrototype("blueRectangle", new Rectangle("蓝色", 0, 0, 20, 30));
System.out.println("=== 创建多个圆形 ===");
long startTime = System.currentTimeMillis();
// 通过克隆创建多个圆形
List<Shape> circles = manager.createMultipleShapes("redCircle", 5);
for (int i = 0; i < circles.size(); i++) {
Shape circle = circles.get(i);
circle.setPosition(i * 30, 50);
circle.draw();
}
long endTime = System.currentTimeMillis();
System.out.println("创建5个圆形耗时: " + (endTime - startTime) + "ms");
System.out.println("\n=== 创建多个矩形 ===");
startTime = System.currentTimeMillis();
// 通过克隆创建多个矩形
List<Shape> rectangles = manager.createMultipleShapes("blueRectangle", 5);
for (int i = 0; i < rectangles.size(); i++) {
Shape rectangle = rectangles.get(i);
rectangle.setPosition(i * 40, 100);
rectangle.draw();
}
endTime = System.currentTimeMillis();
System.out.println("创建5个矩形耗时: " + (endTime - startTime) + "ms");
}
}
5. Spring框架中的原型模式
Spring框架通过作用域配置支持原型模式:
5.1 Spring的原型Bean
java
// 原型作用域的Bean
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class EmailTemplate {
private String subject;
private String body;
private String from;
private List<String> to;
private LocalDateTime createTime;
public EmailTemplate() {
this.createTime = LocalDateTime.now();
// 模拟复杂初始化
System.out.println("创建EmailTemplate实例: " + this.hashCode());
loadDefaultTemplate();
}
private void loadDefaultTemplate() {
this.subject = "默认主题";
this.body = "默认正文内容...";
this.from = "noreply@company.com";
this.to = new ArrayList<>();
// 复杂的模板加载逻辑
try { Thread.sleep(100); } catch (InterruptedException e) {}
}
// 业务方法
public EmailTemplate customize(String subject, String body) {
EmailTemplate customized = new EmailTemplate();
customized.subject = subject;
customized.body = body;
customized.from = this.from;
customized.to = new ArrayList<>(this.to);
return customized;
}
// getter和setter
public String getSubject() { return subject; }
public void setSubject(String subject) { this.subject = subject; }
public String getBody() { return body; }
public void setBody(String body) { this.body = body; }
public String getFrom() { return from; }
public void setFrom(String from) { this.from = from; }
public List<String> getTo() { return to; }
public void setTo(List<String> to) { this.to = to; }
public LocalDateTime getCreateTime() { return createTime; }
}
// 使用原型Bean的服务
@Service
public class EmailService {
private final ApplicationContext applicationContext;
public EmailService(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
public void sendBatchEmails(List<String> recipients, String subject, String body) {
for (String recipient : recipients) {
// 每次获取都是新的实例
EmailTemplate template = applicationContext.getBean(EmailTemplate.class);
template.setSubject(subject);
template.setBody(body);
template.setTo(Collections.singletonList(recipient));
sendEmail(template);
}
}
private void sendEmail(EmailTemplate template) {
System.out.println("发送邮件: " + template.getSubject() + " 给 " + template.getTo());
// 实际发送逻辑
}
}
5.2 使用ObjectFactory延迟获取原型
java
@Component
public class PrototypeBeanFactory {
@Autowired
private ObjectFactory<EmailTemplate> emailTemplateFactory;
public void processTemplates() {
// 每次getObject()都会返回新的原型实例
EmailTemplate template1 = emailTemplateFactory.getObject();
EmailTemplate template2 = emailTemplateFactory.getObject();
System.out.println("template1 == template2: " + (template1 == template2)); // false
System.out.println("template1: " + template1.hashCode());
System.out.println("template2: " + template2.hashCode());
}
}
6. 原型模式的进阶用法
6.1 序列化实现深拷贝
java
// 通过序列化实现深拷贝的工具类
public class DeepCopyUtil {
@SuppressWarnings("unchecked")
public static <T extends Serializable> T deepCopy(T object) {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(object);
oos.close();
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
T copy = (T) ois.readObject();
ois.close();
return copy;
} catch (IOException | ClassNotFoundException e) {
throw new RuntimeException("深拷贝失败", e);
}
}
}
// 可序列化的复杂对象
public class ComplexObject implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private List<String> items;
private Map<String, Object> properties;
private NestedObject nested;
public ComplexObject(String name) {
this.name = name;
this.items = new ArrayList<>();
this.properties = new HashMap<>();
this.nested = new NestedObject();
initializeComplexData();
}
private void initializeComplexData() {
// 模拟复杂初始化
System.out.println("初始化ComplexObject...");
for (int i = 0; i < 1000; i++) {
items.add("item-" + i);
properties.put("key-" + i, "value-" + i);
}
nested.initialize();
}
// 使用序列化实现克隆
public ComplexObject clone() {
return DeepCopyUtil.deepCopy(this);
}
// 内部嵌套类
public static class NestedObject implements Serializable {
private static final long serialVersionUID = 1L;
private List<Integer> numbers;
public void initialize() {
numbers = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
numbers.add(i);
}
}
}
// getter方法
public String getName() { return name; }
public List<String> getItems() { return items; }
public Map<String, Object> getProperties() { return properties; }
}
// 测试序列化深拷贝
public class SerializationCopyTest {
public static void main(String[] args) {
ComplexObject original = new ComplexObject("原始对象");
long startTime = System.currentTimeMillis();
ComplexObject copy = original.clone();
long endTime = System.currentTimeMillis();
System.out.println("深拷贝耗时: " + (endTime - startTime) + "ms");
System.out.println("原始对象items大小: " + original.getItems().size());
System.out.println("拷贝对象items大小: " + copy.getItems().size());
System.out.println("是否是同一个对象: " + (original == copy));
System.out.println("items是否是同一个: " + (original.getItems() == copy.getItems()));
}
}
6.2 原型模式与对象池结合
java
// 带对象池的原型管理器
public class PooledPrototypeManager<T extends Prototype<T>> {
private final T prototype;
private final Queue<T> objectPool;
private final int maxPoolSize;
public PooledPrototypeManager(T prototype, int maxPoolSize) {
this.prototype = prototype;
this.maxPoolSize = maxPoolSize;
this.objectPool = new LinkedList<>();
initializePool();
}
private void initializePool() {
for (int i = 0; i < maxPoolSize / 2; i++) {
objectPool.offer(prototype.clone());
}
}
public T getObject() {
T object = objectPool.poll();
if (object == null) {
// 池中无可用对象,创建新的
object = prototype.clone();
}
return object;
}
public void returnObject(T object) {
if (objectPool.size() < maxPoolSize) {
// 重置对象状态
resetObject(object);
objectPool.offer(object);
}
// 如果池已满,则让对象被GC回收
}
protected void resetObject(T object) {
// 子类可以重写此方法来重置对象状态
}
public int getPoolSize() {
return objectPool.size();
}
}
// 使用示例
public class DatabaseConnection implements Prototype<DatabaseConnection> {
private String connectionId;
private boolean inUse;
public DatabaseConnection() {
this.connectionId = "Connection-" + UUID.randomUUID();
initializeConnection();
}
private DatabaseConnection(DatabaseConnection source) {
this.connectionId = "Connection-" + UUID.randomUUID();
// 克隆时跳过复杂的初始化
this.inUse = false;
}
private void initializeConnection() {
System.out.println("初始化数据库连接: " + connectionId);
// 模拟耗时的连接初始化
try { Thread.sleep(200); } catch (InterruptedException e) {}
this.inUse = false;
}
@Override
public DatabaseConnection clone() {
return new DatabaseConnection(this);
}
public void connect() {
this.inUse = true;
System.out.println("使用连接: " + connectionId);
}
public void disconnect() {
this.inUse = false;
System.out.println("断开连接: " + connectionId);
}
public String getConnectionId() {
return connectionId;
}
}
// 测试带池的原型管理器
public class PooledPrototypeTest {
public static void main(String[] args) {
DatabaseConnection prototype = new DatabaseConnection();
PooledPrototypeManager<DatabaseConnection> pool =
new PooledPrototypeManager<>(prototype, 3);
// 获取连接
DatabaseConnection conn1 = pool.getObject();
DatabaseConnection conn2 = pool.getObject();
DatabaseConnection conn3 = pool.getObject();
conn1.connect();
conn2.connect();
// 使用后归还
conn1.disconnect();
pool.returnObject(conn1);
conn2.disconnect();
pool.returnObject(conn2);
// 再次获取,应该从池中获取而不是创建新的
DatabaseConnection conn4 = pool.getObject();
DatabaseConnection conn5 = pool.getObject();
System.out.println("当前池大小: " + pool.getPoolSize());
System.out.println("conn4 ID: " + conn4.getConnectionId());
System.out.println("conn5 ID: " + conn5.getConnectionId());
}
}
7. 原型模式 vs 其他模式
7.1 原型模式 vs 工厂模式
- 原型模式:通过复制现有对象创建新对象
- 工厂模式:通过调用构造器创建新对象
7.2 原型模式 vs 单例模式
- 原型模式:每次创建新实例
- 单例模式:确保只有一个实例
7.3 原型模式 vs 建造者模式
- 原型模式:基于现有对象复制创建
- 建造者模式:通过步骤构建复杂对象
8. 总结与思考
8.1 原型模式的优点
- 性能提升:避免昂贵的初始化操作
- 简化创建:隐藏对象创建的复杂性
- 动态配置:运行时动态改变原型
- 减少子类:通过复制改变对象,而不是继承
8.2 原型模式的缺点
- 深拷贝复杂性:需要正确处理引用对象的拷贝
- 循环引用问题:对象间循环引用可能导致问题
- 内存开销:原型对象本身占用内存
- 克隆方法实现复杂:复杂对象的克隆实现可能很复杂
8.3 设计思考
原型模式的本质是**"通过复制来创建"**。它利用了对象复制的特性来避免重复的初始化成本,特别适合创建成本高的对象。
深入思考的角度:
"原型模式的核心价值在于它提供了一种基于现有对象创建新对象的方式。这种方式避免了重复执行昂贵的初始化操作,同时保持了对象的灵活性。它是在创建成本和使用灵活性之间找到的平衡点。"
在实际应用中,原型模式有很多需要注意的地方:
- Java的Cloneable接口的设计缺陷
- 深拷贝与浅拷贝的选择
- 循环引用的处理
- 与序列化的结合使用
最佳实践建议:
- 对于创建成本高的对象,考虑使用原型模式
- 仔细考虑深拷贝和浅拷贝的选择
- 使用拷贝构造器而不是Cloneable接口
- 考虑使用序列化来实现深拷贝
- 为原型对象提供重置状态的方法
使用场景判断:
- 适合:对象创建成本高,需要创建多个相似对象,对象状态变化频繁
- 不适合:对象结构简单,创建成本低,对象间差异很大
下一篇预告:设计模式手册008 - 适配器模式:如何让不兼容的接口能够协同工作?
版权声明:本文为CSDN博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。