原型模式
概述
原型模式(Prototype Pattern)是指使用原型实例创建对象的类型,并且通过复制原型创建新的对象,属于创建型模式不属于23中设计模式。说白了就是将一个对象作为原型,对其进行复制、克隆,产生一个和原对象类似的新对象。原型模式的例子:BeanUtils、JSON.parseObject等
原型模式适用场景:
- 类初始化消耗大
- new对象时需要非常繁琐的过程(数据准备、访问权限等)
- 构造函数比较复杂
对对象复制和拷贝分为浅复制和深复制下面来介绍
浅复制
完整复制值类型的数据,不复制引用类型的数据。换句话说就是只复制当前对象的值数据,对于内部数组、引用对象不复制,也就是说修改引用对象时,所以复制对象包括原型对象都会被修改。
下面我们实现JDK的Cloneable接口实现克隆
原型实体对象
public class ConcretePrototypeA implements Cloneable{ private String name; private String age; private Date birthday; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAge() { return age; } public void setAge(String age) { this.age = age; } public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } @Override public String toString() { return "ConcretePrototypeA{" + "name='" + name + '\'' + ", age='" + age + '\'' + ", birthday=" + birthday + '}'; } @Override public Object clone() throws CloneNotSupportedException { Object obj = super.clone(); ConcretePrototypeA cloneObj = (ConcretePrototypeA)obj; return obj; } }
使用Object的clone方法实现
测试代码
public class Client { public static void main(String[] args) throws CloneNotSupportedException { ConcretePrototypeA concretePrototypeA1 = new ConcretePrototypeA(); concretePrototypeA1.setAge("20"); concretePrototypeA1.setName("小明"); concretePrototypeA1.setBirthday(new Date(21322212L)); System.out.println("原型对象"+concretePrototypeA1); ConcretePrototypeA concretePrototypeA2 = (ConcretePrototypeA) concretePrototypeA1.clone(); System.out.println("复制对象"+concretePrototypeA2); System.out.println("原型对象与复制对象是否相等:"+ (concretePrototypeA1 == concretePrototypeA2)); System.out.println("原型对象Birthday与复制对象Birthday是否相等:"+(concretePrototypeA1.getBirthday() == concretePrototypeA2.getBirthday())); concretePrototypeA2.getBirthday().setTime(55455544L); System.out.println("原型对象"+concretePrototypeA1); System.out.println("复制对象"+concretePrototypeA2); } }
结果
可以看到复制了一个新的对象,复制了值类型的属性,不过Date类型的Birthday属性没有复制仍然是同一个引用,我们修改复制对象的Birthday属性值时,元对象也会改变
深复制
除了复制值类型的属性,那些指向对象的引用也会复制指向一个新的对象。那么这是怎么实现的呢。我们举一个经典的例子齐天大圣。首先他是只猴子,手拿金箍棒,可变大小,一根猴毛吹出千万个泼猴。
原型猴子Monkey
public class Monkey { public String height; public String weight; public Date birthday; }
引用类金箍棒jinGuBang
public class JinGubang implements Serializable { public float h = 100; public float d = 10; public void big() { this.h *= 2; this.d *=2; } public void small() { this.h = this.h/2; this.d = this.d/2; } }
具体原型类QiTianDaShen
public class QiTianDaSheng extends Monkey implements Cloneable, Serializable { public JinGubang jingubang; public QiTianDaSheng() { this.height = "1.8m"; this.weight = "130g"; 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(); bos.close(); oos.close(); bis.close(); ois.close(); return copy; } catch (Exception e) { e.printStackTrace(); return null; } } public Object shallowClone() throws CloneNotSupportedException { return super.clone(); } }
测试类
public class DeepTest { public static void main(String[] args) { QiTianDaSheng q1 = new QiTianDaSheng(); try { QiTianDaSheng q2 = (QiTianDaSheng)q1.clone(); System.out.println("原型对象与复制对象里面的引用对象是否相等:"+(q1.jingubang == q2.jingubang)); }catch (Exception e) { e.printStackTrace(); } } }
结果
实现序列化接口,把对象写到二进制流里然后在读出对象产生新的对象实现深度克隆
有没有发现原型模式与单例模式是互相冲突的,所以为防止克隆破坏单例时,我们禁止深度克隆。要么单例类不实现Cloneable接口;要么重写clone方法,在clone方法里直接返回单例对象就行。