原型模式引入
在我们实际开发过程中,经常会遇到有些实体类中的属性特别多,我们需要一个一个的去匹配对应的属性然后set进去。这样我们也忍了,但是可恨的是,有时候我们需要创建这个类很多次,基本上复制过去的,但是还不能用原来的对像,就像spring中有一个属性scope="prototype"设置的操作,还有JSON.parseObject(),就必须要把值给弄过去。这些操作复制对应值的时候肯定不是先get后set了,那得多麻烦,还消耗资源。这就引入了我们的原型模式。
原型模式是指原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象。
适用场景如下:
- 类初始化小号资源较多
- 使用new生成一个对象需要非常繁琐的过程
- 构造函数复杂
- 在循环体中产生大量的对象
使用原型模式的主要有两种:浅克隆和深克隆
浅克隆
浅克隆,顾名思义,就是表面的复制,并没有影响人家的内涵。浅克隆复制的是引用的地址而不是值。也就是如果我们修改了原型的其中一个属性的值,那么克隆出来的值也会发生变化。我们看如下代码:
public interface Prototype {
Prototype clone();
}
public class ClonePrototype implements Prototype {
private int age;
private String name;
private List hobbies;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List getHobbies() {
return hobbies;
}
public void setHobbies(List hobbies) {
this.hobbies = hobbies;
}
@Override
public ClonePrototype clone() {
ClonePrototype clone = new ClonePrototype();
clone.setAge(this.age);
clone.setName(this.name);
clone.setHobbies(this.hobbies);
return clone;
}
}
public class Client {
public Prototype startClone(Prototype concretePrototype) {
return concretePrototype.clone();
}
}
public class PrototypeTest {
public static void main(String[] args) {
// 原型,一个被克隆的对象,也就是母体
ClonePrototype clonePrototype = new ClonePrototype();
clonePrototype.setAge(18);
clonePrototype.setName("Naomi");
List<String> hobbies = new ArrayList<>();
hobbies.add("shilylyp");
clonePrototype.setHobbies(hobbies);
System.out.println("母体:" + clonePrototype);
// 创建复制的工具,开始复制原型
Client client = new Client();
ClonePrototype clone = (ClonePrototype)client.startClone(clonePrototype);
System.out.println("复制体:" + clone);
System.out.println("母体中属性的引用地址:" + clonePrototype.getHobbies());
System.out.println("复制体中属性的引用地址:" + clone.getHobbies());
System.out.println("两者是否相等:" + (clone.getHobbies() == clonePrototype.getHobbies()));
}
}
测试结果如下:
母体:com.shmilylyp.demo10.ClonePrototype@5cad8086
复制体:com.shmilylyp.demo10.ClonePrototype@6e0be858
母体中属性的引用地址:[shilylyp]
复制体中属性的引用地址:[shilylyp]
两者是否相等:true
Process finished with exit code 0
很显然是一样的,这样的结果当然不是我们想要的。有浅就有深么,不能因为你短就说所有人都短是吧。
深克隆
废话不多说,直接上代码,我们要复制好多苹果味的蛋糕。这次吃个饱,嘻嘻。
public class AppleCake implements Cloneable, Serializable {
// 苹果酱
public List<String> apples;
public AppleCake() {
List appleList = new ArrayList();
appleList.add("apple");
this.apples = appleList;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return this.deepClone();
}
private Object deepClone() {
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
AppleCake appleCake = (AppleCake)ois.readObject();
return appleCake;
}catch (Exception e){
e.printStackTrace();
return null;
}
}
public AppleCake shallowClone(AppleCake cake) {
AppleCake appleCake = new AppleCake();
appleCake.apples = cake.apples;
return appleCake;
}
}
public class AppleCakeTest {
public static void main(String[] args) {
AppleCake appleCake1 = new AppleCake();
try {
AppleCake appleCakeDeep = (AppleCake)appleCake1.clone();
System.out.println("深克隆属性:" + (appleCake1.apples == appleCakeDeep.apples));
System.out.println("深克隆实体:" + (appleCake1 == appleCakeDeep));
}catch (Exception e){
e.printStackTrace();
}
AppleCake appleCake2 = new AppleCake();
AppleCake appleCakeShallow = appleCake2.shallowClone(appleCake2);
System.out.println("浅克隆属性:" + (appleCake2.apples == appleCakeShallow.apples));
System.out.println("浅克隆实体:" + (appleCake2 == appleCakeShallow));
}
}
运行结果如下:
深克隆属性:false
深克隆实体:false
浅克隆属性:true
浅克隆实体:false
Process finished with exit code 0
由此可见虽然深克隆和浅克隆弄出来的实体都是不一样的,但是浅克隆里面的属性是相等的。
克隆破坏单例
从深克隆的结果来看,我们也能够看出克隆是可以破坏掉单例的,浅克隆是没有破坏的,实际内存还是那个,只不过复制了一个引用地址而已,不必惊慌。因此当我们使用单例的时候就需要防止这种情况的发生。其实也很简单,只要我们禁止掉深克隆就OK了,不然你克隆,你还能搞破坏???我们可以这样做:
- 单例类的时候不实现Cloneable接口
- 在重写方法clone()中返回原来的实例