Java设计模式之原型模式

原型模式简介

原型模式实际上不算一种设计模式,应该说是一种技巧吧。当我们需要创建与已有对象一样的对象时,我们通常可以有两种容易想到的方法,一种是将已有对象指向另外一个重新创建的对象,如

//将old赋给new
Object newObject=oldObject;

这种做法是相当于newObject还是指向oldObject的地址,也就是说,二者实际上是一样的,未来也是一样的,随便对哪个对象进行更改,二者都会保持一致,因为可以把它们看做两个相同的“指针”;另外一种常见的做法是,重新创建一个对象,用new来实例化,这样就创建了另外一个对象,即向内存中再写入了一个对象,虽然内容一样,但地址不一样,但是这种做法费力,如果对象比较复杂的话。

原型模式在这种需求下就诞生了,我们知道Object乃一切对象的父类(超类),并且Object有一个原生的clone方法,但是该方法的调用必须要求类实现了Cloneable接口,虽然Cloneable接口只是一个摆设,里面空空荡荡,姑且就当Cloneable接口是clone方法实现的一个标志吧!我们可以创建一个类实现Cloneable即可,在覆写clone方法即可完成该类的克隆了。

原型模式的代码实现

下面写一个Dog类,该类实现了Cloneable接口,并且覆写了超类的clone方法,里面使用了super.clone,表示调用超类的原生代码即可。注意,Dog类有一个非基本数据类型的变量eye,下面会介绍这个点。

浅复制

package com.prototype;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class Dog implements Cloneable,Serializable {

	/**
	 *
	 */
	private static final long serialVersionUID = -2050795770781171788L;

	private String name;

	Eye eye;

	public Dog(Eye eye) {
		this.eye=eye;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name=name;
	}

	@Override
	protected Object clone() throws CloneNotSupportedException {
		Dog dog;
		dog=(Dog) super.clone();
		return dog;
	}
}
class Eye implements Serializable{
	/**
	 *
	 */
	private static final long serialVersionUID = -2723012171722328322L;
	String name;
	public Eye(String name) {
		this.name=name;
	}
}

原型模式中的复制方法

在上面的原型模式代码中,我们覆写了clone方法,下面我们来进行一个测试:

测试代码一:

package com.prototype;

/**
 * @author zzw922cn
 *
 */
public class Test1 {

	public static void main(String[] args) throws CloneNotSupportedException {
		Dog dog = new Dog(new Eye("红眼睛"));
		dog.setName("狗一");

		Dog object1 = (Dog) dog.clone();

		object1.eye.name="绿眼睛";
		object1.setName("狗二");

		System.out.println(dog.eye.name);
		System.out.println(object1.eye.name);

		System.out.println(dog.getName());
		System.out.println(object1.getName());

		System.out.println(object1.equals(dog));
		System.out.println(object1==dog);
		System.out.println(object1.getClass().equals(dog.getClass()));
	}
}

在上面的代码中可以看到,object1是dog的克隆对象,当我们克隆完成以后,再对object1进行调用相关设置,改变其Eye类型的变量以及String类型的变量,会发生什么呢?dog是否会发生变化呢?并且object1与dog是否一样呢(equals和==)?它们是否属于同样的类呢?最后一个问题是毋庸置疑的,肯定是同一个类。

运行结果

绿眼睛
绿眼睛
狗一
狗二
false
false
true

从运行结果中可以看到,在object1修改了eye对象以后,dog的eye对象的name也自动由红眼睛变为绿眼睛,但是object1修改了String类型的name对象后,dog却保持原有的name对象。这二者有什么联系或区别吗?联系是String和Eye都是非基本数据类型,Java的八大基本数据类型有char,byte,int,short,long,float,double,boolean。区别是既然同属非基本数据类型,但是一个跟随克隆对象变化而变化,另外一个却保持不变,这是很奇怪的。因为它是String类型,String是一个例外。因此,总结一下clone方法,我们得知克隆对象的基本数据类型字段是原有对象字段的复制,但是非基本类型(String除外)并没有复制,而是对原有对象的非基本类型的一个引用罢了,这种情况正如博文一开始中的newObject与oldObject两者的关系。而String类型则是一个例外,它是对原有对象的一个复制,并非指向原有的String对象的地址。

这种clone一般称为浅复制,即并没有完全复制!

测试代码二

下面来进行一次深复制,即完全复制。我使用流的方式来进行深度复制,即完全拷贝。

深复制

package com.prototype;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class Dog implements Cloneable,Serializable {

	/**
	 *
	 */
	private static final long serialVersionUID = -2050795770781171788L;

	private String name;

	Eye eye;

	public Dog(Eye eye) {
		this.eye=eye;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name=name;
	}

	@Override
	protected Object clone() throws CloneNotSupportedException {
		Dog dog;
		dog=(Dog) super.clone();
		return dog;
	}

	/* 深复制 */
    public Object deepClone() throws IOException, ClassNotFoundException {  

        /* 写入当前对象的二进制流 */
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(this);  

        /* 读出二进制流产生的新对象 */
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);
        return ois.readObject();
    } 

}
class Eye implements Serializable{
	/**
	 *
	 */
	private static final long serialVersionUID = -2723012171722328322L;
	String name;
	public Eye(String name) {
		this.name=name;
	}
}

接着写一个类似的测试代码

package com.prototype;

import java.io.IOException;

public class Test2 {
/**
 * equal强调内容是否相同
 * =强调地址是否相同
 * @param args
 * @throws CloneNotSupportedException
 * @throws IOException
 * @throws ClassNotFoundException
 */
	public static void main(String[] args) throws CloneNotSupportedException, ClassNotFoundException, IOException {
		Dog dog = new Dog(new Eye("红眼睛"));
		dog.setName("狗一");

		System.out.println("-----------------深复制--------------");
		Dog object2 = (Dog) dog.deepClone();
		object2.eye.name="绿眼睛";
		object2.setName("狗二");

		System.out.println(dog.eye.name);
		System.out.println(object2.eye.name);

		System.out.println(dog.getName());
		System.out.println(object2.getName());

		System.out.println(object2.equals(dog));
		System.out.println(object2==dog);
		System.out.println(object2.getClass().equals(dog.getClass()));

	}
}

运行测试结果:

-----------------深复制--------------
红眼睛
绿眼睛
狗一
狗二
false
false
true

我们看到深度复制,二者便“分道扬镳”了,除了复制之初两者是一样的之外,后续的任何变化都不会对彼此产生任何影响了。这就是深复制。

equals与==的区别

前面我们看到克隆对象与原始对象的equals和==都返回false,无论是浅复制还是深复制。那么equals和==到底是什么关系呢?

对于基本数据类型,==比较的是它们的值。而对于非基本类型的对象,==比较是它们在内存中的地址,如之前的oldObject与newObject二者的地址相同;而equals方法,如果它没有被子类覆写,它最原始的也是比较对象在内存中的地址,如果被子类覆写了,就不好说了。例如在String类型中,equals方法比较的是字符串的“表面值”,它并不是比较对象在内存中的地址,而==比较的是两个字符串在内存中的地址是否一样。例如String str1="Java",String str2=new String("Java");那么二者的关系是str1!=str2,str1.equals(str2)=true。原因是str1存在于字符串常量池中,str2存在于Java堆中,二者地址当然不同;但是二者的表面值是一样的,都是Java,因此equals方法返回true。

时间: 2024-08-23 04:44:04

Java设计模式之原型模式的相关文章

图解Java设计模式之原型模式

图解Java设计模式之原型模式 克隆羊的问题 原型模式 - 基本介绍 原型模式在Spring框架中源码分析 浅拷贝的介绍 深拷贝基本介绍 克隆羊的问题 现在有一只羊tom,姓名为 : tom,年龄为 :1,颜色为 :白色,请编写程序创建和tom羊属性完全相同的10只羊. 传统方式解决克隆羊的问题 package com.example.demo.prototype; public class Sheep { private String name; private int age; privat

Java设计模式四: 原型模式(Prototype Pattern)

网上找了好多这个模型的资料说的都不透彻,看了半天都是云里雾里.只好自己操刀研究一把. 原型模式是一种创建型设计模式,它通过复制一个已经存在的实例来返回新的实例,而不是新建实例.被复制的实例就是我们所称的原型,这个原型是可定制的.原型模式多用于创建复杂的或者耗时的实例, 因为这种情况下,复制一个已经存在的实例可以使程序运行更高效,或者创建值相等,只是命名不一样的同类数据. 原型模式中的拷贝分为"浅拷贝"和"深拷贝":浅拷贝: 对值类型的成员变量进行值的复制,对引用类型

JAVA设计模式之 原型模式【Prototype Pattern】

一.概述: 使用原型实例指定创建对象的种类,而且通过拷贝这些原型创建新的对象. 简单的说就是对象的拷贝生成新的对象(对象的克隆),原型模式是一种对象创建型模式. 二.使用场景: 创建新的对象能够通过对已有对象进行复制来获得,假设是相似对象,则仅仅需对其成员变量稍作改动. 三.UML结构图: 四.參与者 (1)    Prototype(抽象原型类):它是声明克隆方法的接口,是全部详细原型类的公共父类,能够是抽象类也能够是接口,甚至还能够是详细实现类. (2)    ConcretePrototy

java设计模式之五原型模式(Prototype)

原型模式虽然是创建型的模式,但是与工程模式没有关系,从名字即可看出,该模式的思想就是将一个对象作为原型,对其进行复制.克隆,产生一个和原对象类似的新对象.本小结会通过对象的复制,进行讲解.在Java中,复制对象是通过clone()实现的,先创建一个原型类: [java] view plaincopy public class Prototype implements Cloneable { public Object clone() throws CloneNotSupportedExcepti

大话设计模式_原型模式(Java代码)

原型模式:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象 简单描述:即通过实现接口Cloneable重写方法clone(),使得创建新的拷贝对象不需要一个成员一个成员的重新复制,而且可以提高创建对象的效率 Java中要想实现拷贝使用clone()方法,类必须实现Cloneable接口,并且重写Object类中的clone()方法,调用父类的clone()方法即可实现浅复制 代码如下: WorkExperience类: 1 package com.longsheng.prototy

深入浅出设计模式 ------ Prototype(原型模式)之深度克隆

继上篇深入浅出设计模式 ------ Prototype(原型模式)的浅克隆实现, 本文进入Prototype(原型模式)的进阶篇----深度克隆. 深度克隆 ---- 序列化方式实现 把对象写到流里的过程是序列化(Serilization)过程,而把对象从流中读出来的过程则叫做反序列化(Deserialization).写在流里的是对象的一个克隆(新的, 独立的), 而原对象仍存在于JVM内存模型里.因此, 以下代码采用序列化方式实现深度克隆. 第一步: 将上篇的代码做些许改动, 加入对象引用

java设计模式5--原型模式(Prototype)

本文地址:http://www.cnblogs.com/archimedes/p/java-prototype-pattern.html,转载请注明源地址. 原型模式 用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象. 概述 原型模式是从一个对象出发得到一个和自己有相同状态的新对象的成熟模式,该模式的关键是将一个对象定义为原型,并为其提供复制自己的方法. java.lang.Object类的clone方法 参见<java中的深浅克隆> 适用性 1.当一个系统应该独立于它的产品创建

折腾Java设计模式之备忘录模式

原文地址:折腾Java设计模式之备忘录模式 备忘录模式 Without violating encapsulation, capture and externalize an object's internal state allowing the object to be restored to this state later. 在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态. 所谓备忘录模式就是在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个

创建型设计模式 之 原型模式

同为创建型模式的原型模式与单例模式是密不可分的,这也是最常用的设计模式之一. 原型模式是一种非常简单的设计模式.这里除了基本介绍和演示,还详细介绍了Java中原型模式的本质. 一.介绍 同样,先来看一下<研磨设计模式>的定义——用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象. 原型模式的本质——克隆生成对象. 那么原型模式是什么意思呢?说白了就是克隆自身.我们知道Java中没有引用这个概念,Java用变量名代表引用.像 Apple a = new Apple();我们知道,想要操