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); } }