概念:该模式有一个样板实例,这个被克隆的实例就是我们说的“原型”。说白了就是有一个对象A,我们要操作的时候拷贝A得到一个A的副本B,这个A就称之为原型。我们操作的时候是对B做操作,这样就不会影响到原型A。
应用场景:
- 类初始化的时候需要消耗的资源比较多,这些资源包括数据、硬件等资源,通过原型拷贝可以避免这些消耗。
- 通过new产生一个对象需要非常繁琐的数据准备或者权限,这时可以使用原型模式。
- 一个对象需要供给其他对象访问,而且各个对象可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用,即保护性拷贝。
- 构造函数比较复杂
- 循环体中有大量对象
优点:
- 性能优良,Java自带额原型模式是基于内存二进制流的拷贝,比直接new一个对象性能上升了许多。
- 可以使用深克隆方式保存对象的状态,使用原型模式将对象复制一份并将其状态保护起来,简化了创建过程。
缺点:
- 必须具备克隆(或拷贝)的方法(如实现cloneable接口)。
- 当对已有类进行改造的时候,需要修改代码,违反了开闭原则。
- 深拷贝,浅拷贝需要运用得当(深拷贝方法一般都要自己实现,直接调用clone()方法都是浅拷贝)。
拷贝:
- 浅拷贝:只拷贝基本类型,不会拷贝引用类型,引用类型复制之后,还是共同引用,但是String类型除外,因为String是被final修饰的,所以各个对象之间互不影响。
- 深拷贝:会在计算机中开辟一个新的内存地址,所以深拷贝对象都是新的对象。
注意:原型模式和单例模式不能同时使用,他们是相背的,单例模式是使构造方法私有化,而原型模式根本不需要构造方法,所以在设计程序的时候,如果使用单例模式,我们一般就不能实现cloneable接口,否则可能会带来其他问题(如可以克隆出很多对象,破坏单例)。
原型模式demo:
public class Book implements Cloneable{
//名称
private String title;
//图片
private ArrayList<String> listImg = new ArrayList<>();
public void addImg(String imgName){
listImg.add(imgName);
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public List<String> getListImg() {
return listImg;
}
public void setListImg(ArrayList<String> listImg) {
this.listImg = listImg;
}
public void showBook(){
System.out.println("..................start......................");
System.out.println("title:" + title);
listImg.stream().forEach(img -> System.out.println("img name" + img));
System.out.println("..................end......................");
}
@Override
protected Object clone() throws CloneNotSupportedException {
//浅克隆 默认浅克隆
Book book = (Book)super.clone();
//深克隆
book.listImg = (ArrayList<String>) this.listImg.clone();//这个方法在ArrayList类中,List接口没有这个clone()方法
return book;
}
}
public class Client {
public static void main(String[] args) throws CloneNotSupportedException {
Book book1 = new Book();
book1.setTitle("图书1");
book1.addImg("图片1");
book1.showBook();
//以原型方式进行复制
Book cloneBook = (Book) book1.clone();
book1.setTitle("图书2");
book1.addImg("图片2");
cloneBook.showBook();
book1.showBook();
}
}
输出结果:
..................start......................
title:图书1
img name图片1
..................end......................
..................start......................
title:图书1
img name图片1
..................end......................
..................start......................
title:图书2
img name图片1
img name图片2
..................end......................
上述代码,定义了一本书,有名称和图片两个属性,标题是String类型的,图片是ArrayList集合,通过浅克隆克隆String类型的title,深克隆克隆ArrayList类型的图片,这样就可以克隆出一个新的对象出来,然后进行赋值比较。通过输出结果可以看出,book1赋值图书1和图片1之后拷贝一个cloneBook对象,然后再对book1对象进行修改,但是此时并不会对cloneBook有影响,也就是说已经是两个不同的对象了。如果使用浅克隆对Arraylist进行操作的话,cloneBook对象的输出应该是:“图书1,图片1,图片2”(只要将“ book.listImg = (ArrayList<String>) this.listImg.clone();” 这句注释即可,可以私下去验证一下)。
ArrayList、 HashMap底层其实也是浅克隆,只是把元素搬了个家,只要实现了cloneable接口的都是浅克隆,如果一个类中又有list又有map那我们不是要一个个去调用,如果引用类型里面又有引用类型,处理起来也是非常麻烦的。所以我们一般使用序列化和反序列化以及JSON转换方式来实习深克隆,这样也比较方便,直接转换就行了。
序列化深克隆方法:
public User deepCloneByIO(){
try{
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (User) ois.readObject();
}catch (Exception e){
e.printStackTrace();
return null;
}
}
注意:实现序列化必须要实现Serializable接口,否则会报异常。
使用序列化来实现深克隆带来一系列的问题,会带来一定的性能问题以及占用io,所以一般情况下都使用JSON对象转换来实现深克隆:
public User deepCloneByJson(Object object){
return JSON.parseObject(JSON.toJSONString(object), User.class);
}
操作起来简单,方便。