java设计模式——原型模式
一. 定义与类型
定义:指原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。不需要知道任何创建的细节,不调用构造函数
类型:创建型
二.使用场景
类初始化消耗较多资源
new 产生的一个对象需要非常繁琐的过程(数据准备,访问权限等)
构造函数比较复杂
循环体中生产大量对象时
三.优缺点
优点:
原型模式性能比直接new一个对象性能高,简化创建过程
缺点:
必须配备克隆方法,
对克隆复杂对象或对克隆出的对象进行复杂改造时,容易引入风险
深拷贝,浅拷贝要运用得当
四. 扩展
深克隆:对于引用类型,如果需要指向不同的对象,而对于某个对象的引用类型的时候,必须要显式的去写对那个属性进行深克隆
浅克隆:
五. Coding
/** * @program: designModel * @description: * @author: YuKai Fan * @create: 2018-12-13 16:11 **/ public class Mail implements Cloneable { private String name; private String emailAddress; private String content; public Mail() { System.out.println("Mail Class Constructor"); } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getEmailAddress() { return emailAddress; } public void setEmailAddress(String emailAddress) { this.emailAddress = emailAddress; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } @Override public String toString() { return "Mail{" + "name='" + name + '\'' + ", emailAddress='" + emailAddress + '\'' + ", content='" + content + '\'' + super.toString() + '}'; } @Override protected Object clone() throws CloneNotSupportedException { System.out.println("clone mail object"); return super.clone(); } }
/** * @program: designModel * @description: * @author: YuKai Fan * @create: 2018-12-13 16:17 **/ public class MailUtil { public static void sendMail(Mail mail) { String outputContent = "向{0}同学,邮件地址:{1},邮件内容:{2}发送成功"; System.out.println(MessageFormat.format(outputContent,mail.getName(),mail.getEmailAddress(),mail.getContent())); } public static void saveOriginMailRecord(Mail mail) { System.out.println("存储originMail记录,originMail:" + mail.getContent()); } }
/** * @program: designModel * @description: * @author: YuKai Fan * @create: 2018-12-13 16:20 **/ public class Test { public static void main(String[] args) throws CloneNotSupportedException { Mail mail = new Mail(); mail.setContent("初始化模板"); System.out.println("初始化mail:" + mail); for (int i = 0; i < 10; i++) { //克隆的时候,并不会使用原对象的构造器 Mail mailTemp = (Mail) mail.clone(); mailTemp.setName("姓名" + i); mailTemp.setEmailAddress("姓名" + i + "@qq.com"); mailTemp.setContent("恭喜您,中奖了"); MailUtil.sendMail(mailTemp); } MailUtil.saveOriginMailRecord(mail); } }
从上面的代码不难看出,其实原型模式就是实现了cloneable接口,在创建一个不同的对象,来完成邮件的发送,而保留了原来的邮件模板。由于clone不会,调用原对象的构造器,所以在效率上比直接new 对象要高。但是,因为在Mail类中的属性都是简单类型,所以在clone的时候,基本上不会出现上面问题。但是看下面一个实体:
/** * @program: designModel * @description: * @author: YuKai Fan * @create: 2018-12-13 16:33 **/ public class Pig implements Cloneable { private String name; private Date birthDay; public Pig(String name, Date birthDay) { this.name = name; this.birthDay = birthDay; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Date getBirthDay() { return birthDay; } public void setBirthDay(Date birthDay) { this.birthDay = birthDay; } @Override public String toString() { return "Pig{" + "name='" + name + '\'' + ", birthDay=" + birthDay + '}'; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } }
/** * @program: designModel * @description: * @author: YuKai Fan * @create: 2018-12-13 16:34 **/ public class Test { public static void main(String[] args) throws CloneNotSupportedException, NoSuchMethodException, InvocationTargetException, IllegalAccessException { Date birthDay = new Date(0L); Pig pig = new Pig("佩奇", birthDay); Pig pig1 = (Pig) pig.clone(); System.out.println(pig); System.out.println(pig1); pig.getBirthDay().setTime(6666666666666L); /** * 通过上面对birthDay进行操作,如果是浅拷贝,那么pig与pig1的date对象都会改变: * 因为在浅拷贝的时候,两个对象中的引用对象date,都是引用同一个对象,所以改变了一个,那么两个都会改变 * * 如果是深拷贝,那么pig与pig1引用的date对象就是不一样的,改变其中一个,对另一个并没有影响。 * * 由于深克隆,浅克隆的关系,也算是原型模式的一个坑。(原则是,都会使用深克隆,不然就算是给项目埋坑) * */ System.out.println(pig); System.out.println(pig1); } }
输出结果为:
上面的注释和结果,其实也清楚的看到了。我只改变了pig对象中的birthDay,但是pig1中也改变了。如果clone的对象中存在引用类型的对象,那么如果是浅拷贝,拷贝与被拷贝出的对象的引用对象都是指向同一地址的,所以改变其中一个,另一个也会改变。这时候,如果根据需求就必须使用深拷贝。
也就是对于对象中的引用对象也要进行clone。看下面代码:
/** * @program: designModel * @description: * @author: YuKai Fan * @create: 2018-12-13 16:33 **/ public class Pig implements Cloneable { private String name; private Date birthDay; public Pig(String name, Date birthDay) { this.name = name; this.birthDay = birthDay; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Date getBirthDay() { return birthDay; } public void setBirthDay(Date birthDay) { this.birthDay = birthDay; } @Override public String toString() { return "Pig{" + "name='" + name + '\'' + ", birthDay=" + birthDay + '}'; } @Override protected Object clone() throws CloneNotSupportedException { Pig pig = (Pig) super.clone(); //深克隆 pig.birthDay = (Date) pig.birthDay.clone(); return pig; } }
输出结果:
可以看到,Pig中重写了clone方法,对Date对象也进行了clone,从而使得,引用对象指向不同的地址。所以改变pig中的birthDay,对于pig1并没有影响。
由于clone方法,可以用原型模式拷贝来破坏单例模式:
/** * @program: designModel * @description: * @author: YuKai Fan * @create: 2018-12-13 16:34 **/ public class Test { public static void main(String[] args) throws CloneNotSupportedException, NoSuchMethodException, InvocationTargetException, IllegalAccessException { /** * 使用原型模式,克隆破坏单例模式 * * 这种情况的解决方式: * 要么单例模式类不去实现Cloneable接口,要么就重写clone方法,直接返回getInstance()方法,这个对象的实例 */ HungrySingleton hungrySingleton = HungrySingleton.getInstance(); Method method = hungrySingleton.getClass().getDeclaredMethod("clone"); method.setAccessible(true); HungrySingleton cloneHungrySingleton = (HungrySingleton) method.invoke(hungrySingleton); System.out.println(hungrySingleton); System.out.println(cloneHungrySingleton); } }
六. 源码分析
基本上只要知道了哪些类使用了Cloneable就知道,原型模式如何使用。
比如ArrayList,HashMap类都重写了clone方法
Mybatis中的CacheKey类也重写了clone()方法