[toc]
简介Prototype
原型模式
:调用object.clone()方法,将对象复制一份,产生一个新的对象,和原有对象一样,然后再修改细节的数据,而不通过new 关键字来产生一个对象,而是通过这种复制的方式产生对象的实现模式叫原型模式.
- 类别
- 浅拷贝
- 深拷贝
- 抽象原型(Prototype)角色:这是一个抽象角色,通常由一个Java接口或Java抽象类实现。此角色给出所有的具体原型类所需的接口。
- 具体原型(Concrete Prototype)角色:被复制的对象。此角色需要实现抽象的原型角色所要求的接口。
- 客户(Client)角色:客户类提出创建对象的请求。
浅拷贝
浅拷贝:只拷贝基本的数据类型,对于数组、容器对象、引用对象都不会拷贝,只是创建对应对象指向这些不能拷贝对象的地址空间
深拷贝
优缺点深拷贝:除拷贝基本数据类型外,还在原型模式中实现了数组、容器对象、引用对象的拷贝
-
优点
性能优良
:相对于new 一个对象来说,它由于是在内存中进行二进制流的拷贝,所以它的性能要好很多,这一点特别体现在循环体中产生大量对象时- 隐藏了创建新实例 的复杂性
- 缺点
- 每一个类必须被配一个clone方法
- 深层复制比较复杂
- 复制对象的结构与数据
- 希望对目标对象的修改稿不影响既有的原型对象
- 创建对象的成本较大的情况,eg:类创建时比较耗时,有各种属性要配置
- 一个对象多个修改者
- 构造函数不会执行
- 原型模式与单例模式冲突:使用原型模式复制对象不会调用类的构造方法,单例模式要保证类的实例的唯一性,所以单例模式不要实现Cloneable
- 原型模式中不要出现final对象
小鸟的名片
大牛:“我们小鸟也是有身份的人哦,你看,你们公司都给你配上名片了。”
小鸟:“牛哥,你就别取笑我了,你又不是不知道我的水平,小菜鸟一枚,名片上印着高级工程师,就代表我水平达到高级了?面子工程,还不容给我来点实际的,比如说,把这经费给我,去买2本专业书籍,或者涨涨工资啊!”
大牛:“哈哈,不错,看来我们的小鸟很务实,今天就教你一手涨工资的技能。”
小鸟:“来吧!来吧!这个我最喜欢了”
大牛:“如果说,你们公司为了响应环保号召,决定使用电子名片,需要的时候,才根据电子名片进行打印,要你来开发这款项目的打印模块时,你打算怎么弄?”
小鸟:“如果要打印电子名片,首先我需要知道名片上有什么内容,就拿我这张名片来说,上面有:公司名称、公司产品、我的名字、电话号码...”,有这些信息,那么我这里就需要一个名片类(主要是我上面说的那些信息 + get/set + toString).
注意:为了解说方便,我们只使用 “公司名称”和“个人名称”这两个字段
public class VisitingCard {
// 公司名称
private String companyName;
// 个人名称
private String userName;
public String getCompanyName() {
return companyName;
}
public void setCompanyName(String companyName) {
this.companyName = companyName;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
@Override
public String toString() {
return "VisitingCard{" +
"companyName='" + companyName + '\'' +
", userName='" + userName + '\'' +
'}';
}
}
小鸟:“之后,我的打印,即客户端调用”
VisitingCard card = new VisitingCard();
card.setCompanyName("XX公司");
card.setUserName("小鸟");
System.out.println(card.toString());
大牛:“如果你们boss要求你打印5个人的名片呢?”
小鸟:“那还不简单,我直接把上面的打印代码,复制5份,就OK了”
大牛:“如果,现在要你你打印100张呢?难道你复制100份?”
小鸟:“我这一时图快,当然不能这么弄了,我可以将它用for循环包含起来的,由于打印的是5个不同人的名片,他们除了名字不同外,都是我们公司的,所以我这里还要声明一个数组,用来存放这5个人的名字”
说完,小鸟就重新写下了自己的代码.
String[] userNames = {"小鸟", "张三","李四","王五","赵六"};
for (int i = 0; i < userNames.length; i++) {
VisitingCard card = new VisitingCard();
card.setCompanyName("XX公司");
card.setUserName(userNames[i]);
System.out.println(card.toString());
}
大牛:“这还有点样子,你还记得你刚才所说的,名片上其他信息都一样,就是名字不同吗?”
小鸟:“记得啊,这里有什么问题吗?”
浅拷贝
大牛:“听说过克隆吗?”
小鸟:“听过,当然听过,记得初中生物课就有一节课是关于克隆的。这个跟我们今天的涨工资技能有关?”
大牛:“小鸟,不错呦!其实我们程序中也有克隆一词,它提供的对外接口就是Cloneable
”
第1步:实现Cloneable
接口
实现Cloneable --> 覆写clone()
小鸟:“你是说让我用名片类去实现Cloneable”
小鸟:“牛哥,是这样吗?”
public class VisitingCard implements Cloneable{
// 公司名称
private String companyName;
// 个人名称
private String userName;
@Override
public VisitingCard clone() {
VisitingCard visitingCard = null;
try {
visitingCard = (VisitingCard) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return visitingCard;
}
public String getCompanyName() {
return companyName;
}
public void setCompanyName(String companyName) {
this.companyName = companyName;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
@Override
public String toString() {
return "VisitingCard{" +
"companyName='" + companyName + '\'' +
", userName='" + userName + '\'' +
'}';
}
}
第2步:客户端调用
String[] userNames = {"小鸟", "张三","李四","王五","赵六"};
VisitingCard cardClone = new VisitingCard();
cardClone.setTelPhones("我是电话号码");
for (int i = 0; i < userNames.length; i++) {
// 调用clone方法创建对象,而不是new
VisitingCard card = cardClone.clone();
card.setCompanyName("XX公司");
card.setUserName(userNames[i]);
System.out.println(card.toString());
}
深拷贝
大牛:“嗯嗯,悟性不错! 有没有想过给它增加电话号码?”
小鸟:“这还不容易,电话号码,像有在家那个地区用的,有在公司的所在地区用的,所以我们一般会有多个,故此,声明一个list对象用来存放电话号码,然后生命其get和set方法就ok了。”
大牛:“你要不要写写试一下.”
小鸟:“写就写。”
public class VisitingCard implements Cloneable{
// 公司名称
private String companyName;
// 个人名称
private String userName;
private ArrayList<String> telPhones = new ArrayList<>();
@Override
public VisitingCard clone() {
VisitingCard visitingCard = null;
try {
visitingCard = (VisitingCard) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return visitingCard;
}
public String getCompanyName() {
return companyName;
}
public void setCompanyName(String companyName) {
this.companyName = companyName;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public List<String> getTelPhones() {
return telPhones;
}
public void setTelPhones(String addString) {
this.telPhones.add(addString);
}
@Override
public String toString() {
return "VisitingCard{" +
"companyName='" + companyName + '\'' +
", userName='" + userName + '\'' +
", telPhones=" + telPhones +
'}';
}
}
测试代码
String[] userNames = {"小鸟", "张三","李四","王五","赵六"};
VisitingCard cardClone = new VisitingCard();
for (int i = 0; i < userNames.length; i++) {
// 调用clone方法创建对象,而不是new
VisitingCard card = cardClone.clone();
card.setCompanyName("XX公司");
card.setUserName(userNames[i]);
card.setTelPhones("139-0322-345"+i);
System.out.println(card.toString());
}
小鸟:“牛哥,貌似我开始的思路不对耶,你看结果这里,电话号码,我本来是每人设置了一个,但是随着for循环执行次数的增加,电话号码也跟着增加了,是不是我的分析有问题啊!”
大牛:“你前面的分析完全没问题,只是你需要改一下覆写的clone方法,让list对象也调用一下其clone 方法”
@Override
public VisitingCard clone() {
VisitingCard visitingCard = null;
try {
visitingCard = (VisitingCard) super.clone();
// 给list对象添加clone方法
this.telPhones = (ArrayList<String>) this.telPhones.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return visitingCard;
}
大牛:“你再运行试试。”
小鸟:“咦!这次是对的,每人只有一个号码,这是为啥呢,为啥我要调用list对象的clone方法呢?”
大牛:“这里就涉及到两个概念,浅拷贝与深拷贝,浅拷贝就是你前面的那种实现方式,它进行拷贝的时候,数组,引用类型这些数据是不会拷贝的。”
小鸟:“既然不会拷贝,那为什么我还能用呢?”
大牛:“这是因为,拷贝的时候,它会创建一个变量指向数组,引用类型这些数据所在的地址,其实就是相当于拷贝后的对象和拷贝前的对象共用了一个地址,这也就是为什么你会看到电话号码随for循环次数增加时,也会增加的原因,因为它们改变的是一处地址空间。”
小鸟:“那后面修改clone方法修改list对象,调用其clone 方法是不是就是就是叫深拷贝。”
大牛:“是的,我们小鸟挺有老板天分啊,挺会总结的.”