一, 简介
当系统中需要大量创建相同或者相似的对象时,就可以通过“原型模式”来实现。原型模式也是“创建型设计模式”中的一种,五种创建型设计模式到此就介绍完毕了,需要了解前四种的可以看看本人前面的文章。
原型模式的核心思想是,通过拷贝指定的“原型实例(对象)”,创建跟该对象一样的新对象。简单理解就是“克隆指定对象”。
这里提到的“原型实例(对象)”,就是被克隆的对象,它的作用就是指定要创建的对象种类。
二,实现
所有的原型模式都是实现CloneAble接口,这接口是一个标记接口,里面没有任何内容,作用就是用于标识实现该接口 的启用了原型模式。只有当实现了CloneAble接口以后才有重写Object类clone方法的权限,否则会报CloneNotSupportedException异常。
三,浅拷贝和深拷贝
原型模式有两种,一种是浅拷贝另一种是深拷贝。
浅拷贝:当类的成员变量是基本数据类型时,原对象的值会直接赋给新的对象,当成员变量为引用数据类型时,克隆对象会引用原有的引用地址,当原对象引用地址的变量被修改时,克隆对象中的变量也会跟着改变。
深拷贝:深拷贝则是重新开拓出一块新的内存地址,原对象的引用地址的变量发生改变时对克隆对象无影响。其余跟浅拷贝一致。
四,实现
浅拷贝实现:
package clone;
/**
* @author: chenwj
* @description:
* @create: 2021-02-18 17:26
**/
public class Person implements Cloneable{
String name;
Integer age;
Integer stature;
Skills skills;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Integer getStature() {
return stature;
}
public void setStature(Integer stature) {
this.stature = stature;
}
public Skills getSkills() {
return skills;
}
public void setSkills(Skills skills) {
this.skills = skills;
}
public Person(String name, Integer age, Integer stature,Skills skills) {
this.name = name;
this.age = age;
this.stature = stature;
this.skills = skills;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
class Main{
public static void main(String[] args) throws CloneNotSupportedException {
Skills skills = new Skills();
skills.setEat("eat");
skills.setSleep("1ms");
Person person = new Person("vangi",25,180,skills);
Person person1 = (Person) person.clone();
System.out.println(person.getName()+"-"+person.getAge()+"-"+person.getStature()+"-"+person.getSkills().getSleep());
System.out.println(person1.getName()+"-"+person1.getAge()+"-"+person1.getStature()+"-"+person.getSkills().getSleep());
skills.setSleep("2ms");
System.out.println("原对象:"+person.getName()+"-"+person.getAge()+"-"+person.getStature()+"-"+person.getSkills().getSleep());
System.out.println("克隆对象"+person1.getName()+"-"+person1.getAge()+"-"+person1.getStature()+"-"+person.getSkills().getSleep());
}
}
}
这里我将skill中的字段sleep的值进行了修改,可以发现原对象个克隆对象都发生了改变。
要注意的是String是通过常量赋值,相当于基本数据类型,当用new时则为引用数据类型。
深拷贝实现:
深拷贝实现由两种方式:一是自行开辟出一块内存地址,并把克隆的对象赋给它,二是通过序列化的方式实现(推荐)。
首先说第一种方式:
我们先来建一个引用类型对象Skills:
package clone;
/**
* @author: chenwj
* @description:
* @create: 2021-02-18 17:56
**/
public class Skills implements Cloneable{
String eat;
String sleep;
public String getEat() {
return eat;
}
public void setEat(String eat) {
this.eat = eat;
}
public String getSleep() {
return sleep;
}
public void setSleep(String sleep) {
this.sleep = sleep;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
Person类:
package clone;
/**
* @author: chenwj
* @description:
* @create: 2021-02-18 17:26
**/
public class Person implements Cloneable{
String name;
Integer age;
Integer stature;
Skills skills;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Integer getStature() {
return stature;
}
public void setStature(Integer stature) {
this.stature = stature;
}
public Skills getSkills() {
return skills;
}
public void setSkills(Skills skills) {
this.skills = skills;
}
public Person(String name, Integer age, Integer stature,Skills skills) {
this.name = name;
this.age = age;
this.stature = stature;
this.skills = skills;
}
@Override
protected Object clone() throws CloneNotSupportedException {
Person person = null;
person = (Person) super.clone();
person.skills = (Skills) this.skills.clone();
return person;
}
}
class Main{
public static void main(String[] args) throws CloneNotSupportedException {
Skills skills = new Skills();
skills.setEat("eat");
skills.setSleep("1ms");
Person person = new Person("vangi",25,180,skills);
Person person1 = (Person) person.clone();
System.out.println("原对象:"+person.getName()+"-"+person.getAge()+"-"+person.getStature()+"-"+person.getSkills().getSleep());
System.out.println("克隆对象:"+person1.getName()+"-"+person1.getAge()+"-"+person1.getStature()+"-"+person1.getSkills().getSleep());
skills.setSleep("2ms");
System.out.println("修改后原对象:"+person.getName()+"-"+person.getAge()+"-"+person.getStature()+"-"+person.getSkills().getSleep());
System.out.println("修改后克隆对象:"+person1.getName()+"-"+person1.getAge()+"-"+person1.getStature()+"-"+person1.getSkills().getSleep());
}
}
此时由于重新给克隆对象分配了一块内存,所以克隆对象的值并没有发生改变。
这种方法的缺陷是后续所有的引用数据类型都需要在clone方法中单独处理,违反了开闭原则。
第二种方式,通过序列化
public Person deepClone(){
ObjectInputStream ois = null;
ObjectOutputStream oos = null;
ByteArrayInputStream bis = null;
ByteArrayOutputStream bos = null;
try {
//创建序列化流
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
//以对象流方式输出
oos.writeObject(this);
bis = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bis);
return (Person) ois.readObject();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
} finally {
try {
ois.close();
oos.close();
bis.close();
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
这样就不用每次在clone方法中对引用数据类型做处理了。
需要注意的是使用序列化时记得给每个类实现Serializable接口,不然会报NotSerializableException异常。
总结:
1、通过原型模式可以简化创建重量级对象的过程,并提高程序的效率。
2、原型设计模式是动态获取对象运行时的状态进行创建对象的。
3、使用原型设计模式可以使代码变的更加灵活,因为当原型类发生变化(增、减属性)时,克隆的对象也会做出相应的改变。
4、对已经创建好的类进行改造,使其支持克隆时需要修改源代码,这就是违背了ocp(开闭)原则。
最后,如果觉得该篇文章对你有用,麻烦请扫描关注一下我的公众号。谢谢!