【设计模式】原型模式(只能说很少用)
针对原型模式(我有把这个模式命名为克隆模式)的定义(设计意图)
- 原型实例指定创建对象的种类,并通过拷贝这些原型来创建新的对象。(所以核心是:拷贝原型对象)
- 直接基于内存进行拷贝,而不需要再一次进行对象的初始化操作
当然
- Java中的对象复制/克隆分为浅复制和深克隆。在Java语言中,数据类型分为值类型(基本数据类型)和引用类型,浅克隆和深克隆的主要区别在于是否支持引用类型的成员变量的复制
应用实例
浅克隆
在浅克隆中,如果原型对象的成员变量是值类型,将复制一份给克隆对象;如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址。简单来说,在浅克隆中,当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制,还是使用的原型对象的成员变量。
public class Student{
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Teacher源码
public class Teacher implements Cloneable{
private String name;
private int salary;
private Student student;
public Teacher clone() {
Teacher teacher=null;
try {
teacher=(Teacher)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return teacher;
}
public Student getStudent() {
return Student;
}
public void setStudent(Student student) {
this.student= student;
}
public String getName() {
return Name;
}
public void setName(String name) {
this.name = name;
}
public int getSalary() {
return salary;
}
public void setSalary(int salary) {
this.salary= salary;
}
}
public class Client {
public static void main(String[] args){
Student student=new Student();
student.setName("李四");
Teacher teacher=new Teacher();
teacher.setName("李老师");
teacher.setSalary(3000);
teacher.setStudent(student);
Teacher teacher2=teacher.clone();
System.out.println(teacher==teacher2); // false
System.out.println(teacher.getName() == teacher2.getName()); // true
System.out.println(teacher.getStudent() == teacher2.getStudent()); // true
}
}
虽然复制出来的对象重新在堆上开辟了内存空间,但是,对象中各属性确保持相等。对于基本数据类型很好理解,但对于引用数据类型来说,则意味着此引用类型的属性所指向的对象本身是相同的, 并没有重新开辟内存空间存储。换句话说,引用类型的属性所指向的对象并没有复制。由此,我们将其称之为浅复制。当复制后的对象的引用类型的属性所指向的对象也重新得以复制,此时,称之为深复制。
深克隆
在深克隆中,除了对象本身被复制外,对象所包含的所有成员变量也将复制。
在Java语言中,如果需要实现深克隆,可以通过序列化(Serialization)等方式来实现。序列化就是将对象写到流的过程,写到流中的对象是原有对象的一个拷贝,而原对象仍然存在于内存中。通过序列化实现的拷贝不仅可以复制对象本身,而且可以复制其引用的成员对象,因此通过序列化将对象写到一个流中,再从流里将其读出来,可以实现深克隆。需要注意的是能够实现序列化的对象其类必须实现Serializable接口,否则无法实现序列化操作。
public class Student implements Serializable{
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Teacher源代码(需要实现Serializable)
public class Teacher implements Cloneable implements Serializable{
private String name;
private int salary;
private Student student;
public Book deepClone() throws IOException, ClassNotFoundException {
// 写入当前对象的二进制流
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
// 读出二进制流产生的新对象
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (Book) ois.readObject();
}
public Student getStudent() {
return Student;
}
public void setStudent(Student student) {
this.student= student;
}
public String getName() {
return Name;
}
public void setName(String name) {
this.name = name;
}
public int getSalary() {
return salary;
}
public void setSalary(int salary) {
this.salary= salary;
}
}
public class DeepClient {
public static void main(String[] args){
Student student=new Student();
student.setName("李四");
Teacher teacher=new Teacher();
teacher.setName("李老师");
teacher.setSalary(3000);
teacher.setStudent(student);
Teacher teacher2=teacher.deepClone();
System.out.println(teacher==teacher2); // false
System.out.println(teacher.getName() == teacher2.getName()); // true
System.out.println(teacher.getStudent() == teacher2.getStudent()); // true
}
}
深复制不仅在堆内存上开辟了空间以存储复制出的对象,甚至连对象中的引用类型的属性所指向的对象也得以复制,重新开辟了堆空间存储。
什么时候可以使用这个原型模式
- 当直接创建对象的代价比较大的时候,就可以试着采用这种模式。例:当一个对象需要在一个比较高代价的数据库操作之后被创建(需要很多其他对象的数据准备或其他资源的繁琐计算),那么我们可以缓存这个对象,下一个请求来的时候,就可以返回它的克隆,在需要的时候更新数据库,以此来减少数据库的调用
2.如果系统要保存对象的状态,而对象的状态变化很小,或者对象本身占用内存较少时,可以使用原型模式配合备忘录模式来实现。
3.需要避免使用分层次的工厂类来创建分层次的对象,并且类的实例对象只有一个或很少的几个组合状态,通过复制原型对象得到新实例可能比使用构造函数创建一个新实例更加方便。
4.在需要一个类的大量对象的时候,使用原型模式是最佳选择,因为原型模式是在内存中对这个对象进行拷贝,要比直接new这个对象性能要好很多,在这种情况下,需要的对象越多,原型模式体现出的优点越明显。
优缺点
1.优点
- 当创建新的对象比较复杂的时候,就可以使用原型对象来简化对象的创建过程,并且可以通过一个已有实例可以提高新实例的创建效率
- 扩展性较好,由于在原型模式中提供了抽象原型类,在客户端可以针对抽象原型类进行编程,而将具体原型类写在配置文件中,增加或减少产品类对原有系统都没有任何影响。
- 原型模式提供了简化的创建结构,工厂方法模式常常需要有一个与产品类等级结构相同的工厂等级结构,而原型模式就不需要这样,原型模式中产品的复制是通过封装在原型类中的克隆方法实现的,无须专门的工厂类来创建产品。
- 可以使用深克隆的方式保存对象的状态,使用原型模式将对象复制一份并将其状态保存起来,以便在需要的时候使用(如恢复到某一历史状态),可辅助实现撤销操作。
2.缺点
- 由于克隆方法在方法体内部,所以每次进行修改都需要修改源代码,所以违背了“开闭原则”
- 在实现深克隆时需要编写较为复杂的代码,而且当对象之间存在多重的嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来可能会比较麻烦。
在使用时需要注意什么
- 通过克隆方法所创建的对象是一个全新的对象,它在内存中是拥有新的地址,通常来说通过克隆所产生的对象进行修改对原型对象并不会产生任何影响,所以每个克隆对象都是相互独立的,只是通过不同的方式修改来得到一系列相似但不完全相同的对象(可以理解为就是创建了一个新的对象,但这个对象是以原型对象为基础进行创建的)
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek “源神”启动!「GitHub 热点速览」
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 我与微信审核的“相爱相杀”看个人小程序副业
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· spring官宣接入deepseek,真的太香了~