JAVA 设计模式之 原型模式详解
原型模式(Prototype Pattern)是指原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
原型模式利用的是克隆的原理,创建新的对象,JDK提供的Cloneable 和JSON、springUtil里面的克隆都是一般浅克隆,与之对应的还有深克隆
1、浅克隆
浅克隆也是穿件一个新的对象,不过该对象的属性值是被克隆对象的,如果修改被克隆对象,后者跟着修改。
下面我们用Cloneable写一个简单的浅克隆
import java.util.List; /** * @Description TODO * @Author Bert * @Date 2019\6\10 0010 */ public class ShallowClone implements Cloneable{ private String name; private int age; private List<String> hobbies; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public List<String> getHobbies() { return hobbies; } public void setHobbies(List<String> hobbies) { this.hobbies = hobbies; } //实现 Cloneable 的 clone 方法 属于浅克隆 @Override public ShallowClone clone() { ShallowClone shallowClone = null; try { shallowClone = (ShallowClone)super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return shallowClone; } //手写 浅克隆 方法 public ShallowClone spellClone(){ ShallowClone shallowClone = new ShallowClone(); shallowClone.setName(this.name); shallowClone.setAge(this.age); shallowClone.setHobbies(this.hobbies); return shallowClone; } }
测试代码:
public static void main(String[] args) { ShallowClone shallowClone = new ShallowClone(); shallowClone.setName("ZhangSan"); shallowClone.setAge(21); List<String> list = new ArrayList<>(); list.add("play game"); list.add("Listen music"); shallowClone.setHobbies(list); //调用Cloneable 的clone方法 ShallowClone clone1 = shallowClone.clone(); //添加喜好 shallowClone.getHobbies().add("fitness"); //调用手写的浅克隆方法 ShallowClone clone2 = shallowClone.spellClone(); System.out.println(shallowClone == clone1);//判读是否为同一个对象 System.out.println(shallowClone == clone2);//判读是否为同一个对象 System.out.println("克隆对象中引用类型地址:"+shallowClone.getHobbies()); System.out.println("克隆对象中引用类型地址:"+clone1.getHobbies()); System.out.println("克隆对象中引用类型地址:"+clone2.getHobbies()); }
运行结果:
false false 克隆对象中引用类型地址:[play game, Listen music, fitness] 克隆对象中引用类型地址:[play game, Listen music, fitness] 克隆对象中引用类型地址:[play game, Listen music, fitness]
从运行结果我们可以看出,克隆对象和原来的对象不是同一个,但对象的属性完全一样,即使改变了其中一个,其他的也会跟着改变。
通过上面spellClone() 方法我们可以看出,浅克隆的整个过程就是,创建一个新的对象,然后新对象的每个值都是由原对象的值,通过 =
进行赋值;
(1)拷贝后获取的是一个独立的对象,和原对象拥有不同的内存地址;
(2)基本元素类型,两者是隔离的,int, Integer, long, Long, char, Charset, byte,Byte, boolean, Boolean, float,Float, double, Double, String
(3)非基本数据类型(如基本容器,其他对象等),只是拷贝了一份引用出去了,实际指向的依然是同一份,例如上的list<String>
一句话总结就是:基本数据类型是值赋值;非基本的就是引用赋值。
2、深克隆
创建一个全新的对象,新的对象内部所有的成员也都是全新的,只是初始化的值已经由被克隆的对象确定,但是他们是两个完全独立的对象,修改是隔离,互不影响。
下面我们来写一个简单的深克隆
import java.io.*; import java.util.ArrayList; import java.util.List; /** * @Description TODO * @Author Bert * @Date 2019\6\10 0010 */ public class DeepClone implements Serializable{ private String name; private int age; private List<String> hobbies; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public List<String> getHobbies() { return hobbies; } public void setHobbies(List<String> hobbies) { this.hobbies = hobbies; } // 深克隆 方法 1 public DeepClone deepClone(){ DeepClone deepClone = new DeepClone(); deepClone.setName(this.name); deepClone.setAge(this.age); //基本数据类型 重新创建 if(null != this.hobbies){ deepClone.setHobbies(new ArrayList<>(this.hobbies)); } return deepClone; } //深克隆 2 通过 byte字节码 ,这种方式需要 实现 Serializable public DeepClone deepCloneByte() { DeepClone dClone = null; ByteArrayOutputStream bys = new ByteArrayOutputStream(); try { ObjectOutputStream oos = new ObjectOutputStream(bys); oos.writeObject(this); ByteArrayInputStream bis = new ByteArrayInputStream(bys.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis); dClone = (DeepClone)ois.readObject(); } catch (Exception e) { e.printStackTrace(); } return dClone; } }
测试代码:
public static void main(String[] args) { DeepClone deepClone = new DeepClone(); deepClone.setName("ZhangSan"); deepClone.setAge(21); List<String> list = new ArrayList<>(); list.add("play game"); list.add("Listen music"); deepClone.setHobbies(list); //调用第一种方式,并修改值 DeepClone deepClone1 = deepClone.deepClone(); deepClone1.getHobbies().add("fitness"); deepClone1.setAge(22); //调用第二种方式,并修改值 DeepClone deepClone2 = deepClone.deepCloneByte(); deepClone2.getHobbies().add("Climbing mountain"); System.out.println(deepClone == deepClone1); System.out.println(deepClone == deepClone2); System.out.println("克隆对象中引用类型地址:"+deepClone.getHobbies()); System.out.println("克隆对象中引用类型地址:"+deepClone1.getHobbies()); System.out.println("克隆对象中引用类型地址:"+deepClone2.getHobbies()); }
运行结果:
false false 克隆对象中引用类型地址:[play game, Listen music] 克隆对象中引用类型地址:[play game, Listen music, fitness] 克隆对象中引用类型地址:[play game, Listen music, Climbing mountain]
以上可以看出深克隆的对象之间修改完全不受影响。
3、原型模式运用场景
(1)类初始化消耗资源较多。
(2)new 产生的一个对象需要非常繁琐的过程(数据准备、访问权限等)
(3)构造函数比较复杂。
(4)循环体中生产大量对象时。
在 Spring 中,原型模式应用得非常广泛。例如 scope=“prototype”,在我们经常用 的 JSON.parseObject()也是一种原型模式。
4、解决深克隆破坏单例模式
如果我们的被克隆对象是单例模式,深克隆就得破坏单例模式。
解决方案:禁止深克隆。一是不实现Cloneable; 二是实现Cloneable ,在clone方法中返回我们的单例。
@Override protected Object clone() throws CloneNotSupportedException { return INSTANCE; }
Cloneable 源码分析:
以ArryList 为例,ArrayList 就实现了 Cloneable 接口
/** * Returns a shallow copy of this <tt>ArrayList</tt> instance. (The * elements themselves are not copied.) * * @return a clone of this <tt>ArrayList</tt> instance */ public Object clone() { try { ArrayList<?> v = (ArrayList<?>) super.clone(); v.elementData = Arrays.copyOf(elementData, size); v.modCount = 0; return v; } catch (CloneNotSupportedException e) { // this shouldn't happen, since we are Cloneable throw new InternalError(e); } }