原型模式
原型模式,用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
结构图
Prototype具备下面两个条件:
1.实现Cloneable接口,在Java语言有一个Cloneable接口,它的作用就是在运行时通知虚拟机可以安全地在实现了此接口的类上使用clone方法。在Java虚拟机中,只有实现了此接口的类才可以被拷贝,否则在运行时出现CloneNotSupportedException异常。
2.重写Object类中的clone方法。Java中所有类的父类都是Object类,Object类中有一个方法,作用是返回对象的拷贝,但是其作用域为protected,一般的类无法调用,因此,Prototype类需要将clone方法作用域改为public。
代码实现
‘Prototype类:
/**
* 原型类
* @author xukai
* 2016年3月9日 下午4:19:47
*/
public abstract class Prototype implements Cloneable {
public Prototype clone(){
Prototype prototype = null;
try {
prototype = (Prototype) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return prototype;
}
}
ConcretePrototype类:
/**
* 具体原型类
* @author xukai
* 2016年3月9日 下午4:22:31
*/
public class ConcretePrototype extends Prototype {
public void show(){
System.out.println("原型模式实现类");
}
@Override
public Prototype clone() {
return super.clone();
}
}
客户端:
public class Client {
public static void main(String[] args) {
ConcretePrototype prototype = new ConcretePrototype();
for(int i = 0; i < 10; i++){
ConcretePrototype clonecp = (ConcretePrototype) prototype.clone();
clonecp.show();
System.out.println(clonecp);
}
}
}
控制台输出:
可以看出,克隆出了不同的对象。
Demo(浅复制)
问题:实现简历的复制。代码实现
简历类Resume:
/**
* 简历
* @author xukai
* 2016年3月9日 下午5:34:29
*/
public class Resume implements Cloneable {
private String name;
private String sex;
private String age;
private String timeArea;
private String company;
public Resume(String name) {
super();
this.name = name;
}
public void setPersonInfo(String sex, String age){
this.sex = sex;
this.age = age;
}
public void setWorkExperience(String timeArea, String company){
this.timeArea = timeArea;
this.company = company;
}
public void dispaly(){
System.out.println(this.toString());
}
@Override
public String toString() {
return "Resume [name=" + name + ", sex=" + sex + ", age=" + age + ", timeArea=" + timeArea + ", company="
+ company + "]";
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
客户端测试:
public class Client {
public static void main(String[] args) {
Resume resume1 = new Resume("老王");
resume1.setPersonInfo("男", "25");
resume1.setWorkExperience("2015-2016", "服务公司");
resume1.dispaly();
try {
Resume resume2 = (Resume) resume1.clone();
resume2.setWorkExperience("2014-2015", "公关公司");
resume2.dispaly();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
try {
Resume resume3 = (Resume) resume1.clone();
resume3.setWorkExperience("2013-2014", "娱乐公司");
resume3.dispaly();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
控制台输出:
*注:一般在初始化的信息不发生变化的情况下,克隆是最好的办法。既隐藏了对象创建的细节,又对性能是大大的提高。
Demo深复制
问题:实现简历的复制,简历中存在工作经验类,实现深复制。
工作经验类:
/**
* 工作经验类
* @author xukai
* 2016年3月13日 下午7:09:15
*/
public class WorkExperience implements Cloneable {
private String workDate;
private String company;
public String getWorkDate() {
return workDate;
}
public void setWorkDate(String workDate) {
this.workDate = workDate;
}
public String getCompany() {
return company;
}
public void setCompany(String company) {
this.company = company;
}
@Override
public String toString() {
return "WorkExperience [workDate=" + workDate + ", company=" + company + "]";
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
简历类:
public class Resume implements Cloneable{
private String name;
private String sex;
private String age;
private WorkExperience work;
public Resume(String name) {
this.name = name;
work = new WorkExperience();
}
public void setPersonInfo(String sex,String age){
this.sex = sex;
this.age = age;
}
public void setWorkExperience(String workDate,String company){
work.setWorkDate(workDate);
work.setCompany(company);
}
public void display(){
System.out.println(this.toString() + name + "," + sex + "," + age + "," + work);
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
测试:
public class PrototypeTest {
public static void main(String[] args) {
Resume a = new Resume("姓名1");
a.setPersonInfo("男", "18");
a.setWorkExperience("2011-01-01", "公司1");
a.display();
try {
Resume b = (Resume) a.clone();
b.setWorkExperience("2012-01-01", "公司2");
b.display();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
a.display();
}
}
控制台输出:
com.xk.day1230.prototype.Resume@4402083d姓名1,男,18,WorkExperience [workDate=2011-01-01, company=公司1]
com.xk.day1230.prototype.Resume@616affac姓名1,男,18,WorkExperience [workDate=2012-01-01, company=公司2]
com.xk.day1230.prototype.Resume@4402083d姓名1,男,18,WorkExperience [workDate=2012-01-01, company=公司2]
可以看到引用对象b和引用对象a的成员类WorkExperience是同一个对象,b对象的改变了,a对象也改变了。
clone方法复制的时候,如果字段是值类型的,则对字段执行逐位复制,如果字段是引用类型,则复制引用但不复制引用的对象;因此原始对象及其复本引用同一对象。(工作经验类WorkExperience并没有重新复制一份)
正确的复制简历类:
正确的复制简历类:
public class Resume implements Cloneable{
private String name;
private String sex;
private String age;
private WorkExperience work;
public Resume(String name) {
this.name = name;
work = new WorkExperience();
}
private Resume(WorkExperience work) {
try {
this.work = (WorkExperience) work.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
public void setPersonInfo(String sex,String age){
this.sex = sex;
this.age = age;
}
public void setWorkExperience(String workDate,String company){
work.setWorkDate(workDate);
work.setCompany(company);
}
public void display(){
System.out.println(this.toString() + name + "," + sex + "," + age + "," + work);
}
@Override
protected Object clone() throws CloneNotSupportedException {
Resume obj = new Resume(this.work);
obj.name = this.name;
obj.age = this.age;
obj.sex = this.sex;
return obj;
}
}
控制台输出:
com.xk.day1230.prototype.Resume@616affac姓名1,男,18,WorkExperience [workDate=2011-01-01, company=公司1]
com.xk.day1230.prototype.Resume@37b7a72b姓名1,男,18,WorkExperience [workDate=2022-01-01, company=公司2]
com.xk.day1230.prototype.Resume@616affac姓名1,男,18,WorkExperience [workDate=2011-01-01, company=公司1]
可以看到是两个不同的工作经验对象。
深复制VS浅复制
浅复制:被复制对象的所有变量都含有与原来的对象相同的值,而所有的其他对象的引用都依然指向原来的对象。深复制:把引用对象的变量指向复制过的新对象,而不是原有的被引用的对象。
*浅复制不复制引用对象
*深复制重新复制引用对象
*深复制重新复制引用对象
原型模式的优点以及适用场景
使用原型模式创建对象比直接new一个对象在性能上好的多,因为Object类的clone方法是一个本地方法,它直接操作内存中的二进制流,特别是复制大对象时,性能的差别非常明显。注意事项
1.使用原型模式复制对象不会调用类的构造方法。直接在内存中复制数据。并且构造方法的访问权限也对原型模式无效,如单例模式中的私有构造方法,对colne方法无效。使用时一定要注意。
2.深复制和浅复制。Object类的clone类方法只会拷贝对象中的基本的数据类型,对于数组、容器对象、引用对象等都不会复制,此为浅复制。需要实现深复制,必须将原型模式中的数组、容器对象、引用对象另外复制。
public class Prototype implements Cloneable {
private ArrayList<?> list = new ArrayList<>();
@Override
protected Prototype clone() {
Prototype prototype = null;
try {
prototype = (Prototype) super.clone();
prototype.list = (ArrayList<?>) this.list.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return prototype;
}
}
*深复制和浅复制问题,会发生深复制的有Java中的8中基本类型以及他们的封装类型,另外还有String类。其余都是浅拷贝,需要手动拷贝。