原型模式(创建型)
原型模式
介绍
定义:用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型对象相同的新对象。
简单理解,就是当需要创建一个指定的对象时,我们刚好有一个这样的对象,但是又不能直接使用,我会clone一个一模一样的新对象来使用,这就是原型模式。关键字:Clone。
原型模式分为“深拷贝”和“浅拷贝”。
深拷贝:创建一个新对象,对象的属性中引用的其他对象也会被克隆,不再指向原有对象地址;
浅拷贝:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性(引用类型),仍指向原有属性所指向的对象的内存地址。
原型模式包含以下角色:
- 抽象原型类: 规定了具体原型对象必须实现的 clone() 方法;
- 具体原型类:实现抽象原型类的 clone() 方法,它是可被复制的对象;
- 访问类:使用具体原型类中的 clone() 方法来复制新的对象;
Java中的 Object 类中提供了 clone() 方法来实现浅拷贝。 Cloneable 接口是抽象原型类,而实现了Cloneable接口的子实现类就是具体的原型类。
public class Prototype implements Cloneable { // 具体原型类
public Prototype() {
System.out.println("具体的原型对象创建完成");
}
@Override
protected Prototype clone() throws CloneNotSupportedException {
System.out.println("具体原型复制成功!");
return (Prototype) super.clone();
}
}
public class PrototypeTest { // 测试类
public static void main(String[] args) throws CloneNotSupportedException {
Prototype p1 = new Prototype();
Prototype p2 = p1.clone();
System.out.println("对象p1和p2是同一个对象?" + (p1 == p2)); // false
}
}
再举个例子:
现在正是秋招,投递简历会受到笔试的邮件,同一家公司发送的邮件内容除了名字不同,其他都相同,可以使用原型模式复制多个候选人邮件出来。
public class EmailPrototype implements Cloneable {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void show() {
System.out.println(name + "同学:您好!非常高兴邀请您参加本公司2023校园招聘在线考试,希望您能按时完成!");
}
@Override
protected EmailPrototype clone() throws CloneNotSupportedException {
return (EmailPrototype) super.clone();
}
// 测试类
static class EmailPrototypeTest {
public static void main(String[] args) throws CloneNotSupportedException {
EmailPrototype ep1 = new EmailPrototype();
ep1.setName("张三");
// 复制邮件
EmailPrototype ep2 = ep1.clone();
ep2.setName("李四");
ep1.show();
ep2.show();
}
}
}
// 运行结果:
张三同学:您好!非常高兴邀请您参加本公司2023校园招聘在线考试,希望您能按时完成!
李四同学:您好!非常高兴邀请您参加本公司2023校园招聘在线考试,希望您能按时完成!
注意点:如果具体原型类没有实现 Cloneable 接口,运行时会报异常。
使用场景:
- 如果对象的创建非常复杂,可以使用原型模式快捷的创建对象;
- 性能和安全要求比较高时;
(深浅拷贝)
将上面例子中属性 name 改成 Student 类型的属性,代码如下:
public class EmailPrototype2 implements Cloneable {
// 学生类
static class Student {
private String name;
private String university;
// 省略 get set 方法
public Student(String name, String university) {
this.name = name;
this.university = university;
}
}
private Student stu;
public EmailPrototype2(Student stu) {
this.stu = stu;
}
public Student getStu() {
return stu;
}
public void setStu(Student stu) {
this.stu = stu;
}
public void show() {
System.out.printf("来自%s的%s同学:您好!非常高兴邀请您参加本公司2023校园招聘在线考试,希望您能按时完成!%n", stu.getUniversity(), stu.getName());
}
@Override
protected EmailPrototype2 clone() throws CloneNotSupportedException {
return (EmailPrototype2) super.clone();
}
// 测试方法
static class EmailPrototype2Test {
public static void main(String[] args) throws CloneNotSupportedException {
Student stu1 = new Student("张三", "北京大学");
EmailPrototype2 e1 = new EmailPrototype2(stu1);
EmailPrototype2 e2 = e1.clone();
Student stu2 = e2.getStu();
stu2.setName("李四");
System.out.println("stu1和stu2是同一个对象?" + (stu2 == stu1));
e1.show();
e2.show();
}
}
}
// 运行结果
stu1和stu2是同一个对象?true
来自北京大学的李四同学:您好!非常高兴邀请您参加本公司2023校园招聘在线考试,希望您能按时完成!
来自北京大学的李四同学:您好!非常高兴邀请您参加本公司2023校园招聘在线考试,希望您能按时完成!
分析:e2引用对象是由e1拷贝来的,然后修改stu2中的name属性为李四,发现stu1也改了,说明stu1和stu2指向的是同一块堆内存(同一对象),这就是浅拷贝的效果。
但是这种场景下需要使用深拷贝,当前类和属性类都要实现 Cloneable 接口,代码如下:
public class EmailPrototype2 implements Cloneable {
// 学生类
static class Student implements Cloneable {
private String name;
private String university;
// 省略 get set 方法
public Student(String name, String university) {
this.name = name;
this.university = university;
}
@Override
protected Student clone() throws CloneNotSupportedException {
return (Student) super.clone();
}
}
private Student stu;
public EmailPrototype2(Student stu) {
this.stu = stu;
}
public Student getStu() {
return stu;
}
public void setStu(Student stu) {
this.stu = stu;
}
void show() {
System.out.printf("来自%s的%s同学:您好!非常高兴邀请您参加本公司2023校园招聘在线考试,希望您能按时完成!%n", stu.getUniversity(), stu.getName());
}
@Override
protected EmailPrototype2 clone() throws CloneNotSupportedException {
EmailPrototype2 ep = (EmailPrototype2) super.clone();
ep.setStu(ep.getStu().clone()); // 实现深拷贝这句很关键
return ep;
}
static class EmailPrototype2Test {
public static void main(String[] args) throws CloneNotSupportedException {
Student stu1 = new Student("张三", "北京大学");
EmailPrototype2 e1 = new EmailPrototype2(stu1);
EmailPrototype2 e2 = e1.clone();
Student stu2 = e2.getStu();
stu2.setName("李四");
System.out.println("stu1和stu2是同一个对象?" + (stu2 == stu1));
e1.show();
e2.show();
}
}
}
// 运行结果:
stu1和stu2是同一个对象?false
来自北京大学的张三同学:您好!非常高兴邀请您参加本公司2023校园招聘在线考试,希望您能按时完成!
来自北京大学的李四同学:您好!非常高兴邀请您参加本公司2023校园招聘在线考试,希望您能按时完成!
改动的地方有以下几点:
- 属性类Student 也要实现 Cloneable 接口,并重写 clone() 方法;
- 邮件类 EmailPrototype2 中重写的 clone() 方法中,要对 Student 类型的属性进行克隆;
总结
原型模式的本质就是clone,可以解决构建复杂对象的资源消耗问题,能在某些场景中提升构建对象的效率。