原型模式

定义

原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。可以理解为克隆方法克隆对象。

特点

不需要知道任何创建的细节,不调用构造函数

类型

创建型

适用场景

1
2
3
4
◆类初始化消耗较多资源
◆new产生的一个对象需要非常繁琐的过程(数据准备、访问权限等)
◆构造函数比较复杂
◆循环体中生产大量对象时

优点

  • 原型模式性能比直接new一个对象性能高
  • 简化创建对象的过程

缺点

  • 必须配备克隆方法(重写Object的clone方法,否则不会生效,克隆也是原型模式的核心)
  • 对克隆复杂对象或对克隆出的对象进行复杂改造时,容易引入风险
  • 对复杂对象的深拷贝、浅拷贝要运用得当

扩展

深克隆

1
创建一个新对象,本体对象的引用类型属性需要进行深克隆,这样它就不会再指向原有对象地址。

浅克隆

1
创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。

小结

1
深浅克隆都会在堆中新分配一块区域,它用来指向本体,区别在于对象属性引用的对象是否需要进行克隆(递归性的)

简单需求

某个对象创建的时候相对比较消耗资源,但是这个对象又不得不创建多次,这时可以使用原型模式。原型模式是在内存中进行二进制字节流的拷贝,比new一个对象性能好很多

原型模式实践

使用原型模式之前

邮件类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.design.pattern.prototype;
import lombok.Data;

/**
* Mail
*
* @author shunhua
* @date 2019-09-16
*/
@Data
public class Mail {
/**
* 邮件名
*/
private String name;
/**
* 邮件地址
*/
private String address;
/**
* 邮件内容
*/
private String content;

}

邮件工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.design.pattern.prototype;
import lombok.extern.slf4j.Slf4j;
import java.text.MessageFormat;

/**
* MailUtil
*
* @author shunhua
* @date 2019-09-16
*/
@Slf4j
public class MailUtil {
/**
* 发送邮件
* 重点: 占位符赋值的实现
* @param mail
*/
public static void sendMail(Mail mail){
String content = "向{0},邮件地址:{1},邮件内容:{2}发送邮件";
log.info(MessageFormat.format(content,mail.getName(),mail.getAddress(),mail.getContent()));
}

/**
* 保存邮件的模版内容
* @param mail
*/
public static void mailTemplate(Mail mail){
log.info("邮件的模版内容:" + mail.getContent());
}
}

客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.design.pattern.prototype;
import org.junit.Test;

/**
* Client
*
* @author shunhua
* @date 2019-09-16
*/
public class Client {

@Test
public void test(){
Mail mail = new Mail();
mail.setContent("邮件模版");
for(int i = 0; i < 10; i++){
mail.setName("姓名" + i);
mail.setAddress("姓名" + i + "@" + "gmail.com");
mail.setContent("你收到一封邮件");
MailUtil.sendMail(mail);
}
MailUtil.mailTemplate(mail);
}

}

使用原型模式默认方式(浅拷贝)

邮件类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com.design.pattern.prototype;

import lombok.Data;

/**
* Mail 想要能被克隆需要实现Cloneable接口
*
* @author shunhua
* @date 2019-09-16
*/
@Data
public class Mail implements Cloneable {
/**
* 邮件名
*/
private String name;
/**
* 邮件地址
*/
private String address;
/**
* 邮件内容
*/
private String content;

/**
* 重写克隆方法
* @return
* @throws CloneNotSupportedException
*/
@Override
protected Object clone() throws CloneNotSupportedException {
System.out.println("克隆Mail...");
return super.clone();
}
}

邮件工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.design.pattern.prototype;

import lombok.extern.slf4j.Slf4j;

import java.text.MessageFormat;

/**
* MailUtil
*
* @author shunhua
* @date 2019-09-16
*/
@Slf4j
public class MailUtil {
/**
* 发送邮件
* @param mail
*/
public static void sendMail(Mail mail){
String content = "向{0},邮件地址:{1},邮件内容:{2}发送邮件";
log.info(MessageFormat.format(content,mail.getName(),mail.getAddress(),mail.getContent()));
}

/**
* 保存邮件的模版内容
* @param mail
*/
public static void mailTemplate(Mail mail){
log.info("邮件的模版内容:" + mail.getContent());
}
}

客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.design.pattern.prototype;
import org.junit.Test;

/**
* Client
*
* @author shunhua
* @date 2019-09-16
*/
public class Client {

@Test
public void test() throws CloneNotSupportedException {
Mail mail = new Mail();
mail.setContent("邮件模版");

for(int i = 0; i < 10; i++){
/** 需要创建多个Mail对象,注意不会调用Mail的构造方法,而是调用了Mail中的clone方法 **/
Mail mailTemp = (Mail) mail.clone();
mailTemp.setName("姓名" + i);
mailTemp.setAddress("姓名" + i + "@" + "gmail.com");
mailTemp.setContent("你收到一封邮件");
MailUtil.sendMail(mailTemp);
}
// 得到原始的邮件模版
MailUtil.mailTemplate(mail);
}
}

使用原型模式默认方式(深拷贝)

1
2
3
4
5
6
7
8
对于被克隆的目标对象中有引用类型的属性时,如果不对该引用类型的属性进行克隆处理,那么该属性对于目标对象和克隆得到的新对象都是同一个,这很容易引起问题,一定要注意。这样情况,只需要对这个属性进行浅拷贝处理即可解决。
@Override
protected Object clone() throws CloneNotSupportedException {
Mail mail = (Mail) super.clone();
//深克隆
mail.date = (Date) pig.date.clone();
return mail;
}

原型模式扩展

实体类

1
2
3
4
5
6
7
8
9
10
11
/**
* 一种常用的原型模式
* 通过抽象类来实现原型模式
*/
public abstract class A implements Cloneable{

@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}

继承类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 继承A类,直接调用clone接口
*/
public class B extends A{

public static void main(String [] args){
B b = new B();
try {
b.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
System.out.println("处理异常");
}
}
}

克隆破坏单例

1
2
3
克隆破坏单例的根本原因是,通过反射暴力调用单例类中的clone方法,这样就会得到一个新对象,单例也就变成了多例。
防止克隆破坏单例,只需要让单例类不实现克隆接口即可,或者实现了克隆接口但是克隆方法返回的仍然是同一对象,而不是
处理克隆。

原型模式在源码中的使用

1
可以通过观察Cloneable接口的使用,就可以追踪原型模式是怎样使用的

源码解析1(Object)

1
2
//native 调用非java代码接口 
protected native Object clone() throws CloneNotSupportedException;

源码解析2(ArrayList实现克隆)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable{

/**
* Returns a shallow copy of this <tt>ArrayList</tt> instance. (The
* elements themselves are not copied.)
*
* @return a clone of this <tt>ArrayList</tt> instance
*/
public Object clone() {
try {
@SuppressWarnings("unchecked")
ArrayList<E> v = (ArrayList<E>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError();
}
}

}

源码解析3(MyBatis的cacheKey)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package org.apache.ibatis.cache;

import java.io.Serializable;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;

/**
* @author Clinton Begin
*/
public class CacheKey implements Cloneable, Serializable {

@Override
public CacheKey clone() throws CloneNotSupportedException {
CacheKey clonedCacheKey = (CacheKey) super.clone();
clonedCacheKey.updateList = new ArrayList<Object>(updateList);
return clonedCacheKey;
}
}