在有些系统中,存在大量相同或者类似对象的创建问题,如果用传统的构造函数来创建对象,会比较复杂而且耗时耗资源,用原型模式生成对象就很高效。
定义:用一个已经创建的实例(对象)作为原型,通过复制该原型对象来创建一个和原型对象相同的新对象(除了内存地址和原型不一样,其余都一样)。
结构: 由于Java提供了对象的clone()方法,所以用Java实现原型模式很简单。
抽象原型类:规定了具体原型对象必须实现的clone()方法。
具体原型类:实现抽象原型类的clone()方法,它是可被复制的对象。
访问类:使用具体原型类中的clone()方法来复制新的对象。(克隆出来的是具体原型类的对象)
实现:
原型模型的克隆分为浅克隆和深克隆。
原型模式指的是浅克隆。
浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性(比如,引用类型),仍指向原有属性所指向的对象的内存地址。
深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不会再指向原有对象地址。
Java中的Object类中提供了clone()方法来实现浅克隆。Cloneable接口就是原型模式中的抽象原型类角色,而实现了Cloneable接口的子实现类就是具体原型类。
在展现代码前,我们要注意以下事项、弄清楚以下问题:
需要注意的是,clone()方法原始访问权限修饰符是protect,但在访问类中要使用clone()方法。如果不提升方法的作用域,访问类就不能使用clone()方法,所以方法的访问修饰符应改为public。
我们要弄清楚的是,原型模式克隆对象的方法是不是通过再new一个具体原型类对象来完成的?为了检验这一点,我们在具体实现类的构造方法中打印一句话“原型对象创建完成!”。
如果是通过new具体原型对象来实现克隆,那么我们使用clone()方法时就会打印这句话。即,运行结果中会出现两次“原型对象创建完成!”(第一次是因为创建原型对象,第二次是因为创建克隆的对象)
如果不是通过new具体原型对象来实现克隆,运行结果中就只会出现一次“原型对象创建完成!”。
代码展示:
1.Realizetype(具体原型类),实现Cloneable接口,重写父类中的clone()方法。
CloneNotSupportedException异常:Object类中的clone()是protected的方法,在没有重写Object的clone()方法且没有实现Cloneable接口的实例上调用clone方法,会报CloneNotSupportedException异常。
public class Realizetype implements Cloneable{
//看一下构造方法是否被调用
public Realizetype() {
System.out.println("具体原型对象创建完成!");
}
//1,外面要调用,所以更改为public(原本是protect)
//2,克隆出来的是具体原型类的对象,所以进行了强制类型转换。
@Override
public Object clone() throws CloneNotSupportedException {
System.out.println("具体原型复制成功!");
return (Realizetype) super.clone();//直接调用父类的clone()方法
}
}
2.访问类(Client)
public class Client {
public static void main(String[] args) throws CloneNotSupportedException {
//创建一个原型类对象
Realizetype realizetype = new Realizetype();//调用无参构造方法
//调用原型类中的clone方法进行对象的克隆
Realizetype clone = (Realizetype) realizetype.clone();
System.out.println("原型对象和克隆出来的是否是同一个对象?"+(realizetype == clone));//false
}
}
3.运行结果
结果分析:通过运行结果出现一次“原型对象创建完成!”可以看出,复制的时候没有调用无参构造方法(方法体中的输出语句没有输出),所以super.clone()底层不是通过new对象来实现克隆的。最后的判断原型对象和克隆对象是否指向同一内存地址,结果是false,说明克隆成功。
下面我们来通过一个具体案例理解体会“原型模式”(浅克隆)
应用:用原型模式生成“三好学生”奖状。
分析:同一学校的“三好学生”奖状除了获奖人姓名不同,其他都相同,可以使用原型模式复制多个“三好学生”奖状出来,然后在修改奖状上的名字即可。
1.抽象原型类(Cloneable接口:java.lang包中的接口)
2.奖状类(Citation),包含成员变量name(学生姓名),对应的set和get方法,重写的父类中的抽象方法clone(),展示奖状的方法show()。
public class Citation implements Cloneable{
//三好学生的姓名
private String name;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
//为了效果比较明显
public void show(){
System.out.println(name + "同学,在2022年第一学期中表现优秀,被评为三好学生。特发此状!");
}
@Override
public Object clone() throws CloneNotSupportedException {
return (Citation) super.clone();
}
}
3.访问类(CitationTest)
public class CitationTest {
public static void main(String[] args) throws CloneNotSupportedException {
//创建原型对象
Citation citation = new Citation();
citation.setName("张三");
//克隆
Citation citation1 = (Citation) citation.clone();
citation1.setName("李四");
//调用show方法展示奖状
citation.show();
citation1.show();
}
}
4.运行结果
使用场景:
①对象的创建非常复杂,可以使用原型模式快捷的创建对象。(前提是原型对象必须实现Cloneable接口)
②性能和安全要求比较高。(不用new对象,而是按照自己的逻辑创建对象)
{{ cmt.username }}
{{ cmt.content }}
{{ cmt.commentDate | formatDate('YYYY.MM.DD hh:mm') }}