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

  原型模式其实就是从一个对象创建另外一个可复制的对象,而且不需要知道任何创建的细节。(最常用的就是基于流的深复制)

原始模型模式

  Java语言本身支持原始原型模式。所有的JavaBean都继承自Java.lang.Object,而Object类提供一个clone方法,可以将一个JavaBean对象复制一份。但是这个JavaBean必须实现一个表示接口Cloneable,表明这个JavaBean支持复制。如果一个对象没有实现这个接口而调用clone()方法,Java编译器会抛出CloneNotSupportedException异常。

变量、对象以及对象的引用

  对象就是类的实例。一般情况下,一个类被实例化时,此类的所有成员,包括变量和方法,都被复制到属于此数据类型的一个新的实例中取。比如:

User user = new User();

上面的语句做了如下的事情:

(1)创建了一个User类型的变量,称为user

(2)建立了一个User类的对象

(3)使变量user指到这个对象上。

可以将上面分成下面两步:

        User user;
user = new User();

  第一行建立了一个变量叫做user,可以指到User类对象上。但在上面第一行结束时并没有指到它上面,只是在第二行才真正指到这样的一个对象上。

  第二行创建了一个对象。当对象刚被创建时是一个无名对象,随后这个对象才有了一个名字user。

  也有可能一个对象被创建之后就没有名字,永远保持无名状态。

        User user = new User();
user.setBirthDay(new Date());

  如上面,我们知道参数的传递是通过引用传递,所以只是将地址传下去。接受参数的setBirthDay 方法也不关心它接收的对象有没有名字。

   因此,对象的创建与它们的引用是独立的

克隆满足的条件

  clone()方法将对象复制了一份并返还给调用者。所谓“复制”的含义与clone()方法是怎么实现的。一般而言,clone()方法满足以下的描述:

  (1)对任何的对象x,都有:x.clone()!=x。换言之,克隆对象与原对象不是同一个对象。

  (2)对任何的对象x,都有:x.clone().getClass() == x.getClass(),换言之,克隆对象与原对象的类型一样。

  (3)如果对象x的equals()方法定义其恰当的话,那么x.clone().equals(x)应当成立的。

  在JAVA语言的API中,凡是提供了clone()方法的类,都满足上面的这些条件。JAVA语言的设计师在设计自己的clone()方法时,也应当遵守着三个条件。一般来说,上面的三个条件中的前两个是必需的,而第三个是可选的。

原始模式结构:

  原型模式有两种表现形式:(1)简单形式;(2)登记形式。

(1)简单形式:

三个角色:

  (1)客户(Client)角色:客户类提出创建对象的请求。

  (2)抽象原型(Prototype)角色:这是一个抽象角色,通常由一个Java接口或Java抽象类实现。此角色给出所有的具体原型类所需的接口。

  (3)具体原型(Concrete Prototype)角色:被复制的对象。此角色需要实现抽象的原型角色所要求的接口。

如下:

package cn.qlq.prototype;

public interface Prototype {

    Prototype clone();
}
package cn.qlq.prototype;

public class ConcretePrototype1 implements Prototype {

    public Prototype clone() {
// 没有属性就不再复制值了
Prototype prototype = new ConcretePrototype1(); return prototype;
} }
package cn.qlq.prototype;

public class ConcretePrototype2 implements Prototype {

    public Prototype clone() {
// 没有属性就不再复制值了
Prototype prototype = new ConcretePrototype2();
return prototype;
} }

客户端代码:

package cn.qlq.prototype;

public class Client {

    private Prototype prototype;

    public Client(Prototype prototype) {
this.prototype = prototype;
} public void operation(Prototype example) {
Prototype copyPrototype = prototype.clone();
} }

(2)登记形式

  作为原型模式的第二种形式,它比简单形式多了一个原型管理器(PrototypeManager)角色,该角色的作用是:创建具体原型类的对象,并记录每一个被创建的对象。

  原型管理器角色保持一个集合,作为对所有原型对象的登记,这个角色提供必要的方法,供外界增加新的原型对象和取得已经登记过的原型对象。

package cn.qlq.prototype;

import java.util.HashMap;
import java.util.Map; public class PrototypeManager { private static Map<String, Prototype> prototypes = new HashMap<String, Prototype>(); private PrototypeManager() {
} public synchronized static void setPrototype(String prototypeId, Prototype prototype) {
prototypes.put(prototypeId, prototype);
} public synchronized static void removePrototype(String prototypeId) {
prototypes.remove(prototypeId);
} public synchronized static Prototype getPrototype(String prototypeId) throws Exception {
Prototype prototype = prototypes.get(prototypeId);
if (prototype == null) {
throw new Exception("不存在");
} return prototype;
}
}

测试代码:

package cn.qlq.prototype;

public class Client {

    public static void main(String[] args) throws Exception {
Prototype p1 = new ConcretePrototype1();
PrototypeManager.setPrototype("p1", p1);
// 获取原型来创建对象并且复制一份
Prototype p3 = PrototypeManager.getPrototype("p1").clone();
System.out.println(p1 == p3);
} }

两种形式的比较:

  如果需要创建的原型对象数目较少而且固定的话,可以采取第一种形式也就是简单形式的原始模型模式。在这种情况下,原型对象的引用由客户端自己保存。

  如果要创建的原型对象数目不固定的话,采用登记形式。保存原对象的引用由管理器角色执行。在复制一个原型对象之前,客户端可以查看管理员对象是否已经有一个满足要求的的原型对象,如果有直接返回,如果没有客户端就需要自行复制此原型对象。

浅复制和深复制:

浅复制
  只负责克隆按值传递的数据(比如基本数据类型、String类型),而不复制它所引用的对象,换言之,所有的对其他对象的引用都仍然指向原来的对象。

深复制
  除了浅度克隆要克隆的值外,还负责克隆引用类型的数据。那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深度克隆把要复制的对象所引用的对象都复制了一遍,而这种对被引用到的对象的复制叫做间接复制。

  深度克隆要深入到多少层,是一个不易确定的问题。在决定以深度克隆的方式复制一个对象的时候,必须决定对间接复制的对象时采取浅度克隆还是继续采用深度克隆。因此,在采取深度克隆时,需要决定多深才算深。此外,在深度克隆的过程中,很可能会出现循环引用的问题,必须小心处理。

利用串行化实现深度克隆:(重要)

  把对象写到流里的过程称为串行化,在Java中又称为"冷冻"或者"腌咸菜";而把对象从流里读出来的并行化过程则叫做"解冻"或者"回鲜"过程。应当指出的是,写到流里的是对象的一个拷贝,而原对象仍然存在于JVM里面,因此"腌咸菜"只是对象的一个拷贝。

  在Java语言里深度克隆一个对象,常常可以先使对象实现Serializable接口,然后把对象(实际上只是对象的拷贝)写到一个流里(序列化),再从流里读回来(反序列化),便可以重建对象。

如下代码:

     public static <T> T cloneObj(T obj) {
T retVal = null; try {
// 将对象写入流中
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(obj); // 从流中读出对象
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais); retVal = (T) ois.readObject();
} catch (Exception e) {
e.printStackTrace();
} return retVal;
}

  这样做的前提就是对象以及对象内部所有引用到的对象都是可序列化的,否则,就需要仔细考察那些不可序列化的对象可否设成transient,从而将之排除在复制过程之外。

  浅复制显然比深复制更容易实现,因为Java语言的所有类都会继承一个clone()方法,而这个clone()方法所做的就是浅复制。

  有一些对象,比如线程(Thread)对象或Socket对象,是不能简单复制或共享的。不管是使用浅复制还是深复制,只要涉及这样的间接对象,就必须把间接对象设成transient而不予复制;或者由程序自行创建出相当的同种对象,权且当做复制件使用。

如下测试代码:

  User是根对象,其内部引用类也要实现Serializable接口,user的name属性用transient关键字修饰不会被序列化。

package cn.qlq.prototype;

import java.io.Serializable;

public class User implements Serializable {

    private Address address;

    private int id;

    private transient String name;
private String sex;
private String job;
private String health;
private String BMI;
private int height;
private int weight; public User() {
super();
} public User(Address address, int id, String name, String sex, String job, String health, String bMI, int height,
int weight) {
super();
this.address = address;
this.id = id;
this.name = name;
this.sex = sex;
this.job = job;
this.health = health;
BMI = bMI;
this.height = height;
this.weight = weight;
} public int getId() {
return id;
} public void setId(int id) {
this.id = id;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public String getSex() {
return sex;
} public void setSex(String sex) {
this.sex = sex;
} public String getJob() {
return job;
} public void setJob(String job) {
this.job = job;
} public String getHealth() {
return health;
} public void setHealth(String health) {
this.health = health;
} public String getBMI() {
return BMI;
} public void setBMI(String bMI) {
BMI = bMI;
} public int getHeight() {
return height;
} public void setHeight(int height) {
this.height = height;
} public int getWeight() {
return weight;
} public void setWeight(int weight) {
this.weight = weight;
} public Address getAddress() {
return address;
} public void setAddress(Address address) {
this.address = address;
} @Override
public String toString() {
return "User [address=" + address + ", id=" + id + ", name=" + name + ", sex=" + sex + ", job=" + job
+ ", health=" + health + ", BMI=" + BMI + ", height=" + height + ", weight=" + weight + "]";
} } class Address implements Serializable { private String province; private String country; public Address(String province, String country) {
super();
this.province = province;
this.country = country;
} public String getProvince() {
return province;
} public void setProvince(String province) {
this.province = province;
} public String getCountry() {
return country;
} public void setCountry(String country) {
this.country = country;
} @Override
public String toString() {
return "Address [province=" + province + ", country=" + country + "]";
} }
    public static void main(String[] args) throws Exception {
User user = new User();
user.setName("张三");
user.setBMI("BMI");
user.setHealth("健康");
user.setHeight(50);
user.setId(1);
Address address = new Address("山西", "cn");
user.setAddress(address); // 克隆对象并修改属性后打印
User cloneedUser = cloneObj(user);
cloneedUser.getAddress().setCountry("北京");
System.out.println(cloneedUser); System.out.println("========");
System.out.println(user);
}

结果:(name属性没有被复制上,而且其address是深复制)

User [address=Address [province=山西, country=北京], id=1, name=null, sex=null, job=null, health=健康, BMI=BMI, height=50, weight=0]
========
User [address=Address [province=山西, country=cn], id=1, name=张三, sex=null, job=null, health=健康, BMI=BMI, height=50, weight=0]

原型模式的优点

  原型模式允许在运行时动态改变具体的实现类型。原型模式可以在运行期间,由客户来注册符合原型接口的实现类型,也可以动态地改变具体的实现类型,看起来接口没有任何变化,但其实运行的已经是另外一个类实例了。因为克隆一个原型就类似于实例化一个类。

原型模式的缺点

  原型模式最主要的缺点是每一个类都必须配备一个克隆方法。配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类来说不是很难,而对于已经有的类不一定很容易,特别是当一个类引用不支持序列化的间接对象,或者引用含有循环结构的时候。

适用场景:

  假设一个系统的产品类是动态加载的,而且产品具有一定的等级结构。这个时候如果使用工厂模式的话,工厂模式就得具有一定的结构。而产品类的等级结构发生变化,工厂类的等级结构就得随之变化。这对于产品结构经常可能会变的系统来说,采用工厂模式就不方便。

  这时候就可以采用原型模式,给每个产品装配一个克隆方法(大多数时候只需给产品类的根类装配克隆方法)。

总结:

  这种模式使用的最多的就是深复制,项目中也没用到这种模式。

原始(Prototype)模式的更多相关文章

  1. 常见设计模式解析和实现(C++)Prototype模式(原型模式)

    作用:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象. UML结构图: 抽象基类: 1)  Prototype:虚拟基类,所有原型的基类,提供Clone接口函数 接口函数: 1)  P ...

  2. Prototype 模式

    Prototype 模式提供了一个通过已存在对象进行新对象创建的接口(Clone) ,Clone()实现和具体的实现语言相关,在 C++中我们将通过拷贝构造函数实现之. /////////////// ...

  3. Java设计模式(4)原型模式(Prototype模式)

    Prototype模式定义:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象. Prototype模式允许一个对象再创建另外一个可定制的对象,根本无需知道任何如何创建的细节,工作原理是: ...

  4. 设计模式之Prototype模式

    通常我们会使用new 类名()的方法会去生成一个新的实例,但在开发过程中,有时候也会有"在不指定类名的前提下生成实例"的需求,那样,就只能根据现有实例来生成新的实例. 有三种情况, ...

  5. Java 实现原型(Prototype)模式

    public class BaseSpoon implements Cloneable {//spoon 匙, 调羹 String name; public String getName() { re ...

  6. 23种设计模式——Prototype模式

    Prototype模式是提供自我复制的功能.包括浅拷贝和深拷贝. 一.Prototype模式的用途 场景1:游戏场景中有很多类似的敌人,它们的技能都一样,但是随着敌人出现的位置和不同,它们的能力也不太 ...

  7. 设计模式(六)Prototype模式

    Prototype模式就是不根据类来生成实例,而是根据实例来生成新实例.至于为什么不能根据类来生成实例,在最后会讲到. 还是根据实例程序来理解这种设计模式吧. 下面是实例代码. package Big ...

  8. 设计模式C++描述----08.原型(Prototype)模式

    一. 概述 定义:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象. 换句话说,就是不用重新初始化对象,而是动态地获得对象运行时的状态. 再说明白点,就是要一个拷贝过构造函数类似功能的接 ...

  9. javascript模式——Prototype模式

    GoF权威的解释是,原型模式是一种通过对一个对象的克隆,创建基于这个对象的多种对象的模式. 为了实现这种原型模式,可以直接使用ECMAScript 5 中的方法Object.create.它不紧可以创 ...

随机推荐

  1. English--辅音

    English|辅音 英语中的辅音,按照发音的松紧,唇形舌位,划分为七大类.需要好好地体会具体的发音部位与口型. 前言 目前所有的文章思想格式都是:知识+情感. 知识:对于所有的知识点的描述.力求不含 ...

  2. 并发编程-线程,JMM,JVM,volatile

    1.线程 相信大家对线程这个名词已经很不陌生了,从刚开始学习java就接触到线程,先说说进程吧,进程就是系统分配资源的基本单位,线程是调度cpu的基本单位,进程由线程组成,一个进程至少又一个线程组成, ...

  3. JS基石之-----防抖节流函数

    防抖和节流函数   阅读目录 一 .防抖函数 二 .节流函数 三 .个人理解两者的区别   一.防抖函数 1.1 概念: 触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算 ...

  4. JavaScript中数组相关的属性方法

    下面的这些方法会改变调用它们的对象自身的值: Array.prototype.copyWithin() 在数组内部,将一段元素序列拷贝到另一段元素序列上,覆盖原有的值. Array.prototype ...

  5. H5新增form控件和表单属性

    第一个知识点:表单的属性及总结 第二个知识点:H5新增的表单控件和属性以及总结 第一个知识点: 我们常见的表单验证有哪些呢 text 文本框标签 password 密码框 checkbox 多选框 r ...

  6. libass简明教程

    [时间:2019-05] [状态:Open] [关键词:字幕,libass,字幕渲染,ffmpeg, subtitles, video filter] 0 引言 libass库则是一个轻量级的对ASS ...

  7. python测试开发django-69.templates模板过滤器filter

    前言 templates 模板里面过滤器 filter 的作用是对变量的出来,比如小写转大写,替换一些特殊字符,对列表取值,排序等操作. 内置的过滤器有很多,本篇拿几个常用的过滤器做案例讲解下相关的功 ...

  8. JanusGraph入门,schema及数据模型

    5.Schema和数据建模 每个JanusGraph都有一个schema,该schema由edge labels,property keys,和vertex组成.JanusGraph schema可以 ...

  9. VMware Workstation创建Windows2012server虚拟机

    镜像文件需要下载到物理机 3.需要输入iso文件 对应的密钥 定义普通的用户名与密码 4.指定按照路径 5. 大概都是下一步 根据提示需要重启 选择带GUI的服务器进行安装,因为windows命令行模 ...

  10. gnome3 调整标题栏高度

    适用于:gtk 3.20 + 1. 在用户主目录 -/.config/gtk3.0/ 下新建gtk.css文件: 2. 复制如下css值: headerbar.default-decoration { ...