一、概念

  用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。

二、模式动机

  当已有一个对像,暂且称之为原型对象,需要一个新的对像,该对像和已有的原型对像具有相同的类型,且里面的属性大部分相同,或者只有个别不同时,这时就可以用原型模式,克隆原型对像,产生一个新的对像,并对新的对像属性进行适当的修改,已适应系统需求。比如新生产的同一批次同一型号的笔记本电脑,如果每个电脑都是一个实例对像,这些电脑配置都是相同的,唯一不同可能是序列号不同,如何实例化所有的电脑,这时就可以用一个原型电脑,去克隆出所有的电脑,只需对克隆出来的新电脑设置正确的序列号就可以了。

三、模式的结构

  

  角色分析:

    1.IPrototype:声明一个克隆自身的接口

    2.ConcretePrototype(ConcretePrototypeA,ConcretePrototypeA): 实现了IPrototype接口, 这些类真正实现了克隆自身的功能

    3.Client:让一个原型对象克隆自身产生一个新的对象。

  

样例代码:

package prototype;

/**
* 声明一个克隆自身的接口,所有实现该接口的类实例,都可以克隆自身产生一个新的对象
* @ClassName: IPrototype
* @author beteman6988
* @date 2017年10月23日 下午9:47:35
*
*/
public interface IPrototype { /**
* 实现克隆自身的接口方法
* @Title: clone
* @param @return
* @return IPrototype
* @throws
*/
public IPrototype clone(); }
package prototype;

/**
* 实现了克隆接口的具体实现对象
* @ClassName: ConcretePrototype
* @author beteman6988
* @date 2017年10月23日 下午9:51:13
*
*/
public class ConcretePrototypeA implements IPrototype { /**
* 实现克隆功能的具本方法
*/
@Override
public IPrototype clone() {
//最简单的方式就是new 一个新对像,并一一将自已的属性值复制到新的对像里面
IPrototype cloneObj=new ConcretePrototypeA();
//将本对像的属性值赋于新的对像
//如 cloneObj.setProperty(this.propety); 等
return cloneObj;
} }
package prototype;

/**
* 实现了克隆接口的具体实现对象
* @ClassName: ConcretePrototype
* @author beteman6988
* @date 2017年10月23日 下午9:51:13
*
*/
public class ConcretePrototypeB implements IPrototype { /**
* 实现克隆功能的具本方法
*/
@Override
public IPrototype clone() {
//最简单的方式就是new 一个新对像,并一一将自已的属性值复制到新的对像里面
IPrototype cloneObj=new ConcretePrototypeB();
//将本对像的属性值赋于新的对像
//如 cloneObj.setProperty(this.propety); 等
return cloneObj;
} }
package prototype;

/**
* 客户端程序,从已有实例克隆出一个新的对象
* @ClassName: Client
* @author beteman6988
* @date 2017年10月23日 下午10:06:18
*
*/
public class Client {
private IPrototype instance=new ConcretePrototypeA(); private void operation() {
//从已有实例 instance 克隆出一个新的对象 cloneObj
IPrototype cloneObj=instance.clone();
} }

 

 关于浅度克隆、深度克隆及java对该原型模式的支持

  1.浅度克隆与深度克隆:在讲原型模式是,浅度克隆与深度克隆是逃不开的话题,上面的示例都是浅度克隆,那么什么是浅度克隆和深度克隆呢?

    浅度克隆:只负责按值传递的数据,如基本数据类型和String  ,如果是引用,则原型对像和克隆出的对像指的是同一个引用地址。

     深度克隆:除了浅度克隆要克隆的值外,引用对像也会被克隆,克隆出的对像和原型对像中的引用是不同的,指上不同的地址空间。

  2. java对浅度克隆的支持:java.lang.Object.clone()方法,该方法为protected native方法,表明继承于他的类都可以调用该方法,又由于java中的所有类都继承于java.lang.Object,所以说java中的所有类都可以以super.clone()的方式调用java.lang.Object.clone()方法。当子类通过调用java.lang.Object.clone()方法实现克隆时,子类必须实现Cloneable标识接口,该标识接口的作用就是在运行时通知java虚拟机可以安全的在这个类上使用clone()方法,通过该方法得到一个对像的克隆,如下图所示:

            

  代码如下:

  

package prototype.cloneable;

/**
* 声明一个克隆自身的接口,所有实现该接口的类实例,都可以克隆自身产生一个新的对象
* @ClassName: IPrototype
* @author beteman6988
* @date 2017年10月23日 下午9:47:35
*
*/
public interface IPrototype extends Cloneable { /**
* 实现克隆自身的接口方法
* @Title: clone
* @param @return
* @return IPrototype
* @throws
*/
public IPrototype clone(); }
package prototype.cloneable;
/**
* 实现了克隆接口的具体实现对象
* @ClassName: ConcretePrototype
* @author beteman6988
* @date 2017年10月23日 下午9:51:13
*
*/
public class ConcretePrototypeA implements IPrototype { /**
* 实现克隆功能的具本方法
*/
@Override
public IPrototype clone() { try {
return (IPrototype)super.clone(); } catch ( Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
}
}
package prototype.cloneable;

/**
* 客户端程序 跟据已有实例,克隆出一个新的实例
* @ClassName: Client
* @author beteman6988
* @date 2017年10月23日 下午11:37:18
*
*/
public class Client {
private IPrototype instance=new ConcretePrototypeA();
public void option() {
IPrototype p=instance.clone();
} }

    3.java对深度克隆的支持:在java里面利用串行化Serilization可以实现深度克隆,他要求原型类及原型类里面的引用类都需要实现Serializable标识接口,他的实现思想是通过将原型对像写到流里面,然后再从流里读出来重建对像。还是基于上面的例子,如下图:

      

package prototype;

import java.io.Serializable;

public class A  implements Serializable {

}
package prototype;

import java.io.Serializable;

/**
* 声明一个克隆自身的接口,所有实现该接口的类实例,都可以克隆自身产生一个新的对象
* @ClassName: IPrototype
* @author beteman6988
* @date 2017年10月23日 下午9:47:35
*
*/
public interface IPrototype extends Serializable { /**
* 实现克隆自身的接口方法
* @Title: clone
* @param @return
* @return IPrototype
* @throws
*/
public IPrototype clone(); }
package prototype;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream; /**
* 实现了克隆接口的具体实现对象
* @ClassName: ConcretePrototype
* @author beteman6988
* @date 2017年10月23日 下午9:51:13
*
*/
public class ConcretePrototypeA implements IPrototype { private A a =new A(); /**
* 实现克隆功能的具本方法
*/
@Override
public IPrototype clone() { try {
//将对像写入流中
ByteArrayOutputStream bo=new ByteArrayOutputStream();
ObjectOutputStream oo=new ObjectOutputStream(bo);
oo.writeObject(this); //将对像从流中读出
ByteArrayInputStream bi=new ByteArrayInputStream(bo.toByteArray());
ObjectInputStream oi=new ObjectInputStream(bi);
return (IPrototype)oi.readObject(); } catch ( Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
}
}
package prototype;

/**
* 客户端程序
* @ClassName: Client
* @author beteman6988
* @date 2017年10月23日 下午11:37:18
*
*/
public class Client { public static void main(String[] args) {
IPrototype prototype=new ConcretePrototypeA();
IPrototype copy= prototype.clone();
}
}

通过调试见下图,可以看出copy对象及内部的a 引用对象与原型对象是不同的对象,如下图示:

        

    4.java通过逐级浅克隆实现深度克隆:通过java.lang.Object.clone()对java对像及内部的引用逐级克隆也是可以实现的,难点只是无法确定克隆的层次,因为引用里面还有可能有引用,引用的层次无法确定。 

四、模式样例

  现实生活中复印机和细胞分裂就是很贴切的例子,当我们将一份文件放到复印机中,复印机可以将原件自身复印(克隆)出另一份文件,两份文件内容相同,但是是不同的两个实体。如下图所示:

               

  

package prototype;

/**
* 具有复印功能的接口
* @ClassName: CopyAble
* @author beteman6988
* @date 2017年10月23日 下午11:19:45
*
*/
public interface CopyAble { public CopyAble copy(); }
/**
* 可以复制的一张纸
* @ClassName: Paper
* @author beteman6988
* @date 2017年10月23日 下午11:28:57
*
*/
public class Paper implements CopyAble { private String header; //文件头
private String content; //文件内容
private String footer; //文件尾 /**
* 复制出内容一样的一张纸
*/
@Override
public CopyAble copy() {
Paper anotherPaper=new Paper();
anotherPaper.setHeader(this.header);
anotherPaper.setContent(this.content);
anotherPaper.setFooter(this.footer);
return anotherPaper;
} public String getHeader() {
return header;
} public void setHeader(String header) {
this.header = header;
} public String getContent() {
return content;
} public void setContent(String content) {
this.content = content;
} public String getFooter() {
return footer;
} public void setFooter(String footer) {
this.footer = footer;
} }
/**
* 可以复制的一张纸
* @ClassName: Paper
* @author beteman6988
* @date 2017年10月23日 下午11:28:57
*
*/
public class Paper implements CopyAble { private String header; //文件头
private String content; //文件内容
private String footer; //文件尾 /**
* 复制出内容一样的一张纸
*/
@Override
public CopyAble copy() {
Paper anotherPaper=new Paper();
anotherPaper.setHeader(this.header);
anotherPaper.setContent(this.content);
anotherPaper.setFooter(this.footer);
return anotherPaper;
} public String getHeader() {
return header;
} public void setHeader(String header) {
this.header = header;
} public String getContent() {
return content;
} public void setContent(String content) {
this.content = content;
} public String getFooter() {
return footer;
} public void setFooter(String footer) {
this.footer = footer;
} }
/**
* 可复制的一张照片
* @ClassName: Picture
* @author beteman6988
* @date 2017年10月23日 下午11:30:51
*
*/
public class Picture implements CopyAble { private String data; //照片内容 /**
* 复制出内容一模一样的一张照片
*/
@Override
public CopyAble copy() {
// TODO Auto-generated method stub
return null;
} public String getData() {
return data;
} public void setData(String data) {
this.data = data;
} }
package prototype;

/**
* 复印机类,可以复印一切可以复印的东西(即实现CopyAble的实现类)
* @ClassName: CopyMachine
* @author beteman6988
* @date 2017年10月23日 下午11:34:35
*
*/
public class CopyMachine { /**
* 对传入的可复制对像进行复制
* @Title: runCopy
* @param @param source
* @param @return
* @return CopyAble
* @throws
*/
public CopyAble runCopy(CopyAble source) {
return source.copy();
} }
package prototype;

/**
* 客户端程序 使用复印机,对现有的可复制资料进行复制
* @ClassName: Client
* @author beteman6988
* @date 2017年10月23日 下午11:37:18
*
*/
public class Client { public static void main(String[] args) { CopyMachine machine=new CopyMachine(); Paper aPaper=new Paper();
aPaper.setHeader("文件头内空");
aPaper.setContent("文件内容");
aPaper.setFooter("文件尾内容"); Paper anotherPaper= (Paper) machine.runCopy(aPaper); //复印机复印出另一文件
System.out.println(anotherPaper.getHeader());
System.out.println(anotherPaper.getContent());
System.out.println(anotherPaper.getFooter());
   

      System.out.println(aPaper==anotherPaper);
      System.out.println(aPaper.getClass()==anotherPaper.getClass());


    }

}

运行结果如下:

文件头内空
文件内容
文件尾内容
false
true

 

五、模式的约束

  对于第三方客户提供的实现类,这种类往往无权进行修改,这时如何实现对该类实例的克隆,是相当麻烦,这时可以借助一些通用性比较好的工具类来完成,如apache 的org.apache.commons.beanutils.BeanUtils.copyProperties(Object dest, Object orig)

  对于克隆出的新对象和原对象之间需满足以下条件约束

  1 对于任何对象  x.clone()!=x  克隆对像和原对象不是同一个对像  ,该条件必须满足

  2. x.clone().getClass()=x.getClass() ,克隆对像和原对像的类型必须相同

  3.x.clone().equals(x)  , 可选

  

六、模式的变体与扩展

  在实际的开发中,对于需要单独实现克隆的情况相对较少,经常使用的是一些写好的通用工具类,如apache 的org.apache.commons.beanutils.BeanUtils.copyProperties(Object dest, Object orig) ,其实现原理是通过java的反射机制来实现的,拿来就可以直接使用。

七、与其它模式的关系

  如抽像工厂模式或工厂方法模式,这些模式关注点是要产生什么样的对像,对于如何产生这样的对像,适当的情况下就可以结合原型模式,如通过已有的对像产生想要的对像。

八、模式优缺点

  优点:对客户隐藏具体的实现类型,原型模式的客户端只知道原型接口的类型,并不知道具体的实现类型,从而减少了客户端对这些具体实现类的依赖。

缺点:在实现深度克隆时比较麻烦,对于对像里面有引用,引用里面可能还有引用,每个引用层级的类都必须正确实现clone()方法才能让所有层级的对像正确的实现克隆。

设计模式-原型(prototype)的更多相关文章

  1. Objective-C设计模式——原型Prototype(对象创建)

    1.原型 原型设计模式所谓原型设计模式,其实就是对象复制,这个特性在所有语言基本上都是存在的. 我们知道在OC中,对象赋值其实是对对象的引用复制,其实就是相当于C语言中的指针.创建了一个新的变量,但是 ...

  2. 设计模式--原型(Prototype)模式

    写这些也许有人认为“为了模式而模式”.Insus.NET所想到的,每个大师成为大师之前,也许都得这样做. 走路,从小就开始学,直至现在,谁还不是为了走路而走路?一直重复着...... 很多人没有分享自 ...

  3. 克隆复制可使用原型( Prototype)设计模式

    今天有学习设计模式的原型(Prototype)<设计模式--原型(Prototype)模式>http://www.cnblogs.com/insus/p/4152773.html .为了加 ...

  4. PHP 设计模式 原型模式(Prototype)之深/浅拷贝

      看PHP 设计模式 原型模式(Prototype)时,衍生出一个扩展问题之 原型拷贝的浅拷贝和深拷贝问题(不管写Java还是写PHP还是写JS时都多多少少遇到过对象拷贝问题)   比如写前端页面时 ...

  5. 原型设计模式(prototype

    # 什么是原型设计模式 > 这里与软件工程中的原型开发模式有那么一点类似的地方,我们首先需要构建出一个原型,这个原型可以在现实开发中抽象出来的具体类型,但是这个类型与具体的类又不同,需要抽取公共 ...

  6. PHP设计模式 原型模式(Prototype)

    定义 和工厂模式类似,用来创建对象.但实现机制不同,原型模式是先创建一个对象,采用clone的方式进行新对象的创建. 场景 大对象的创建. 优点 1.可以在运行时刻增加和删除产品 2.可以改变值或结构 ...

  7. Java设计模式-原型模式(Prototype)

    原型模式属于对象的创建模式.通过给出一个原型对象来指明所有创建的对象的类型,然后用复制这个原型对象的办法创建出更多同类型的对象.这就是选型模式的用意. 原型模式的结构 原型模式要求对象实现一个可以“克 ...

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

    1.模式定义: 原型模式就是用原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象. 2.使用场景: 在原型模式中我们可以利用过一个原型对象来指明我们所要创建对象的类型,然后通过复制这个对象的 ...

  9. 4.java设计模式-原型模式(prototype)

    在<JAVA与模式>一书中开头是这样描述原型(Prototype)模式的: 原型模式属于对象的创建模式.通过给出一个原型对象来指明所有创建的对象的类型,然后用复制这个原型对象的办法创建出更 ...

随机推荐

  1. 【新手向】阿里云上ubuntu+flask+gunicorn+nginx服务器部署(二)项目部署

    本项目实现的是类似于ins的图片分享网站.继续(一),当nginx的配置已修改好后,要在远程服务器上部署网站,只需要几个步骤: 1 前期准备 2 将运行网站的代码从github上下载过来 3 下载依赖 ...

  2. Swift5 语言参考(一) 关于语言参考

    本系列文章的这一部分描述了Swift编程语言的形式语法.此处描述的语法旨在帮助您更详细地理解语言,而不是允许您直接实现解析器或编译器. Swift语言相对较小,因为Swift代码中几乎无处不在的许多常 ...

  3. Swift 里 Array (二)初始化

    init() 函数 在 Array 里 public init() { _buffer = _Buffer() } 以Buffer 是 _ContiguousArrayBuffer 为例. 即初始化了 ...

  4. 【sping揭秘】3、Spring容器中bean默认是保持一个实例

    Spring容器中bean默认是保持一个实例 这里做一个测试,基础代码 package cn.cutter.start.provider; import org.springframework.con ...

  5. ASP.NET Core 1.0 中使用 Log 日志配置

    https://github.com/aspnet/Logging https://docs.asp.net/en/latest/fundamentals/logging.html ASP.NET C ...

  6. #ifdef、#ifndef、#else、#endif执行条件编译

         我们开发的程序不只在pc端运行,也要在移动端运行.这时程序就要根据机器的环境来执行选择性的编译,如对PC端编译PC端的程序,对移动端编译移动端的程序,这里我们就可以用两组条件编译.     ...

  7. Docker概念学习系列之为什么使用docker?(3)

    不多说,直接上干货! 见[博主]撰写的https://mp.weixin.qq.com/s/FFSIOSecVdAr_aSDIFZwSA Docker容器虚拟化的优点: (1)环境隔离: 通过cgro ...

  8. 高可用Hadoop平台-集成Hive HAProxy

    1.概述 这篇博客是接着<高可用Hadoop平台>系列讲,本篇博客是为后面用 Hive 来做数据统计做准备的,介绍如何在 Hadoop HA 平台下集成高可用的 Hive 工具,下面我打算 ...

  9. 【详解】核心组件之UserDetailService

    简介 UserDetails => Spring Security基础接口,包含某个用户的账号,密码,权限,状态(是否锁定)等信息.只有getter方法. Authentication => ...

  10. Flutter踩坑日记:解除依赖

    Flutter已经融入工程有一段时间了,由于团队人数较少,所以一直没有管和原有工程解依赖的问题,今天有时间正好把这个问题给搞了. 一.分析 首先,直接忽略上一篇<接入现有iOS项目>的所有 ...