1.定义
- 用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
- 是一种创建型设计模式,允许一个对象在创建另外一个可以可定制的对象,无需知道如何创建的细节。
2.工作原理
通过将一个原型对象传给那个要创建的对象,此对象通过请求原型对象拷贝本身来实现创建,即对象.clone()方法,通过该方法进行对象的拷贝。
3.通用类图
- 类图说明
- Prototype:原型类,声明一个克隆自己的接口。
- ConcretePrototype:具体的原型类,实现一个克隆自己的操作。
- Client:让一个原型对象克隆自己,从而创建一个新的对象。
4.通用代码
实现一个接口,然后重写clone方法即可完成原型模式。
public class PrototypeClass implements Cloneable {
//重写父类Object方法
@Override
public PrototypeClass clone(){
PrototypeClass prototypeClass = null;
try {
prototypeClass = (PrototypeClass) super.clone();
} catch (CloneNotSupportedException e) {
//异常处理
}
return prototypeClass;
}
}
5.应用
优点
- 性能优良:原型模式是在内存二进制流的拷贝,比直接new对象性能更好,在一个循环体中需要产生大量对象时,原型模式更能体现出其性能优良。
- 逃避构造函数的约束:直接在内存中拷贝,构造函数不会执行。
缺点
需要为每一个类配备一个克隆方法,对已有类进行改造时,需要修改其源代码,违背了开闭原则(OCP原则)。
使用场景
- 资源优化场景:类初始化需要消耗大量资源。
- 性能和安全要求场景:通过new一个对象需要非常繁琐的数据准备或访问权限。
- 一个对象多个修改者场景:一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以使用原型模式拷贝多个对象供调用者使用。
在实际项目中原型模式一般和工厂方法模式相结合,通过clone方法创建一个对象,然后由工厂方法提供给调用者。
原型模式在Spring框架中的源码分析
- Spring中原型Bean的创建应用了原型模式。
- 代码:
- beans.xml:
<bean id="id01" class="spring.bean.Monster" scope="prototype"/>
- Test.java:
ApplicationContext applicationContext = new ClassPathXmlApplication("beans.xml");
//通过ID获取monster
Object bean = applicationContext.getBean("id01");
System.out.println("bean" + bean);
@Override
public Object getBean(String name) throws BeansException{
return doGetBean(name,null,null,false)
}
6.浅拷贝和深拷贝
浅拷贝
-
对于数据类型是基本数据类型的成员变量,浅拷贝会直接将该属性值复制一份给新的对象。
-
对于数据类型是引用数据类型的成员变量,浅拷贝会将该成员变量的引用值(内存地址)复制一份给新的对象,实际上两个对象的该成员变量都指向同一个实例。在此情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量的值。
-
浅拷贝使用默认的clone方法实现:sheep = (Sheep)super.clone;
深拷贝
- 复制对象的所有基本数据类型的成员变量值。
- 为所有的引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象,即深拷贝会对整个对象(包括对象的引用数据类型)进行拷贝。
- 可以通过重新clone方法和对象序列化—自己写二进制流来操作(推荐)来实现深拷贝。
- 示例代码:
- 序列化拷贝方法:
public Object deepClone(){
//创建流对象
ByteArrayOutputStream bos = null;
ObjectOutputStream oos = null;
ByteArrayInputStream bis = null;
ObjectInputStream ois = null;
try {
//序列化
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
oos.writeObject(this); //当前对象以对象流方式输出
//反序列化
bis = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bis);
DeepProtoType copyObj = (DeepProtoType)ois.readObject();
return copyObj;
}catch (Exception e){
e.printStackTrace();
return null;
}finally {
//关闭流
try {
bos.close();
oos.close();
bis.close();
ois.close();
}catch (Exception e2){
System.out.println(e2.getMessage());
}
}
}
7.注意事项
构造函数不会被执行
因为Object类的clone方法是从堆内存中以二进制流的方式进行拷贝,重新分配一个内存块,所以构造函数不会被执行。
clone方法和final关键字
要使用clone方法,类的成员变量上不要增加final关键字。
8.应用实例——个性化电子账单
- 发送电子账单类图:
- 广告信模版代码:
public class AdvTemplate {
//广告信名称
private String advsubject = "XX银行国庆信用卡抽奖活动";
//广告信内容
private String advContext = "国庆抽奖活动通知:刷卡有奖!";
//获取广告信名称
public String getAdvsubject() {
return advsubject;
}
//获取广告信内容
public String getAdvContext() {
return advContext;
}
}
- 邮件类代码:
public class Mail implements Cloneable{
//收件人
private String receiver;
//邮件名称
private String subject;
//称谓
private String appellation;
//邮件内容
private String contxt;
//邮件尾部,一般为版权信息
private String tail;
//构造器
public Mail(AdvTemplate advTemplate){
this.contxt = advTemplate.getAdvContext();
this.subject = advTemplate.getAdvsubject();
}
@Override
public Mail clone(){
Mail mail = null;
try {
mail = (Mail) super.clone();
} catch (CloneNotSupportedException e){
e.printStackTrace();
}
return mail;
}
public String getReceiver() {
return receiver;
}
public void setReceiver(String receiver) {
this.receiver = receiver;
}
public String getSubject() {
return subject;
}
public void setSubject(String subject) {
this.subject = subject;
}
public String getAppellation() {
return appellation;
}
public void setAppellation(String appellation) {
this.appellation = appellation;
}
public String getContxt() {
return contxt;
}
public void setContxt(String contxt) {
this.contxt = contxt;
}
public String getTail() {
return tail;
}
public void setTail(String tail) {
this.tail = tail;
}
}
- 场景类:
import java.util.Random;
public class Client {
//发送账单的数量
private static int MAX_COUNT = 6;
public static void main(String[] args) {
//模拟发送邮件
int i = 0;
//定义模版
Mail mail = new Mail(new AdvTemplate());
mail.setTail("XX银行版权所有");
while (i < MAX_COUNT){
Mail cloneMail = mail.clone();
mail.setAppellation(getRandString(5) + "先生");
mail.setReceiver(getRandString(5) + "@" + getRandString(8)
+ ".com");
//发送邮件
sendMail(mail);
i++;
}
}
//发送邮件
public static void sendMail(Mail mail){
System.out.println("标题:" + mail.getSubject() + "\t收件人:" + mail.getReceiver() + "\t发送成功");
}
//获取指定长度的随机字符串
public static String getRandString(int maxLength){
String source = "abcdefghhdskanjjdajk";
StringBuffer sb = new StringBuffer();
Random rand = new Random();
for (int i = 0; i < maxLength; i++) {
sb.append(source.charAt(rand.nextInt(source.length())));
}
return sb.toString();
}
}