原型模式主要使用与以下场景:
(1)类初始化消耗资源较多。
(2)使用 new 生成一个对象需要非常繁琐的过程(数据准备、访问权限等)。
(3)构造函数比较复杂。
(4)在循环体中产生大量对象。
在 spring 中,原型模式应用的非常广泛。例如 scope="prototype" ,我们常用的JSON.oarseObject() 也是一种原型模式。
浅克隆
一个标准的原型模式代码应该这样设计,先创建一个 Prototype 接口
public interface Prototype { Prototype clone(); }
创建具体需要克隆的类 ConcretePrototypeA:
@Data public class ConcretePrototypeA implements Prototype { private int age; private String name; private List hobbies; @Override public Prototype clone() { ConcretePrototypeA concretePrototypeA = new ConcretePrototypeA(); concretePrototypeA.setAge(this.age); concretePrototypeA.setName(this.name); concretePrototypeA.setHobbies(this.hobbies); return concretePrototypeA; } }
创建 Client 类:
public class Client { private Prototype prototype; public Client(Prototype prototype){ this.prototype = prototype; } public Prototype startClone(Prototype concretePrototype){ return (Prototype)concretePrototype.clone(); }
测试代码如下:
@Test void PrototypeTest(){ //创建一个具体的需要克隆的对象 ConcretePrototypeA concretePrototype = new ConcretePrototypeA(); //天成属性,方便测试 concretePrototype.setAge(18); concretePrototype.setName("prototype"); List hoobies = new ArrayList<String>(); concretePrototype.setHobbies(hoobies); System.out.println(concretePrototype); //创建client类准备克隆 Client client = new Client(concretePrototype); ConcretePrototypeA concretePrototypeClone = (ConcretePrototypeA)client.startClone(concretePrototype); System.out.println(concretePrototypeClone); System.out.println("克隆对象中的引用类型地址值:" + concretePrototypeClone.getHobbies()); System.out.println("原对象中的引用类型地址值:" + concretePrototype.getHobbies()); System.out.println("对象地址比较:" + (concretePrototype.getHobbies() == concretePrototypeClone.getHobbies())); }
从测试结果可以看出,hobbies 的引用地址是相同的,意味着赋值的不是指,而是引用的地址。这样的话,如果我们修改任意一个对象的属性值,则 concretePrototype 和 concretePrototypeClone 的 hobbies 的值都会改变,这就是我们常说的浅克隆。浅克隆只是完整复制了值类型数据,没有赋值引用对象。换言之,所有的应用对象仍然指向原来的对象,显然这不是我们想要的结果。
深克隆
我们换一个场景,大家都知道七天大声,首先它是一个猴子,有七十二变,把一根毫毛就可以吹出千万个猴子,还有一个金箍棒,金箍棒可以变大变小。这就是我们耳熟能详的原型摩尔是的经典体现。
创建原型猴子类 Monkey:
public class Monkey { public int height; public int weight; public Date birthday; }
创建引用对象金箍棒类:
public class JinGuBang implements Serializable { public float h = 100; public float d = 10; public void big(){ this.d *= 2; this.h *= 2; } public void small(){ this.d /= 2; this.h /= 2; } }
创建具体的对象七天大圣类
public class QiTianDaSheng extends Monkey implements Cloneable, Serializable { public JinGuBang jinGuBang; public QiTianDaSheng(){ this.birthday = new Date(); this.jinGuBang = new JinGuBang(); } @Override protected Object clone() throws CloneNotSupportedException{ return this.deepClone(); } public 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); QiTianDaSheng copy = (QiTianDaSheng) ois.readObject(); copy.birthday = new Date(); return copy; } catch (Exception e){ e.printStackTrace(); return null; } } public QiTianDaSheng shallClone(QiTianDaSheng target){ QiTianDaSheng qiTianDaSheng = new QiTianDaSheng(); qiTianDaSheng.height = target.height; qiTianDaSheng.weight = target.weight; qiTianDaSheng.jinGuBang = target.jinGuBang; qiTianDaSheng.birthday = target.birthday; return qiTianDaSheng; } }
测试代码如下:
@Test void DeepCloneTest(){ QiTianDaSheng qiTianDaSheng = new QiTianDaSheng(); try{ QiTianDaSheng clone = (QiTianDaSheng) qiTianDaSheng.clone(); System.out.println("深克隆:" + (qiTianDaSheng.jinGuBang == clone.jinGuBang)); } catch (Exception e){ e.printStackTrace(); } QiTianDaSheng q = new QiTianDaSheng(); QiTianDaSheng n = q.shallClone(q); System.out.println("浅克隆:" + (q.jinGuBang == n.jinGuBang)); }
测试结果如下:
克隆破坏单例模式
如果克隆的目标是单例对象,那么意味着深克隆会破坏单例模式。实际上防止克隆破坏单例模式的解决思路很简单,禁止深克隆便可。要么单例就不实现 Cloneable 接口,要么我们重写 clone() 方法,在clone() 方法中直接返回单例对象即可,具体代码如下:
@Override public Object clone() throws CloneNotSupportedException{ return INSTANCE; }
clean() 方法源码:
/** * 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); } }