原型模式
定义:
指原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象
特点 :
不需要知道任何创建细节,不调用构造函数
类型 :创建型
适用场景
- 类初始化消耗较多资源
- new产生一个对象需要非常繁琐的过程(数据准备,访问权限等)
- 构造函数比较复杂
- 循环体中产生大量的对象
优点
- 比直接new一个对象性能高
- 简化了创建过程
缺点
- 必须配备克隆方法(有一个Cloneable接口,在jvm中具有这个的对象才有可能被拷贝。只有覆盖object的克隆方法才会一定被拷贝。因此这个也变成了一个风险点,毕竟我们可能会忘记。)
- 对克隆复杂对象或对克隆出的复杂对象进行复杂改造时,容易造成风险
- 深拷贝、浅拷贝要运用得当
我们模拟发送email的一个场景,假设new一个实体相当耗时。
@Data
public class Email {
private String name;
private String address;
private String content;
}
public class EmailUtils {
/**
* 发送邮件
*/
public static void sendmail(Email email) {
String outContent = "向{0}同学,邮件地址:{1},邮件内容{2},发送邮件";
System.out.println(MessageFormat.format(outContent, email.getName(), email.getAddress(), email.getContent()));
}
/**
* 记录邮件
*/
public static void saveOriginMailRRecord(Email email) {
System.out.println("存储originMail记录,riginMailriginMail:" + email.getContent());
}
}
通过打印结果我们可以得到可以轻松看出来,实际打印跟我们预想的并不符合。我们是想让saveOriginMailRRecord()这个方法打印“初始化模版”这句话的,但是最后结果却是打印了“恭喜中奖了哦”。
按照往常的解决方法不是将24行的代码放到16行就是在for循环里面每次都new一个对象。因为假设了new的话是一个极其耗时的动作,所以我们在这里用原型模式去创建。我们只需要让Email实现Cloneable接口,然后重写一下clone方法即可。
@Data
public class Email implements Cloneable {
private String name;
private String address;
private String content;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
然后我们改一下for循环里面的代码
for (int i = 0; i < 10; i++) {
//一个临时变量
Email temp = (Email) email.clone();
temp.setName("姓名" + i);
temp.setAddress("姓名" + i + "@XX.com");
temp.setContent("恭喜中奖了哦");
EmailUtils.sendmail(email);
}
原型模式第二种常用方法
如果可以抽象出来,用这种方法也是可以的,不过大多数还是第一种颇多。
public abstract class AbstrractClass implements Cloneable {
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class A extends AbstrractClass{
public static void main(String[] args) throws CloneNotSupportedException {
A a =new A();
a.clone();
}
}
原型模式的坑
原型模式默认都是浅克隆,因此会出现下面的情况。
当我们改a的时候,最后发现b也被更改了。因此我们需要深克隆。所以我们需要对clone方法进行简单的更改。
public abstract class AbstrractClass implements Cloneable {
@Override
protected Object clone() throws CloneNotSupportedException {
A a = (A) super.clone();
//深克隆
a.setLocalDate((Date) a.getLocalDate().clone());
return a;
}
}
通过图片我们能看到对象已经不一样了。其实原型模式也是破坏单例模式的一种方案,这个就不贴图了,解决方案其实和解决反射破坏也很类似。
源码中原型模式的一些实现
网速不好我就不贴太多图了,其中还包括hashmap,redis等等。其中每个实现最重要的都是对于引用的判断,需要去对对象的引用进行思考。