原型模式
用原型实例指定创建对象的种类,并通过复制这些原型创建新的对象。原型模式是从一个对象出发得到一个和自己有相同状态的新对象的成熟模式,该模式的关键是将一个对象定义为原型,并为其提供复制自己的方法。
UML类图
原型模式的结构中包括两种角色:
抽象原型(Prototype):一个接口,负责定义对象复制自身的方法。
具体原型(Concrete Prototype):实行prototype接口的类,具体
要理解原型原型模式必须先理解Java里的浅复制和深复制,有的地方,复制也叫做克隆。因为Java提供了clone()方法来实现对象的克隆,所以Prototype模式的实现变得很简单。
浅克隆:被克隆对象的成员变量是对象的话,那么clone()方法仅仅复制了当前对象所拥有的对象的引用,并没有复制这个对象所拥有的变量,这就使clone()方法返回的新对象和当前对象拥有一个相同的对象,未能实现完全意义的复制。
深克隆:被克隆对象的所有变量都含有与原来的对象相同的值,但它所有的对其他对象的引用不再是原有的,而这是指向被复制过的新对象。换言之,深复制把要复制的对象的所有引用的对象都复制了一遍,这种叫做间接复制。
java.lang包中的Object类提供了一个权限是protecd用于复制对象的clone()方法。为了能让一个对象使用clone()方法,创建该对象的类可以重写clone()方法,并将访问权限提高为public,为了能够使用被覆盖的clone()方法,只需在重写的clone()方法中使用关键字super调用Object类的clone()方法;也可以在创建对象的类中新定义一个复制对象的方法,将访问权限定义为public,并在该方法中调用Object类的clone()方法。另外,当调用Object类中的clone()方法时,JVM将会逐个复制该对象的成员变量,然后创建一个新的对象返回,所以JVM要求调用clone()方法的对象必须实行Cloneable接口。
举例
public class User implements Cloneable {
String name;
Info info;
public User(String name, Info info) {
this.name = name;
this.info = info;
}
/**
* 重写clone()方法
*
* @return
* @throws CloneNotSupportedException
*/
@Override
public java.lang.Object clone() throws CloneNotSupportedException {
User object = (User) super.clone();
object.info = (Info) info.customClone();//对Info进行复制
return object;
}
}
class Info implements Cloneable {
String address;
public Info(String address) {
this.address = address;
}
/**
* 自定义一个复制对象的方法
*
* @return
*/
public Object customClone() {
Object object = null;
try {
object = clone();//调用Object类的clone()方法
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return object;
}
}
//使用
public class Test {
public static void main(String[] args) {
try {
User user=new User("张三",new Info("beijing"));
User userCopy= (User) user.clone();
System.out.println("user对象中的数据:"+user.name+","+user.info.address);
System.out.println("userCopy对象中的数据:"+userCopy.name+","+userCopy.info.address);
System.out.println("修改userCopy对象中的数据");
userCopy.name="李四";
userCopy.info.address="shanghai";
System.out.println("user对象中的数据:"+user.name+","+user.info.address);
System.out.println("userCopy对象中的数据:"+userCopy.name+","+userCopy.info.address);
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
使用场景
- 创建新对象成本较大(如初始化需要占用较长的时间,占用太多的CPU资源或网络资源),新的对象可以通过原型模式对已有对象进行复制来获得,如果是相似对象,则可以对其成员变量稍作修改。
- 如果系统要保存对象的状态,而对象的状态变化很小,或者对象本身占用内存较少时,可以使用原型模式配合备忘录模式来实现。
- 需要避免使用分层次的工厂类来创建分层次的对象,并且类的实例对象只有一个或很少的几个组合状态,通过复制原型对象得到新实例可能比使用构造函数创建一个新实例更加方便。
注意事项
使用原型模式复制对象不会调用类的构造方法
因为对象的复制是通过调用Object类的clone方法来完成的,它直接在内存中复制数据,因此不会调用到类的构造方法。不但构造方法中的代码不会执行,甚至连访问权限都对原型模式无效。还记得单例模式吗?单例模式中,只要将构造方法的访问权限设置为private型,就可以实现单例。但是clone方法直接无视构造方法的权限,所以,单例模式与原型模式是冲突的,在使用时要特别注意。
Serializable接口和克隆对象
相对于clone()方法,Java又提供了一种较简单的解决方案,这个方案就是使用Serializable接口和对象流来复制对象。如果希望得到对象object的复制品,必须保证该对象是序列化的,即创建object对象的类必须实现Serializable接口。Serializable接口中的方法对程序是不可见的,因此实现该接口的类不需要实现额外的方法。
为了得到object的复制品,首先需要将object写入ObjectOutputStream流中,当把一个序列化的对象写入ObjectOutputStream输出流时,JVM就会实现Serializable接口中的方法,将一定格式的文本——对象的序列化信息,写入ObjectOutputStream输出流的目的地。然后使用ObjectInputStream对象输入流从ObjectOutputStream输出流的目的地读取对象,这时ObjectInputStream对象流就读回object对象的序列化信息,并根据序列化信息创建一个新的对象,这个新对象就是object对象的一个复制品。
需要注意的是,使用对象流把一个对象写入文件时不仅要保证该对象是序列化的,而且该对象的成员变量也是序列化的。
举例
//抽象原型
public interface Prototype {
Object cloneSelf() throws CloneNotSupportedException;
}
//具体原型
public class Student implements Prototype, Serializable {
String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public Object cloneSelf() throws CloneNotSupportedException {
Object object = null;
try {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(this);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
object = objectInputStream.readObject();
} catch (Exception e) {
e.printStackTrace();
}
return object;
}
}
//模式使用
public class PrototypeTest {
public static void main(String[] args) {
try {
Student student=new Student();
student.setName("张三");
Student studentCopy= (Student) student.cloneSelf();
System.out.println("student对象中的数据:"+student.name);
System.out.println("studentCopy对象中的数据:"+studentCopy.name);
System.out.println("修改studentCopy对象中的数据");
studentCopy.setName("李四");
System.out.println("student对象中的数据:"+student.name);
System.out.println("studentCopy对象中的数据:"+studentCopy.name);
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
总结
原型模式优点如下:
- 当创建新的对象实例较为复杂时,可以简化对象创建过程,通过复制一个已有实例可以提高实例的创建效率。
- 扩展性较好,原型模式中提供了抽象原型类,在客户端可以针对抽象原型类进行编程。
- 原型模式提供了简化的创建结构,工厂方法模式常常需要有一个与产品类等级结构相同的工厂等级结构,而原型模式就不需要这样,原型模式中产品的复制是通过封装在原型类中的克隆方法实现的,无须专门的工厂类来创建产品。
- 可以使用深克隆的方式保存对象的状态,使用原型模式将对象复制一份并将其状态保存起来,以便在需要的时候使用(如恢复到某一历史状态),可辅助实现撤销操作。
原型模式缺点如下:
- 需要为每一个类配备一个克隆方法,而且该克隆方法位于一个类的内部,当对已有的类进行改造时违背“开闭原则”。
- 在实现深克隆时需要编写较为复杂的代码,而且当对象之间存在多重的嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆比较复杂。
参考: