设计模式(Java语言)- 原型模式
原型模式(Prototype Pattern)也有人将原型模式称为克隆模式,是属于创造型设计模式,用于创建重复的对象,提供了一种创建对象的最佳方式。原型模式需要实现Cloneable接口,来实现对象的克隆。在实际的应用中,如果应用需要反复创建相同的对象时,并且创建这个对象需要花费大量时间或者需要访问权限,比如需要读取数据库,配置文件等,如果每次创建重复对象都需要读一次数据库,那么这种方式显然并不是高效的。这时可以考虑使用原型模式来解决,提高效率,此时只需要在创建原型对象时需要读取一次数据库或配置文件等,当后面需要需要创建这个对象时只需要从原型对象克隆一个出来即可。另外,原型模式也解决了构建复杂对象时繁琐的过程,原型模式不关心对象创建的细节,用户只需要调用克隆的方法就可以创建出一个一摸一样的对象,简化创建流程。
既然原型模式也成为克隆模式,那么对象复制过程必然用到Java的克隆方法。所以你也需要了解什么是浅克隆和深克隆。
浅克隆
浅克隆复制的是对象基本类型的属性,对于引用类型的属性,浅克隆置复制该应用类型的地址,因为克隆对象的被克隆对象的应用类型属性是同一个内存地址,即为同一个对象,所以在修改其中一个对象的该属性时,另一个对象的改属性也会被修改,很容易将原型对象属性修改,这也是在使用原型模式时需要注意的地方。浅克隆在代码中的实现也比较简单,Java语言中本身就已经提供相关的接口和方法了,我们在使用时只需要继承Cloneable接口,重写clone方法即可实现对象的浅克隆。代码实现如下:
public class Sheep implements Cloneable { private String name; private Color color = new Color(); public Sheep() { } public Sheep(String name, String color) { this.name = name; setColor(color); } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } @Override public String toString() { return "Sheep{" + "name='" + name + '\'' + ", color=" + color + '}'; } public Color getColor() { return color; } public void setColor(String color) { this.color.setColor(color); } public String getName() { return name; } public void setName(String name) { this.name = name; } }
public class Color implements Cloneable { private String color; public Color() { } public Color(String color) { this.color = color; } public String getColor() { return color; } public void setColor(String color) { this.color = color; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } @Override public String toString() { return "Color{" + "color='" + color + '\'' + '}'; } }
测试
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Sheep test = new Sheep("test","白色");
System.out.println(test);
Sheep clone = (Sheep) test.clone();
clone.setColor("黑色");
clone.setName("test01");
System.out.println(test);
System.out.println(clone);
}
}
运行程序时控制台打印出了:
Sheep{name='test', color=Color{color='白色'}}
Sheep{name='test', color=Color{color='黑色'}}
Sheep{name='test01', color=Color{color='黑色'}}
很显然,test对象创建时是白色的,然后用这个对象进行克隆得到 clone 实例,然后将clone 对象的颜色修改成黑色,name修改成test01,最终两个对象的颜色都变成了黑色,印证了上面说的话,对于引用类型克隆的是对象的内存地址。可能会有人好奇,String也是引用类型,为什么克隆对象修改了name属性,原型对象却没有被修改了?这是因为String是final类型,克隆过程中自然会是两个不同的内存地址。
深克隆
深克隆和浅克隆的区别在于,深克隆时引用类型属性复制的是该属性的值,与原型对象的拥有不同的内存地址,即两个是不同的对象,他们任意一个改属性值都不会影响到彼此。深克隆的实现方式有两种,第一种,实现Cloneable接口,重写clone方法,与浅克隆不同的是多一步将引用类型的变量再调用一次改变量的clone方法。不推荐用这种方法实现深克隆,每次修改对象的变量时都需要修改一次clone方法,违反了ocp原则。第二种,利用Java序列化与发序列化来实现,推荐使用这种方式。
代码实现:
public class Color implements Cloneable, Serializable { private String color; public Color() { } public Color(String color) { this.color = color; } public String getColor() { return color; } public void setColor(String color) { this.color = color; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } @Override public String toString() { return "Color{" + "color='" + color + '\'' + '}'; } }
public class Sheep implements Cloneable, Serializable { private String name; private Color color = new Color(); public Sheep() { } public Sheep(String name, String color) { this.name = name; setColor(color); } /** * 利用序列化与反序列化实现深克隆 * @return */ public Object deepClone() { ByteArrayInputStream bis = null; ByteArrayOutputStream bos = null; ObjectOutputStream oos = null; ObjectInputStream ois = null; try { bos = new ByteArrayOutputStream(); oos = new ObjectOutputStream(bos); oos.writeObject(this); bis = new ByteArrayInputStream(bos.toByteArray()); ois = new ObjectInputStream(bis); return ois.readObject(); }catch (Exception e) { e.printStackTrace(); }finally { try { if (ois != null) { ois.close(); } if (bis != null) { bis.close(); } if (oos != null) { oos.close(); } if (bos != null) { bos.close(); } }catch (Exception e) { e.printStackTrace(); } } return null; } @Override protected Object clone() throws CloneNotSupportedException { Sheep clone = (Sheep) super.clone(); clone.color = (Color) clone.color.clone(); return clone; } @Override public String toString() { return "Sheep{" + "name='" + name + '\'' + ", color=" + color + '}'; } public Color getColor() { return color; } public void setColor(String color) { this.color.setColor(color); } public String getName() { return name; } public void setName(String name) { this.name = name; } }
注意,如果使用Java的序列化与反序列化,则改对象需要实现Serializable接口,否则会抛序列化异常。
总结
1、原型模式有两种实现方式,第一种利用Object类中的clone方式,重写Cloneable的clone方法,浅克隆时直接调用Object类提供的clone方式即可。深克隆则需要再调用需要被克隆的对象的clone方法,当然该对象也必须实现Cloneable接口。第二种方式是利用Java的序列化和反序列化技术,这种方式也有一个缺点是所有需要序列化的变量都必须要实现Serializable接口。
2、原型模式的优点:提高效率;屏蔽复杂的对象构建过程,简化代码。
3、原型模式的缺点:
1)配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。
2)必须实现 Cloneable 接口或Serializable接口。
4、原型模式的应用场景:
1)资源优化场景。
2)对象初始化需要大量的资源,包括数据,硬件资源等。