# 原型模式
# 概念
原型模式( Prototype Pattern )是一种创建型设计模式,核心思想是通过复制现有对象(原型)来创建新对象,而无需通过 new
关键字和构造方法。该模式允许你创建对象的副本,同时保持代码与具体对象类的解耦。
# 作用
避免重复初始化开销:当对象创建过程复杂(如涉及数据库查询或复杂计算)时,复制已有对象比重新创建更高效。
简化对象创建过程:客户端无需知道对象创建细节,只需通过原型复制即可获得新对象。
动态配置对象类型:运行时通过改变原型对象来实例化新对象类型(如从对象池中选择原型)。
保护性拷贝:提供对象拷贝的安全机制,防止外部修改影响原始对象(深拷贝场景)。
# 场景
对象初始化成本高昂:需要避免重复执行耗时的初始化操作(如读取配置文件、数据库连接)。
对象状态变化频繁:需要基于当前状态快速创建相似对象(如游戏中的敌人复制)。
需要隔离对象副本:确保新对象与原型独立,修改互不影响(深拷贝场景)。
组合对象创建:复杂结构对象(如树形结构)的快速复制。
# 举例
假设我们有一个简历创建系统,用户可以创建自己的简历,并且希望能够快速地复制一份简历作为基础,然后进行修改。这里就可以使用原型模式:
package net.feixiang.creational.prototype;
/**
* 工作经历类
* 该类用于存储工作经历信息,包括公司名称和工作年限。它可以被 Resume 类使用。
*/
public class WorkExperience {
private String company; // 公司名称
private int workYears; // 工作年限
// 构造方法
public WorkExperience(String company, int workYears) {
this.company = company;
this.workYears = workYears;
}
// getter 和 setter 方法省略……
@Override
public String toString() {
return "WorkExperience{" +
"company='" + company + '\'' +
", workYears=" + workYears +
'}';
}
}
package net.feixiang.creational.prototype;
/**
* 简历类
* 包含姓名、性别、年龄和工作经历。实现了 Cloneable 接口以支持深拷贝。
*/
public class Resume implements Cloneable {
private String name; // 姓名
private String gender; // 性别
private int age; // 年龄
private WorkExperience workExperience; // 工作经历
// 构造方法
public Resume(String name, String gender, int age,
WorkExperience workExperience) {
this.name = name;
this.gender = gender;
this.age = age;
this.workExperience = workExperience;
}
// 实现 clone 方法,深拷贝
@Override
protected Resume clone() {
// 深拷贝:复制工作经历对象
WorkExperience clonedWorkExperience = new WorkExperience(
this.workExperience.getCompany(),
this.workExperience.getWorkYears());
return new Resume(this.name, this.gender, this.age, clonedWorkExperience);
}
// getter 和 setter 方法省略……
@Override
public String toString() {
return "Resume{" +
"name='" + name + '\'' +
", gender='" + gender + '\'' +
", age=" + age +
", \nworkExperience=" + workExperience +
'}';
}
}
运行示例:
package net.feixiang.creational.prototype;
/**
* 原型模式演示类
* 该类演示了如何使用原型模式来复制简历对象。通过克隆原始简历对象,可以创建一个
* 新的简历对象,并修改其属性。
*/
public class PrototypeDemo {
public static void main(String[] args) {
// 创建原始简历对象
WorkExperience workExperience = new WorkExperience("飞翔软件公司", 3);
Resume originalResume = new Resume("飞翔", "男", 28, workExperience);
System.out.println("原始简历:" + originalResume);
// 复制简历对象,这里是直接使用已存在的对象,而不是新建对象
Resume clonedResume = originalResume.clone();
clonedResume.setName("翱翔");
clonedResume.getWorkExperience().setCompany("翱翔设计公司");
System.out.println("复制简历:" + clonedResume);
}
}
控制台输出:
原始简历:Resume{name='飞翔', gender='男', age=28,
workExperience=WorkExperience{company='飞翔软件公司', workYears=3}}
复制简历:Resume{name='翱翔', gender='男', age=28,
workExperience=WorkExperience{company='翱翔设计公司', workYears=3}}
# 反例
如果不使用原型模式,直接通过 new
关键字创建对象,可能会存在以下问题:
重复初始化问题
如果对象的创建过程比较复杂,如需要进行大量的计算或数据访问等,那么每次创建新都需要对象重复这些初始化操作,导致系统性能下降。
无法灵活创建对象
无法根据现有对象的状态快速创建新对象,需要重新指定所有属性的值,不够灵活。
代码可维护性差
如果对象的结构发生变化,如增加新的属性或修改属性的类型等,那么所有创建该对象的地方都需要进行相应的修改,增加了代码的维护成本。
例如,在上述简历创建系统的场景中,如果不使用原型模式,每次创建新简历都需要重新输入姓名、性别、年龄以及工作经历等信息,无法快速地基于已有简历进行修改。而且,如果简历的结构发生变化,如增加教育背景等属性,那么所有创建简历的地方都需要修改,增加了代码的维护难度。
以下是不使用原型模式,而是通过构造方法直接创建副本的完整代码实现,该实现会导致对象共享问题:
package net.feixiang.creational.prototype.contrary;
import net.feixiang.creational.prototype.WorkExperience;
/**
* 简历类
* 该类演示了一个反例实现,使用构造方法创建副本,但只实现了浅拷贝,
* 导致嵌套对象(工作经历)被共享。
*/
public class Resume {
private String name; // 姓名
private String gender; // 性别
private int age; // 年龄
private WorkExperience workExperience; // 工作经历
// 构造方法
public Resume(String name, String gender, int age,
WorkExperience workExperience) {
this.name = name;
this.gender = gender;
this.age = age;
this.workExperience = workExperience;
}
/**
* 通过构造方法创建副本(反例实现)
* 问题:只实现了浅拷贝,嵌套对象会被共享
*/
public Resume(Resume another) {
this.name = another.name;
this.gender = another.gender;
this.age = another.age;
// 错误:直接引用原始对象的工作经历
this.workExperience = another.workExperience;
}
public void updateWorkExperience(String company, int years) {
this.workExperience.setCompany(company);
this.workExperience.setWorkYears(years);
}
// getter 和 setter 方法省略……
@Override
public String toString() {
return "Resume{" +
"name='" + name + '\'' +
", gender='" + gender + '\'' +
", age=" + age +
", \nworkExperience=" + workExperience +
'}';
}
}
运行示例:
package net.feixiang.creational.prototype.contrary;
import net.feixiang.creational.prototype.WorkExperience;
/**
* 浅拷贝演示类
* 该类演示了一个反例实现,使用构造方法创建副本,但只实现了浅拷贝,
* 导致嵌套对象(工作经历)被共享。
*/
public class ShallowCopyDemo {
public static void main(String[] args) {
// 创建原始简历
WorkExperience originalExp = new WorkExperience("飞翔软件公司", 3);
Resume originalResume = new Resume("飞翔", "男", 28, originalExp);
// 通过构造方法创建副本(错误方式)
Resume copiedResume = new Resume(originalResume);
copiedResume.setName("翱翔");
System.out.println("\n=== 修改前状态 ===");
System.out.println("原始简历: " + originalResume);
System.out.println("副本简历: " + copiedResume);
// 修改副本的工作经历
copiedResume.updateWorkExperience("翱翔设计公司", 5);
System.out.println("\n=== 修改副本工作经历后 ===");
System.out.println("原始简历: " + originalResume); // 原始简历也被修改!
System.out.println("副本简历: " + copiedResume);
// 更危险的修改方式
originalResume.getWorkExperience().setCompany("百度");
System.out.println("\n=== 修改原始工作经历后 ===");
System.out.println("原始简历: " + originalResume);
System.out.println("副本简历: " + copiedResume); // 副本简历也被修改!
}
}
控制台输出:
=== 修改前状态 ===
原始简历: Resume{name='飞翔', gender='男', age=28,
workExperience=WorkExperience{company='飞翔软件公司', workYears=3}}
副本简历: Resume{name='翱翔', gender='男', age=28,
workExperience=WorkExperience{company='飞翔软件公司', workYears=3}}
=== 修改副本工作经历后 ===
原始简历: Resume{name='飞翔', gender='男', age=28,
workExperience=WorkExperience{company='翱翔设计公司', workYears=5}}
副本简历: Resume{name='翱翔', gender='男', age=28,
workExperience=WorkExperience{company='翱翔设计公司', workYears=5}}
=== 修改原始工作经历后 ===
原始简历: Resume{name='飞翔', gender='男', age=28,
workExperience=WorkExperience{company='百度', workYears=5}}
副本简历: Resume{name='翱翔', gender='男', age=28,
workExperience=WorkExperience{company='百度', workYears=5}}
# 解析
出现对象共享问题(浅拷贝陷阱):
根本原因:构造方法中直接复制了
WorkExperience
对象的引用。风险:原始对象和副本对象共享同一个工作经历对象。
后果:修改任意一个对象都会影响另一个对象,导致数据不一致。
# 原理
在Java中,使用原型模式时,通常需要满足以下条件:
实现
Cloneable
接口Cloneable
是一个标记接口,没有需要实现的方法。但它用于表明该类的对象可以被复制。如果一个类没有实现Cloneable
接口,但却调用了clone()
方法,会抛出CloneNotSupportedException
异常。重写
clone()
方法clone()
方法是Object
类的一个protected
方法。为了能够在外部分使用并定制复制逻辑,通常需要将其重写为public
方法,并在其中实现具体的复制逻辑。深拷贝与浅拷贝的考虑
如果对象包含其他对象的引用(即组合对象),则需要考虑是进行浅拷贝还是深拷贝:
浅拷贝: 只复制对象本身,不会复制其内部引用的对象,复制后的对象和原对象仍然引用相同的内部对象。
深拷贝: 不仅复制对象本身,还会复制其内部引用的所有对象,确保复制后的对象与原对象完全独立。
在实际应用中,需要根据具体需求选择合适的拷贝方式。
总结
原型模式核心是通过复制现有对象创建新对象,无需 new 和构造方法,使代码与具体类解耦。作用包括避免重复初始化开销、简化创建过程、动态配置对象类型、提供保护性拷贝。实现时,类要实现 Cloneable 接口,重写 clone 方法,根据需求选择深拷贝或浅拷贝。

微信公众号

QQ交流群
如若发现错误,诚心感谢反馈。
愿你倾心相念,愿你学有所成。
愿你朝华相顾,愿你前程似锦。