1 基础知识

定义:原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。特征:不需要知道任何创建的细节,不调用构造方法。本质:克隆生成对象。

原型模式会要求对象实现一个可以“克隆”自身的接口,这样就可以通过拷贝或者是克隆一个实例对象本身来创建一个新的实例。如果把这个方法定义在接口上,看起来就像是通过接口来创建了新的接口对象。这样一来,通过原型实例创建新的对象,就不再需要关心这个实例本身的类型,也不关心它的具体实现,只要它实现了克隆自身的方法,就可以通过这个方法来获取新的对象,而无须再去通过new来创建。

几个概念的辨析:

(1)原型模式的功能

原型模式的功能实际上包含两个方面:一个是通过克隆来创建新的对象实例;另一个是为克隆出来的新的对象实例复制原型实例属性的值。原型模式要实现的主要功能就是:通过克隆来创建新的对象实例。一般来讲,新创建出来的实例的数据是和原型实例一样的。但是具体如何实现克隆,需要由程序自行实现,原型模式并没有统一的要求和实现算法。

(2)原型与new

原型模式从某种意义上说,就像是new操作,在前面的例子实现中,克隆方法就是使用new来实现的。但请注意,只是“类似于new”而不是“就是new”。克隆方法和new操作最明显的不同就在于:new一个对象实例,一般属性是没有值的,或者是只有默认值;如果是克隆得到的一个实例,通常属性是有值的,属性的值就是原型对象实例在克隆的时候,原型对象实例的属性的值。

(3)原型实例和克隆的实例

原型实例和克隆出来的实例,本质上是不同的实例,克隆完成后,它们之间是没有关联的,如果克隆完成后,克隆出来的实例的属性值发生了改变,是不会影响到原型实例的。

使用场景:

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

优点:性能比直接new要高、简化了创建过程

缺点:必须配备克隆方法、当进行复杂对象的克隆时要灵活运用深拷贝和浅拷贝,此时可能会引入风险。

2 代码示例

 场景:发送许多封不同的邮件,并保存初始邮件的状态。

邮件类Mail:

/**
* 邮件类
*/
public class Mail {
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();
} }

MailUtil类:

/**
* 邮件工具类
*/
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());
}
}

Test类:

/**
* 应用层调用
*/
public class Test {
public static void main(String[] args) {
Mail mail = new Mail();
mail.setContent("初始化模板");
System.out.println("初始化mail:"+mail);
for(int i = 0;i < 10;i++){
mail.setName("姓名"+i);
mail.setEmailAddress("姓名"+i+"@imooc.com");
mail.setContent("恭喜您,此次慕课网活动中奖了");
MailUtil.sendMail(mail);
}
//保存最开始的邮件模板
MailUtil.saveOriginMailRecord(mail);
}
}

在调用应用层时发现,最后的保存邮件模板保存的是最后一次发送邮件的内容而不是一开始的模板。对于这个问题简单解决就是把这句代码位置放在紧挨着初始化模板后面,但在实际问题中有时还需要保存发送结果等情况,使得MailUtil.saveOriginMailRecord(mail);这句代码的位置不能随便移动那么便有如下代码解决方案:

public class Test {
public static void main(String[] args) {
Mail mail = new Mail();
mail.setContent("初始化模板");
System.out.println("初始化mail:"+mail);
for(int i = 0;i < 10;i++){
Mail mailTemp = new Mail();
mailTemp.setName("姓名"+i);
mailTemp.setEmailAddress("姓名"+i+"@imooc.com");
mailTemp.setContent("恭喜您,此次慕课网活动中奖了");
MailUtil.sendMail(mailTemp);
}
//保存最开始的邮件模板
MailUtil.saveOriginMailRecord(mail);
}
}

但如果Mail的构建十分复杂,那么for循环中不断的new的确不是一种性能良好的方法,此时便需要用到原型模式了。

对Mail类改造:实现Cloneable接口并重写方法。

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();
}
}

在应用层:

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+"@imooc.com");
mailTemp.setContent("恭喜您,此次慕课网活动中奖了");
//发送的是克隆的mailTemp
MailUtil.sendMail(mailTemp);
}
//保存最初的mail
MailUtil.saveOriginMailRecord(mail);
}
}

3 深拷贝与浅拷贝

Pig类:

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 +
'}'+super.toString();
}
}

应用层:

public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Date birthday = new Date(0L);
Pig pig1 = new Pig("佩奇",birthday);
Pig pig2 = (Pig) pig1.clone(); //输出克隆后的对象
System.out.println(pig1);
System.out.println(pig2); pig1.getBirthday().setTime(666666666666L); //输出pig1修改后的对象
System.out.println(pig1);
System.out.println(pig2); }
}

此时便出现了问题:克隆后更加toString()输出的字节码16进制数的确pig1 和 pig2 是不同的对象,但当pig1的生日变化后发现pig2也跟着变化,这是不应该发生的。debug后发现pig1和pig2的Date对象都是425因此一个变化另一个必然变化,这里就是发生了浅拷贝。

对Pig进行改造,对于引用的对象再进行一次单独的克隆

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
protected Object clone() throws CloneNotSupportedException {
Pig pig = (Pig)super.clone(); //深拷贝,单独为生日实现克隆方法
pig.birthday = (Date) pig.birthday.clone();
return pig;
} @Override
public String toString() {
return "Pig{" +
"name='" + name + '\'' +
", birthday=" + birthday +
'}'+super.toString();
}
}

4 克隆破坏单例模式

先放着

5 源码中的使用

ArrayList类:

 //在ArrayList中实现了Cloneable接口
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
//重写clone方法
public Object clone() {
try {
ArrayList<?> v = (ArrayList<?>) 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(e);
}
}

同样还有在HashMap类中:

    @Override
public Object clone() {
HashMap<K,V> result;
try {
//克隆对象
result = (HashMap<K,V>)super.clone();
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
result.reinitialize();
//在克隆的对象存放元素
result.putMapEntries(this, false);
return result;
}

6  相关模式

(1)原型模式和抽象工厂模式

功能上有些相似,都是用来获取一个新的对象实例的。 不同之处在于,原型模式的着眼点是在如何创造出实例对象来,最后选择的方案是通过克隆;而抽象工厂模式的着眼点则在于如何来创造产品簇,至于具体如何创建出产品簇中的每个对象实例,抽象工厂模式则不是很关注。正是因为它们的关注点不一样,所以它们也可以配合使用,比如在抽象工厂模式里面,具体创建每一种产品的时候就可以使用该种产品的原型,也就是抽象工厂管产品簇,具体的每种产品怎么创建则可以选择原型模式。

(2)原型模式和生成器模式

这两种模式可以配合使用生成器模式关注的是构建的过程,而在构建的过程中,很可能需要某个部件的实例,那么很自然地就可以应用上原型模式,通过原型模式来得到部件的实例。

0

原型模式(Prototype)---创建型的更多相关文章

  1. 原型模式 prototype 创建型 设计模式(七)

    原型模式  prototype 意图 用原型实例指定需要创建的对象的类型,然后使用复制这个原型对象的方法创建出更多同类型的对象   显然,原型模式就是给出一个对象,然后克隆一个或者更多个对象 小时候看 ...

  2. 设计模式05: Prototype 原型模式(创建型模式)

    Prototype 原型模式(创建型模式) 依赖关系的倒置抽象不应该依赖于实现细节,细节应该依赖于抽象.对所有的设计模式都是这样的. -抽象A直接依赖于实现细节b -抽象A依赖于抽象B,实现细节b依赖 ...

  3. Java设计模式05:常用设计模式之原型模式(创建型模式)

    1. Java之原型模式(Prototype Pattern)     原型模式属于对象的创建模式.通过给出一个原型对象来指明所有创建的对象的类型,然后用复制这个原型对象的办法创建出更多同类型的对象. ...

  4. 跟着实例学习设计模式(7)-原型模式prototype(创建型)

    原型模式是创建型模式. 设计意图:用原型实例指定创建对象的类型,并通过拷贝这个原型来创建新的对象. 我们使用构建简历的样例的类图来说明原型模式. 类图: 原型模式主要用于对象的复制.它的核心是就是类图 ...

  5. 原型模式--prototype

    C++设计模式——原型模式 什么是原型模式? 在GOF的<设计模式:可复用面向对象软件的基础>中是这样说的:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象.这这个定义中,最 ...

  6. 谈谈设计模式~原型模式(Prototype)

    返回目录 原型模式是创建型模式的一种,其特点在于通过“复制”一个已经存在的实例来返回新的实例(clone),而不是新建(new)实例.被复制的实例就是我们所称的“原型”,这个原型是可定制的. 原型模式 ...

  7. 设计模式学习之原型模式(Prototype,创建型模式)(5)

    通过序列化的方式实现深拷贝 [Serializable] public class Person:ICloneable { public string Name { get; set; } publi ...

  8. 设计模式(四)原型模式Prototype(创建型)

      设计模式(四)原型模式Prototype(创建型) 1.   概述 我们都知道,创建型模式一般是用来创建一个新的对象,然后我们使用这个对象完成一些对象的操作,我们通过原型模式可以快速的创建一个对象 ...

  9. Prototype,创建型模式

    读书笔记_探索式测试_混合探索式测试   一.测试场景 1.讲述用户故事 2.描述需求 3.演示产品功能 4.演示集成场景 5.描述设置和安装 6.描述警告和出错情况 二.使用基于场景的探索式测试 1 ...

  10. 设计模式系列之原型模式(Prototype Pattern)——对象的克隆

    说明:设计模式系列文章是读刘伟所著<设计模式的艺术之道(软件开发人员内功修炼之道)>一书的阅读笔记.个人感觉这本书讲的不错,有兴趣推荐读一读.详细内容也可以看看此书作者的博客https:/ ...

随机推荐

  1. Centos7 添加开机启动服务

    1.在/usr/lib/systemd/system/下创建服务脚本xxx.service,格式如下: [Unit] Description=Scrapyd After=syslog.target n ...

  2. 第十四章 ZYNQ TIMER定时器中断

      上篇文章实现了了PS接受来自PL的中断,本片文章将在ZYNQ的纯PS里实现私有定时器中断.每隔一秒中断一次,在中断函数里计数加1,通过串口打印输出. 本文所使用的开发板是Miz702 PC 开发环 ...

  3. Class.getResources()和classLoader.getResources()区别

    Class.getResource(String path) path不以’/'开头时,默认是从此类所在的包下取资源: path 以’/'开头时,则是从ClassPath根下获取: package t ...

  4. android中sqlite数据库的基本使用和添加多张表

    看了很多关于android使用sqlite数据库的文章,很多都是介绍了数据库的建立和表的建立,而表通常都是只建立一张,而实际情况我们用到的表可能不止一张,那这种情况下我们又该怎么办呢,好了,下面我教大 ...

  5. NameValuePair 简单名称值对节点类型

    /// <summary> /// 组装普通文本请求参数用于post请求 /// </summary> /// <param name="parameters& ...

  6. set-cookie中的SameSite属性

    原文:set-cookie中的SameSite属性 再见,CSRF:讲解set-cookie中的SameSite属性 2016-04-14 13:18:42 来源:360安全播报 作者:暗羽喵 阅读: ...

  7. 出现 HTTP 错误 500.19 错误代码 0x800700b7

    这个内容出现主要问题是在IIS上,我们一般程序开发 iis中默认的路径只是http://localhost/,相当于环境变量中已定义好了,如果自己创建的项目直接将路径定义到这,就会替换图二中的路径,然 ...

  8. 手工实现HttpBasic校验

      HttpBasic: 是RFC中定义的一种控制HTTP协议访问资源的方式.具体当HTTP请求受限资源时,就需要在请求头中添加以"Authorization"为key的heade ...

  9. LEANGOO用户设置

    转自:https://www.leangoo.com/leangoo_guide/leangoo_guide_kanban_user.html#toggle-id-7 1. 点击屏幕右上角头像或用户名 ...

  10. 常见python面试题-手写代码系列

    1.如何反向迭代一个序列 #如果是一个list,最快的方法使用reversetempList = [1,2,3,4]tempList.reverse()for x in tempList:    pr ...