一、原型模式
Specify the kinds of objects to create using a prototypical instance,and create new objects by copying this prototype.
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建,即 对象.clone()
-
性能优良,原型模式是在内存二进制流的拷贝,要比直接new一个对象性能好很多,特别是要在一个循环体内产生大量的对象时,原型模式可以更好地体现其优点。
-
逃避构造函数的约束,这既是它的优点也是缺点,直接在内存中拷贝,构造函数是不会执行的。优点就是减少了约束,缺点也是减少了约束,需要大家在实际应用时考虑。
-
创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能够提高效率
-
不用重新初始化对象,而是动态地获得对象运行时的状态
-
如果原始对象发生变化(增加或者减少属性),其它克隆对象的也会发生相应的变化,无需修改代码
-
在实现深克隆的时候可能需要比较复杂的代码
-
需要为每一个类配备一个克隆方法,这对全新的类来说不是很难,但对已有的类进行改造时,需要修改其源代码,违背了 ocp 原则,这点请同学们注意.
二、代码
模拟一个发放工资短信通知的例子:
SalaryTemplate.java
@Data
public class SalaryTemplate {
private String salSubject="工资发放通知";
private String salContext="本月工资已发放,请您注意查收!";
}
Mail.java
@Data
public class Mail implements Cloneable {
private String username;
private String subject;
private String context;
private String tail;
public Mail(SalaryTemplate salaryTemplate) {
this.subject = salaryTemplate.getSalSubject();
this.context = salaryTemplate.getSalContext();
}
@Override
public Mail clone() {
Mail mail = null;
try {
mail = (Mail) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
System.out.println(e.getMessage());
}
return mail;
}
}
mail实现了cloneable接口,在类中重写了clone()方法。
Client.java
public class Client {
private static final int STR_LEN = 6;
public static void main(String[] args) {
Mail mail = new Mail(new SalaryTemplate());
mail.setTail("感谢您对我们工作的支持");
int i = 0;
while (i < 6) {
Mail cloneMail = mail.clone();
cloneMail.setUsername(getRandomString(STR_LEN));
sendMail(cloneMail);
i++;
}
}
private static void sendMail(Mail cloneMail) {
System.out.println("通知主题:" + cloneMail.getSubject() + " 通知内容: " + "尊敬的" + cloneMail.getUsername() + ", " + cloneMail.getContext() + cloneMail.getTail());
}
private static String getRandomString(int length) {
String source = "abcdefghijklmnopqrskuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
StringBuffer sb = new StringBuffer();
Random rand = new Random();
for (int i = 0; i < length; i++) {
sb.append(source.charAt(rand.nextInt(source.length())));
}
return sb.toString();
}
}
三、浅拷贝与深拷贝
Object类提供的方法clone只是拷贝本对象,其对象内部的数组、引用对象等都不拷贝,还是指向原生对象的内部元素地址,这种拷贝就叫做浅拷贝。
内部的数组和引用对象才不拷贝,其他的原始类型比如int、long、char等都会被拷贝,但是对于String类型,Java就希望你把它认为是基本类型,它是没有clone方法的,处理机制也比较特殊,通过字符串池(stringpool)在需要的时候才在内存中创建新的字符串,在使用的时候就把String当做基本类使用即可。
使用原型模式时,引用的成员变量必须满足两个条件才不会被拷贝:一是类的成员变量,而不是方法内变量;二是必须是一个可变的引用对象,而不是一个原始类型或不可变对象。
实现深拷贝的两种方式:
方式一:使用clone方法:
@Data
public class Thing implements Cloneable {
private String name;
private ArrayList<String> stringList;
@Override
public Thing clone() {
Thing cloneThing = null;
try {
cloneThing = (Thing) super.clone();
cloneThing.stringList = (ArrayList<String>) this.stringList.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return cloneThing;
}
}
方式二:使用序列化(推荐)
@Data
class SerialClone implements Cloneable, Serializable {
private String name;
private ArrayList<String> strings;
@Override
public Object clone() throws CloneNotSupportedException {
try {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream)) {
objectOutputStream.writeObject(this);
}
try (InputStream inputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray())) {
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
return objectInputStream.readObject();
}
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
return null;
}
}
public static void main(String[] args) throws CloneNotSupportedException {
SerialClone serialClone=new SerialClone();
ArrayList<String> arrayList=new ArrayList<>(2);
arrayList.add("java");
serialClone.setName("Clone");
serialClone.setStrings(arrayList);
System.out.println(serialClone.getStrings());
SerialClone clone= (SerialClone) serialClone.clone();
clone.getStrings().add("vue");
System.out.println(clone.getStrings());
System.out.println(serialClone.getStrings());
System.out.println(serialClone==clone);
}
}