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. 【AtCoder】ARC059

    为啥这场ARC那么水--一个点就切完了 ARC059 C - いっしょ / Be Together 枚举就行 #include <bits/stdc++.h> #define fi fir ...

  2. Django 在Python3.5 下报 没有模块MySQLdb

    在整个项目站点下的__init__.py 文件里(即和setting.py在同一个文件下)写入以下代码: import pymysql pymysql.install_as_MySQLdb() 需要提 ...

  3. Django-djangorestframework-响应模块

    响应模块 一般都用 Response 对象来做返回(最后一定是打包成符合 HTTP 协议的数据格式来传输,Response 类做了一系列处理,所以这里我们只需要关注下它的那些参数即可) 响应类构造器 ...

  4. ingress之tls和path使用

    ingress tls 上节课给大家展示了 traefik 的安装使用以及简单的 ingress 的配置方法,这节课我们来学习一下 ingress tls 以及 path 路径在 ingress 对象 ...

  5. Centos 7.3 搭建php7,mysql5.7,nginx1.10.1,redis

    一.安装nginx 更新系统软件(非必要) # yum update 安装nginx 1.下载nginx # wget http://nginx.org/download/nginx-1.15.2.t ...

  6. oracle学习笔记03

    一:表空间 /* 创建表空间:逻辑单位,通常我们新建一个项目,就会去创建表空间,在表空间中创建用户,用户去创建表. 语法:create tablespace 表空间名字 datafile '文件的路径 ...

  7. hdu 2846 字典树变形

    mark: 题目有字串匹配的过程 有两点 1.为了高效的匹配子串 可以把所有的子串都预处理进去 然后字典树计数就放在最后面 2.在同一个母串处理自串的时候 会有重复的时候 比如abab  这里去重用个 ...

  8. javascript——HTML对象

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  9. 关键字:for_each

    std::for_each 先贴cppreference中对for_each的概述: template< class InputIt, class UnaryFunction > //此处 ...

  10. div可以同时设置背景图片和背景颜色吗?

    前言 当然可以同时设置 当图片背景色不透明时 情况一:当图片的长.宽 >= div的长.宽时 我们最终看到div背景是图片,之所以说是最终看到,是因为在页面加载时,我们先看到的div背景是颜色, ...