- 浏览: 206876 次
- 性别:
- 来自: 上海
博客专栏
-
漫谈设计模式
浏览量:114747
文章分类
最新评论
-
redhat:
...
啤酒能算DevOps中的工具吗? -
ihealth:
...
啤酒能算DevOps中的工具吗? -
redhat:
beiyeren 写道楼主最后去了哪家公司?去了家金融公司,现 ...
和Thoughtworks的一次邂逅(二) -
beiyeren:
楼主最后去了哪家公司?
和Thoughtworks的一次邂逅(二) -
lvliang169:
xuwenjin666 写道为什么电子版里面没有写完啊?
那是 ...
新书上架,《漫谈设计模式——从面向对象开始》(有电子版)
5.1 概述
谈到原型模式,学过Java的人可能会想到java.lang.Cloneable这个接口,以为Java的原型模式描述的就是java.lang.Cloneable接口的使用,这就大错特错了。其实,原型模式在我们日常生活中经常可以看到,比如你刚给你的客厅做了装修,你朋友正好也希望给他的客厅做装修,那么,他可能会把你家的装修方案拿过来改改就成,你的装修方案就是原型。
由于很多OOP语言都支持对象的克隆(拷贝)以方便复制对象,但这些方式并不那么完美,后述我们将会讨论。
5.2 原型模式
当创建这些对象(一般情况是一些大对象)非常耗时,或者创建过程非常复杂时,非常有用,GoF给出的原型模式定义如下:
原型模式的静态类图非常简单,如下所示:
Client使用Prototype的clone()方法得到这个对象的拷贝,其实拷贝原型对象不一定是指从内存中进行拷贝,我们的原型数据可能保存在数据库里。
一般情况下,OOP语言都提供了内存中对象的复制,Java语言提供了对象的浅拷贝(Shallow copy),也就是说复制一个对象时,如果它的一个属性是引用,则复制这个引用,使之指向内存中同一个对象;但如果为此属性创建了一个新对象,让其引用指向它,即是深拷贝(Deep copy)。
5.3 寄个快递
下面是一个邮寄快递的场景:
“给我寄个快递。”顾客说。
“寄往什么地方?寄给……?”你问。
“和上次差不多一样,只是邮寄给另外一个地址,这里是邮寄地址……”顾客一边说一边把写有邮寄地址的纸条给你。
“好!”你愉快地答应,因为你保存了用户的以前邮寄信息,只要复制这些数据,然后通过简单的修改就可以快速地创建新的快递数据了。
5.4 实现
我们在复制新的数据时,需要特别注意的是,我们不能把所有数据都复制过来,例如,当对象包含主键时,不能使用原型数据的主键,必须创建一个新的主键。我们这里提供一个静态工厂方法,来获得原型数据,然后拷贝这些数据,最后做相应的初始化。为了操作安全起见,我们不直接使用原型数据,而是使用clone()方法从内存克隆这条原型数据做后续操作。我们使用Java提供java.lang.Cloneable接口克隆数据,它实现对象的浅拷贝,关于java.lang.Cloneable接口的使用请看后续介绍。
5.4.1 UML静态类图
Client使用PackageInfo提供的静态工厂方法clonePackage(String userName)创建一个新对象:首先根据userName加载一条用户以前的数据作为原型数据(可以是数据库,可以是其他任何你保存数据的地方),然后在内存中克隆这条数据,最后初始化该数据并返回。
5.4.2 代码实现
public class PackageInfo implements Cloneable {
//getters, setters and other methods...
public PackageInfo clone() {
try {
return (PackageInfo)super.clone();
} catch (CloneNotSupportedException e) {
System.out.println("Cloning not allowed.");
return null;
}
}
public static PackageInfo clonePackage(String userName) {
//load package as prototype data from db...
PackageInfo prototype = loadPackageInfo(userName);
//clone information ...
prototype = prototype.clone();
//initialize copied data ...
prototype.setId(null);
return prototype;
}
}
代码注解:
- Java的java.lang.Object方法里就提供了克隆方法clone(),原则上似乎所有类都拥有此功能,但其实不然,关于它的使用有如下限制:
- 要实现克隆,必须实现java.lang.Cloneable接口,否则在运行时调用clone()方法,会抛CloneNotSupportedException异常。
- 返回的是Object类型的对象,所以使用时可能需要强制类型转换。
- 该方法是protected的,如果想让外部对象使用它,必须在子类重写该方法,设定其访问范围是public的,参见PackageInfo的clone()方法。
- Object的clone()方法的复制是采用逐字节的方式从复制内存数据,复制了属性的引用,而属性所指向的对象本身没有被复制,因此所复制的引用指向了相同的对象。由此可见,这种方式拷贝对象是浅拷贝,不是深拷贝。
- 静态工厂方法public static PackageInfo clonePackage(String userName)方法根据原型创建一份拷贝:首先拿出用户以前的一条数据,即这句PackageInfo prototype = loadPackageInfo(userName),然后调用方法它的clone()方法完成内存拷贝,即prototype.clone(),最后我们初始化这条新数据,比如使id为空等。
现在来看看我们的测试代码,如下所示:
public class PackageInfoTestDrive {
public static void main(String[] args) {
PackageInfo currentInfo = PackageInfo.clonePackage("John");
System.out.println("Original package information:");
display(currentInfo);
currentInfo.setId(10000l);
currentInfo.setReceiverName("Ryan");
currentInfo.setReceiverAddress("People Square, Shanghai");
System.out.println("\nNew package information:");
display(currentInfo);
}
//other methods…
}
我们通过这句,PackageInfo currentInfo = PackageInfo.clonePackage("John"),拷贝了一份快递信息出来,通过设置currentInfo.setReceiverName("Ryan")和currentInfo.setReceiverAddress("People Square, Shanghai"),便完成了第二个包裹的信息录入,测试结果如下:
Original package information:
Package id: null
Receiver name: John
Receiver address: People Square,Shanghai
Sender name: William
Sender Phone No.: 12345678901
New package information:
Package id: 10000
Receiver name: Ryan
Receiver address: People Square, Shanghai
Sender name: William
Sender Phone No.: 12345678901
在实际的应用中,使用原型模式创建对象图 (Object Graph)非常便捷。
5.5 深拷贝(Deep Copy)
通过上述学习,我们知道Java提供了浅拷贝的方法,那么,如何实现一个深拷贝呢?一般情况下,我们有两种方式来实现:
1. 拷贝对象时,递归地调用属性对象的克隆方法完成。读者可以根据具体的类,撰写出实现特定类型的深拷贝方法。
一般地,我们很难实现一个一般性的方法来完成任何类型对象的深拷贝。有人根据反射得到属性的类型,然后依照它的类型构造对象,但前提是,这些属性的类型必须含有一个公有的默认构造方法,否则作为一个一般性的方法,很难确定传递给非默认构造方法的参数值;此外,如果属性类型是接口或者抽象类型,必须提供查找到相关的具体类方法,作为一个一般性的方法,这个也很难办到。
2. 如果类实现了java.io.Serializable接口,把原型对象序列化,然后反序列化后得到的对象,其实就是一个新的深拷贝对象。
我们整理给出第二种方法的实现,代码片段大致如下所示:
import java.io.Serializable;
//other imports…
public class DeepCopyBean implements Serializable {
private String objectField;
private int primitiveField;
//getters and setters …
public DeepCopyBean deepCopy() {
try {
ByteArrayOutputStream buf = new ByteArrayOutputStream();
ObjectOutputStream o = new ObjectOutputStream(buf);
o.writeObject(this);
ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(buf.toByteArray()));
return (DeepCopyBean) in.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
}
代码注解:
- DeepCopyBean实现了java.io.Serializable接口,它含有一个原始类型(Primitive Type)的属性primitiveField和对象属性objectField。
- 此类的deepCopy()方法首先序列化自己到流中,然后从流中反序列化,得到的对象便是一个新的深拷贝。
为了验证是不是实现了深拷贝,我们编写了如下测试代码:
DeepCopyBean originalBean = new DeepCopyBean();
//create a String object in jvm heap not jvm string pool
originalBean.setObjectField(new String("123456"));
originalBean.setPrimitiveField(2);
//clone this bean
DeepCopyBean newBean = originalBean.deepCopy();
System.out.println("Primitive ==? " + (newBean.getPrimitiveField() == originalBean.getPrimitiveField()));
System.out.println("Object ==? " + (newBean.getObjectField() == originalBean.getObjectField()));
System.out.println("Object equal? " + (newBean.getObjectField().equals(originalBean.getObjectField())));
注意:
这句,originalBean.setObjectField(new String("123456"))和originalBean.setObjectField("123456")是不一样的,前者创建了两个String对象,其中一个是在JVM的字符串池(String pool)里,另外一个在堆中,并且属性引用指向的对象在堆里;后者属性引用指向了JVM字符串池中的"123456"对象。
如果是浅拷贝,即引用指向同一内存地址,则newBean.getObjectField() == originalBean.getObjectField()为true,如果是深拷贝,则创建了不同对象,引用指向的地址肯定不一样,即此值应为false。但是这两种方式,使用这句,newBean.getObjectField().equals(originalBean.getObjectField()),进行比较,其结果必须为true,测试结果如下所示:
Primitive ==? true
Object ==? false
Object equal? true
和我们预想的结果一样,原始类型的使用==进行比较,结果相等,而引用类型使用==比较,结果显示未指向相同的地址,但是使用equals()方法比较的结果为true,即证明我们实现了深拷贝。
使用这种方式进行深拷贝,一方面,它只能拷贝实现Serializable接口类型的对象,其属性也是可序列化的;另一方面,序列化和反序列化比较耗时。选用此方式实现深拷贝时需要做这两方面的权衡。
5.6 总结
我们以前使用java.lang.Cloneable的一个很大原因是使用new创建对象的速度相对来说比较慢,如今,随着JVM性能的提升,new的速度已经很接近Object的clone()方法的速度了,然而这并没有使原型模式使用失去多少光泽,使用原型模式有以下优点:
- 创建大的聚合对象图时,没必要为每个层次的子对象创建相应层次的工厂类。
- 方便实例化,只要复制对象,然后初始化对象,就可以得到你想要的对象,并不不需要过多的编程。
参考:
[1] Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley, 1995.
[2] Alan Shalloway and James R. Trott. Design Patterns Explained: A New Perspective on Object-Oriented Design, 2nd Edition. Addison-Wesley, 2004.
[3] Eric Evans. Domain-Driven Design: Tackling Complexity in the Heart of Software. Addison-Wesley, August, 2003.
[4] Joshua Bloch. Effective Java: Programming Language Guide. Addison-Wesley, 2001.
评论
如果一个类实现了Serializable接口,那么一旦这个类发布,“改变这个类的实现”的灵活性将大大降低
argouml,免费工具。
我在后面说了,在我的书里,代码也在我的博客的附件里,[img]/images/smiles/icon_biggrin.gif" alt="[/img]
链接http://redhat.iteye.com/blog/1007884
这是第5章的内容。大家似乎对整个书籍可能兴趣不大,其实书里讲了好多大家经常搞错的概念。
谢谢分享
发表评论
-
在遗留代码上开发(Development on legacy code)
2013-03-19 15:50 11356在遗留代码上开发(De ... -
在遗留代码上开发(Development on legacy code)
2013-03-14 19:08 1442(草稿,未完全发布 ... -
DDD与TDD比较之——TDD
2012-07-15 21:57 8949DDD与TDD比较——TDD 最近承诺要写一 ... -
有奖图书试读《漫谈设计模式——从面向对象开始 》
2012-01-07 22:14 2809有奖图书试读链接:http://bbs.chinaunix.n ... -
《漫谈设计模式》评述
2012-01-01 11:49 0对于最近书籍的评述, ... -
《漫谈设计模式》评述
2011-12-31 08:26 26对于最近书籍的评述, ... -
新书上架,《漫谈设计模式——从面向对象开始》(有电子版)
2011-12-29 17:25 5899千呼万唤,我的书籍《 ... -
《漫谈设计模式》一书样书终于拿到了
2011-12-20 21:58 6010《漫谈设计模式》的样书今天发过来了,非常高兴,刚给支持我,帮助 ... -
《漫谈设计模式》一书终于出版了
2011-12-02 15:02 6201Dear my friend, Finally, ... -
吃"软饭"的一些不良思考习惯
2011-10-31 13:00 73531. 认为“简单原则”,就是不加认真思考,制作最简单易于实现 ... -
关于软件的核心,莫被算法和使用技术完全忽悠了
2011-09-17 18:13 5412关于软件的核心,莫被 ... -
学习设计模式的一些常见问题
2011-09-02 16:05 3514根据最近热心读者的反馈,这里列出来一些常见的设计模式的问题,进 ... -
漫谈设计模式反馈邮箱变更
2011-08-17 11:01 1971漫谈设计模式反馈邮箱变为ramblingondesignpat ... -
《漫谈设计模式》勘误
2011-06-15 14:43 3617这个帖子里将整理出漫谈设计模式一些勘误,在我的发行印刷版本里修 ... -
关于漫谈设计模式4.2.3章节的一点申明
2011-05-25 11:22 4542之前比较懒,对ThreadLocal代码未做细细研究,而是受一 ... -
ThreadLocal的误解
2011-05-25 11:14 13459可能大部分人的想法和我当初的想法一样,都是以为在ThreadL ... -
IoC != 装配和实例化的反转 != DI(注射依赖)
2011-05-18 11:54 2606Inversion of Control(控制 ... -
一本关于Java设计模式的免费书籍
2011-04-19 13:23 4656大家可以去这里查看:http://redhat.iteye.c ... -
《漫谈设计模式》
2011-04-19 13:07 35361这里给出其中的一章供参考,想阅读书籍全部内容,请参见博客附件, ...
相关推荐
在JavaScript中,原型模式的实现主要依赖于内置的`Object.create()`方法或`clone()`方法(如果对象实现了这个方法)。`Object.create()`可以创建一个新的对象,并将新对象的原型设置为传入的对象,从而继承其属性和...
别急,这里说的超类是Object类,因为所有Java类默认都继承了Object类,而Object类提供了`clone()`方法。) DayLife dayLife = (DayLife) super.clone(); // 复制对象的属性,确保深拷贝 dayLife.setGetUp(this....
`clone`函数是一种简单的原型继承实现,它创建了一个新函数`F`,然后设置`F.prototype`为传入对象`o`。接着,返回`new F()`,这个新实例的`__proto__`会指向`o`,实现了属性的继承: ```javascript function clone...
原型模式是设计模式中的一种,它是Java 23种经典设计模式之一,主要用来提高对象创建的效率。在原型模式中,我们通过复制或克隆一个已经存在的对象来创建新的对象,而不是通过构造函数来创建。这种方法尤其适用于当...
**原型模式(Prototype Pattern)**是一种常用的软件设计模式,它的主要思想是通过复制已有对象来创建新的对象,从而减少创建新对象的成本。在Java等面向对象编程语言中,原型模式经常被用来实现对象的克隆。在给定的...
**原型设计模式(Prototype Pattern)**是一种创建型设计模式,它允许我们通过复制现有的对象来创建新对象,而不是通过构造函数来实例化新对象。在面向对象编程中,当我们需要频繁地创建具有相同或相似属性的对象时,...
在Java中,我们可以使用`Cloneable`接口和`Object`类的`clone()`方法来实现原型模式。以下是一个简单的Java示例: ```java public interface Prototype { Prototype clone(); } public class ConcretePrototype ...
在上述代码中,`createPrototype`是具体原型,它定义了一个`clone`方法,这个方法使用`Object.create()`创建一个新的对象,并将其原型设置为`prototypeInstance`,从而实现对象的浅克隆。 ### 4. 使用Prototype模式...
Java中有一个内置的Cloneable接口和Object类的clone()方法,它们可以被用来实现对象的克隆。但是,直接使用clone()方法需要处理一些复杂的问题,比如深拷贝和浅拷贝的区别。 压缩包文件代码是一个使用Java实现原型...
原型模式(Prototype Pattern)是一种创建型设计模式,它允许我们通过复制现有的对象来创建新对象,而无需知道具体创建过程的细节。这种模式在处理对象的创建时,特别是在需要大量相似对象时,能够提高效率,避免了...
**原型模式(Prototype Pattern)**是一种创建型设计模式,它提供了一种通过复制已有对象来创建新对象的方式,而不是通过构造函数。在某些情况下,当创建新对象的成本非常高时(例如,对象需要大量的初始化操作或者从...
自定义的clone方法更加灵活和通用,但可能会增加原型链的负担,有可能与其他依赖于Object.prototype的代码冲突;递归函数则需要确保所有嵌套的对象都能被正确处理,且需要处理循环引用等复杂情况,否则可能会导致栈...
原型模式(Prototype Pattern)是一种创建型设计模式,它的主要目标是通过复制现有的对象来创建新对象,以减少重复的构造过程,提高性能。在Java中,原型模式通常涉及到对象的克隆操作,即创建一个对象的副本。 **...
所有Java类默认继承自`Object`类,而`Object`类提供了`clone()`方法,这为实现原型模式奠定了基础。不过,值得注意的是,若要使用`clone()`方法,对象所属的类必须实现`Cloneable`接口,否则运行时将抛出`...
原型模式是一种设计模式,属于创建型模式,它允许我们通过复制已有对象来创建新对象,而不是通过构造函数来创建。这种模式在系统中需要频繁创建相似对象时特别有用,可以提高代码的效率和可维护性。 在“原型模式...
原型模式是一种设计模式,主要应用于软件工程领域,用于创建重复的对象,而无需再次进行实例化。在Java、C#等面向对象的语言中,原型模式通过实现`Cloneable`接口或使用序列化机制来实现对象的复制。在这个"原型模式...
### 原型模式 Prototype Pattern #### 概述 原型模式是一种创建型设计模式,它允许用户通过复制现有的实例来创建新的对象,而不是通过传统的构造器来创建对象。这种模式适用于那些创建对象的成本较高,或者当对象...
在原型模式中,首先需要定义一个接口或抽象类,这个接口或抽象类通常叫做`Prototype`,它声明了一个`clone()`方法,用于返回对象的一个副本。例如: ```java public interface Prototype { Prototype clone(); ...
原型模式(Prototype Pattern)是其中一种行为设计模式,主要用于对象创建。它通过复制已有对象来创建新对象,而不是通过传统的构造函数来创建。在Java中,原型模式可以有效地提高性能,特别是在创建复杂对象时。 #...