设计模式之原型模式(克隆模式)
定义:
通过复制现有的对象实例来创建对象实例
核心本质:
就是java的克隆技术,已某个对象为原型,复制出新的对象。显然,新的对象具备原型对象的特点。
原型模式实现方法:
(1) 实现Cloneable接口
Cloneable接口的作用是在运行时通知虚拟机可以安全地在实现了此接口的类上使用clone方法。在java虚拟机中,只有实现了这个接口的类才可以被拷贝,否则在运行时会抛出CloneNotSupportedException异常。
(2) 重写Object类的clone方法
Java中,所有类的父类都是Object类,Object类中有一个clone方法,作用是返回对象的一个拷贝,但是其作用域protected类型的,一般的类无法调用,因此,原型类需要将clone方法的作用域修改为public类型。
代码如下:
public class Sheep implements Cloneable, Serializable {
private String sname;
private Date birthday;
@Override
protected Object clone() throws CloneNotSupportedException {
Object clone = super.clone();
return clone;
}
public Sheep(String sname, Date birthday) {
super();
this.sname = sname;
this.birthday = birthday;
}
public Sheep() {
}
public String getSname() {
return sname;
}
public void setSname(String sname) {
this.sname = sname;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
@Override
public String toString() {
return "Sheep [sname=" + sname + ", birthday=" + birthday + "]";
}
}
public class Client {
public static void main(String[] args) throws CloneNotSupportedException{
Date date = new Date(12312321331L);
Sheep s1 = new Sheep("少利",date);
System.out.println(s1.toString());
Sheep clone = (Sheep) s1.clone();
System.out.println(clone.toString());
}
}
原型模式还可以分为两种:
(1)浅拷贝
(2)深拷贝
浅拷贝和深拷贝的图如下:
浅拷贝:
将一个对象复制后,基本数据类型的变量都会重新创建,而引用类型,指向的还是原对象所指向的(这样不安全)。
浅拷贝后的结果是对象的内存地址变化了(对象的引用发生了变化),可对象中包含的对象内存则没有变化
深拷贝:
将一个对象复制后,不论是基本数据类型还有引用类型,都是重新创建的。
Object类的clone方法只会拷贝对象中的8中基本数据类型,对于数组、日期、容器对象、引用对象等都不会拷贝,这就是浅拷贝。如果要实现深拷贝,必须将原型模式中的数组、容器对象、引用对象等另行拷贝。
深拷贝代码如下:
public class Sheep2 implements Cloneable, Serializable {
private String sname;
private Date birthday;
@Override
protected Object clone() throws CloneNotSupportedException {
Object clone = super.clone();
//添加如下代码实现深复制
Sheep2 s = (Sheep2) clone;
s.birthday = (Date) this.birthday.clone();
return clone;
}
public Sheep2(String sname, Date birthday) {
super();
this.sname = sname;
this.birthday = birthday;
}
public Sheep2() {
}
public String getSname() {
return sname;
}
public void setSname(String sname) {
this.sname = sname;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
@Override
public String toString() {
return "Sheep [sname=" + sname + ", birthday=" + birthday + "]";
}
}
public class Client2 {
public static void main(String[] args) throws CloneNotSupportedException{
Date date = new Date(12312321331L);
Sheep2 s1 = new Sheep2("少利",date);
Sheep2 s2 =(Sheep2) s1.clone();
System.out.println(s1);
s1.setSname("多利");
System.out.println(s1);
System.out.println(s2);
}
}
使用序列化和反序列化实现深克隆
代码如下:
public class Sheep implements Cloneable, Serializable {
private String sname;
private Date birthday;
@Override
protected Object clone() throws CloneNotSupportedException {
Object clone = super.clone();
return clone;
}
public Sheep(String sname, Date birthday) {
super();
this.sname = sname;
this.birthday = birthday;
}
public Sheep() {
}
public String getSname() {
return sname;
}
public void setSname(String sname) {
this.sname = sname;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
@Override
public String toString() {
return "Sheep [sname=" + sname + ", birthday=" + birthday + "]";
}
}
public class Client3 {
public static void main(String[] args) throws CloneNotSupportedException, Exception {
Date date = new Date(12312321331L);
Sheep s1 = new Sheep("少利",date);
System.out.println(s1);
System.out.println(s1.getSname());
System.out.println(s1.getBirthday());
//使用序列化和反序列化实现深复制
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(s1);
byte[] bytes = bos.toByteArray();
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bis);
Sheep s2 = (Sheep) ois.readObject(); //克隆好的对象!
System.out.println("修改原型对象的属性值");
date.setTime(23432432423L);
System.out.println(s1.getBirthday());
s2.setSname("多利");
System.out.println(s2);
System.out.println(s2.getSname());
System.out.println(s2.getBirthday());
}
}
比较原型模式和普通new方式效率测试
代码如下:
public class Client4 {
public static void testNew(int size){
long start = System.currentTimeMillis();
for(int i=0;i<size;i++){
Laptop t = new Laptop();
}
long end = System.currentTimeMillis();
System.out.println("new的方式创建耗时:"+(end-start));
}
public static void testClone(int size) throws CloneNotSupportedException{
long start = System.currentTimeMillis();
Laptop t = new Laptop();
for(int i=0;i<size;i++){
Laptop temp = (Laptop) t.clone();
}
long end = System.currentTimeMillis();
System.out.println("clone的方式创建耗时:"+(end-start));
}
public static void main(String[] args) throws Exception {
testNew(1000);
testClone(1000);
}
}
class Laptop implements Cloneable { //笔记本电脑
public Laptop() {
try {
Thread.sleep(10); //模拟创建对象耗时的过程!
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
protected Object clone() throws CloneNotSupportedException {
Object obj = super.clone(); //直接调用object对象的clone()方法!
return obj;
}
}
如果需要短时间创建大量对象,并且new的过程比较耗时。则可以考虑使用原型模式!
适用场景
(1)复制对象的结构和数据。
(2)希望对目标对象的修改不影响既有的原型对象。
(3)创建一个对象的成本比较大。
(4) 原型模式很少单独出现,一般是和工厂方法模式一起出现,通过clone的方法创建一个对象,然后由工厂方法提供给调用者。(也就是工厂模式底层使用clone方法创建对象)。