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. 【jmeter测试范例】001——TCP测试

    1.打开Jmeter(或者运行NewDriver.java启动Jmeter) 2.新建一个测试计划 ······ 3.新建线程组 4.设置线程组的参数 1.线程的数量 2.要在多久内完成,即每个请求发 ...

  2. 剑指offer17:输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)

    1 题目描述 输入两棵二叉树A,B,判断B是不是A的子结构.(ps:我们约定空树不是任意一个树的子结构) 2 思路和方法 (1)先在A中找和B的根节点相同的结点 (2)找到之后遍历对应位置的其他结点, ...

  3. k-近邻(KNN) 算法预测签到位置

    分类算法-k近邻算法(KNN): 定义: 如果一个样本在特征空间中的k个最相似 (即特征空间中最邻近) 的样本中的大多数属于某一个类别,则该样本也属于这个类别 来源: KNN算法最早是由Cover和H ...

  4. LC 394. Decode String

    问题描述 Given an encoded string, return its decoded string. The encoding rule is: k[encoded_string], wh ...

  5. PHP无限极菜单

    权限表结构 CREATE TABLE `blog_auth` ( `id` ) unsigned NOT NULL AUTO_INCREMENT COMMENT '序号', `pid` ) NOT N ...

  6. User space(用户空间) 与 Kernel space(内核空间)

    出处: User space 与 Kernel space (整理)用户空间_内核空间以及内存映射 学习 Linux 时,经常可以看到两个词:User space(用户空间)和 Kernel spac ...

  7. Unity Button延迟功能

    有时候Button点下去不是要求立即反应的,而是先有个特别短的动画,再反应. 实现: 继承Button,然后重写一下OnPointerClick,利用协程来延迟. using System.Colle ...

  8. input type=color 设置颜色

    在设置背景色的时候,使用html5 type=color 标签,但是初始值一直都是黑色的,背景如果没有设置的时候,应该是白色,比如文本图元,所以需要设置一个初始的颜色值, 注意: value不实用,怎 ...

  9. idea控制台乱码修改

    我的idea当前版本是2019.2.2 试了很多,只有这个有效果 工具类→HELP→Edit Custom VM OPtions中加 -Dfile.encoding=utf-8 然后重启IDEA 这个 ...

  10. Django的请求生命周期与中间件中的5中方法

    请求生命周期: 客户端——>WSGI——> 中间件——>路由匹配——>视图函数——>WSGI——>客户端 中间件: 在全局层明处理请求和响应的 form djang ...