1、设计模式 原型(Prototype)模式 Session3 By xx1st Jul 2008原型(原型(Prototype)模式模式通过给出一个原型对象来指明所要创建的对象类型,然后用拷贝通过给出一个原型对象来指明所要创建的对象类型,然后用拷贝这个原型对象的办法创建出更多的同类型对象。这个原型对象的办法创建出更多的同类型对象。孙大圣的毫毛孙大圣的毫毛孙悟空在与黄风怪的战斗中,孙悟空在与黄风怪的战斗中,“使一个身外身的手段:把毫毛揪使一个身外身的手段:把毫毛揪下一把,用口嚼得粉碎,望上一喷,叫声下一把,用口嚼得粉碎,望上一喷,叫声变变,变有百十个行,变有百十个行者,都是一样得打扮,各执一根铁棒
2、,把那怪围在空中。者,都是一样得打扮,各执一根铁棒,把那怪围在空中。”换而换而言之,孙悟空可以根据自己的形象,拷贝出很多言之,孙悟空可以根据自己的形象,拷贝出很多“身外身身外身”来。来。孙悟空这种身外身的手段在面向对象设计领域里叫原型孙悟空这种身外身的手段在面向对象设计领域里叫原型(Prototype)模式。模式。1 1,JavaJava对原型模式的支持对原型模式的支持在在Java里面,我们可以通过里面,我们可以通过Clone()方法实现原型模式。任何类,只要方法实现原型模式。任何类,只要想支持克隆,必须实现想支持克隆,必须实现Cloneable接口。接口。Cloneable接口中有接口中有C
3、lone方法方法,可以在类中复写实现自定义的克隆方法。克隆的实现方法有三种:浅,可以在类中复写实现自定义的克隆方法。克隆的实现方法有三种:浅拷贝、深拷贝和完全拷贝。拷贝、深拷贝和完全拷贝。(1)浅拷贝浅拷贝被拷贝对象的所有变量都含有与原来的对象相同的值,而所有的对其他被拷贝对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅拷贝仅仅拷贝所考虑的对对象的引用仍然指向原来的对象。换言之,浅拷贝仅仅拷贝所考虑的对象,而不拷贝它所引用的对象。象,而不拷贝它所引用的对象。(2)深拷贝深拷贝被拷贝对象的所有变量都含有与原来的对象相同的值,除去那些引用其被拷贝对象
4、的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被拷贝过的新对象,而他对象的变量。那些引用其他对象的变量将指向被拷贝过的新对象,而不再是原有的那些被引用的对象。换言之,深拷贝把要拷贝的对象所引不再是原有的那些被引用的对象。换言之,深拷贝把要拷贝的对象所引用的对象都拷贝了一遍。用的对象都拷贝了一遍。(3)完全拷贝完全拷贝完全拷贝不仅把要拷贝的对象所直接引用的对象都拷贝了一遍,完全拷贝不仅把要拷贝的对象所直接引用的对象都拷贝了一遍,还把该还把该对象间接引用到的所有对象也都拷贝了一遍。这是最彻底的一种拷贝。对象间接引用到的所有对象也都拷贝了一遍。这是最彻
5、底的一种拷贝。2,JavaJava的的clone()clone()方法方法 Cloneprotected protected ObjectObject clone()throws clone()throws CloneNotSupportedExceptionCloneNotSupportedExceptionThismethodmaybecalledtocreateanewcopyoftheObject.Thetypicalbehaviorisasfollows:o=o.clone()o=o.clone()isfalseo.o.getClassgetClass()=o.clone().()=
6、o.clone().getClassgetClass()()istrueo.equals(o)o.equals(o)istrueHowever,thesearenotstrictrequirements,andmaybeviolatedifnecessary.Ofthethreerequirements,thelastisthemostcommonlyviolated,particularlyifthesubclassdoesnotoverrideequals(Object)equals(Object)55.IftheObjectyoucallclone()ondoesnotimplement
7、Cloneable(whichisaplaceholderinterface),thenaCloneNotSupportedExceptionisthrown.NoticethatObjectdoesnotimplementCloneable;thismethodexistsasaconvenienceforsubclassesthatdo.ObjectsimplementationofcloneallocatesspaceforthenewObjectusingthecorrectclass,withoutcallinganyconstructors,andthenfillsinalloft
8、henewfieldvalueswiththeoldfieldvalues.Thus,itisashallowcopy.However,subclassesarepermittedtomakeadeepcopy.AllarraytypesimplementCloneable,andoverridethismethodasfollows(itshouldneverfail):public Object clone()try super.clone();catch(public Object clone()try super.clone();catch(CloneNotSupportedExcep
9、tionCloneNotSupportedException e)throw new e)throw new InternalErrorInternalError(e.(e.getMessagegetMessage();();2,JavaJava的的clone()clone()方法方法 clone方法将对象复制了一份并返回给调用者。一般而方法将对象复制了一份并返回给调用者。一般而言,言,clone()方法满足:()方法满足:对任何的对象对任何的对象x,都有,都有x.clone()!=x克隆对象与原对象不是同一个对象克隆对象与原对象不是同一个对象对任何的对象对任何的对象x,都有,都有x.clon
10、e().getClass()=x.getClass()克隆对象与原对象的类型一样克隆对象与原对象的类型一样如果对象如果对象x的的equals()方法定义恰当,那么方法定义恰当,那么x.clone().equals(x)应该成立。应该成立。Java中对象的克隆中对象的克隆为了获取对象的一份拷贝,我们可以利用为了获取对象的一份拷贝,我们可以利用Object类的类的clone()方法。方法。在派生类中覆盖基类的在派生类中覆盖基类的clone()方法,并声明为方法,并声明为public。在派生类的在派生类的clone()方法中,调用方法中,调用super.clone()。在派生类中实现在派生类中实现C
11、loneable接口。接口。3,JavaJava的类拷贝实现代码的类拷贝实现代码classStudentimplementsCloneableStringname;intage;Student(Stringname,intage)this.name=name;this.age=age;publicObjectclone()Objecto=null;tryo=(Student)super.clone();/Object中的中的clone()识别出你要复制的是哪一个对象。识别出你要复制的是哪一个对象。catch(CloneNotSupportedExceptione)System.out.prin
12、tln(e.toString();returno;publicstaticvoidmain(Stringargs)Students1=newStudent(“zhangsan”,18);Students2=(Student)s1.clone();s2.name=“lisi”;s2.age=20;System.out.println(“name=”+s1.name+“,”+“age=”+s1.age);/修改学生修改学生2后,不影响学生后,不影响学生1的值。的值。为什么我们在派生类中覆盖Object的clone()方法时,一定要调用super.clone()呢?在运行时刻,Object中的 cl
13、one()识别出你要复制的是哪一个对象,然后为此对象分配空间,并进行对象的复制,将原始对象的内容一一复制到新对象的存储空间中。继承自java.lang.Object类的clone()方法是浅复制。后面的代码可以证明。浅拷贝3,JavaJava的类拷贝实现代码的类拷贝实现代码classProfessorStringname;intage;Professor(Stringname,intage)this.name=name;this.age=age;classStudentimplementsCloneableStringname;/常量对象。常量对象。intage;Professorp;/学生学
14、生1和学生和学生2的引用值都是一样的引用值都是一样的。的。Student(Stringname,intage,Professorp)this.name=name;this.age=age;this.p=p;publicObjectclone()Studento=null;tryo=(Student)super.clone();catch(CloneNotSupportedExceptione)System.out.println(e.toString();/o.p=(Professor)p.clone();returno;publicstaticvoidmain(Stringargs)Prof
15、essorp=newProfessor(wanGWu,50);Students1=newStudent(zhangsan,18,p);Students2=(Student)s1.clone();s2.p.name=lisi;s2.p.age=30;System.out.println(name=+s1.p.name+,+age=+s1.p.age);/学生学生1的教授为的教授为lisi,age为为30,被改了被改了浅拷贝3,JavaJava的类拷贝实现代码的类拷贝实现代码classProfessorimplementsCloneableStringname;intage;Professor(S
16、tringname,intage)this.name=name;this.age=age;publicObjectclone()Objecto=null;tryo=super.clone();catch(CloneNotSupportedExceptione)System.out.println(e.toString();returno;classStudentimplementsCloneableStringname;/常量对象。常量对象。intage;Professorp;/学生学生1和学生和学生2的引用值都是一样的引用值都是一样的。的。Student(Stringname,intage,
17、Professorp)this.name=name;this.age=age;this.p=p;publicObjectclone()Studento=null;tryo=(Student)super.clone();catch(CloneNotSupportedExceptione)System.out.println(e.toString();o.p=(Professor)p.clone();returno;publicstaticvoidmain(Stringargs)Professorp=newProfessor(“wanGWu”,50);Students1=newStudent(“z
18、hangsan”,18,p);Students2=(Student)s1.clone();s2.p.name=“lisi”;s2.p.age=30;System.out.println(“name=”+s1.p.name+“,”+“age=”+s1.p.age);/学生学生1的教授不改变的教授不改变深拷贝3,JavaJava的类拷贝实现代码的类拷贝实现代码classProfessorimplementsSerializableStringname;intage;Professor(Stringname,intage)this.name=name;this.age=age;classStuden
19、timplementsSerializableStringname;/常量对象。常量对象。intage;Professorp;/学生学生1和学生和学生2的引用值都是一样的。的引用值都是一样的。Student(Stringname,intage,Professorp)this.name=name;this.age=age;this.p=p;publicObjectfullClone()throwsIOException,OptionalDataException,ClassNotFoundException/将对象写到流里将对象写到流里ByteArrayOutoutStreambo=newByt
20、eArrayOutputStream();ObjectOutputStreamoo=newObjectOutputStream(bo);oo.writeObject(this);/从流里读出来从流里读出来ByteArrayInputStreambi=newByteArrayInputStream(bo.toByteArray();ObjectInputStreamoi=newObjectInputStream(bi);return(oi.readObject();publicstaticvoidmain(Stringargs)Professorp=newProfessor(wangwu,50)
21、;Students1=newStudent(zhangsan,18,p);Students2=(Student)s1.fullClone();s2.p.name=lisi;s2.p.age=30;System.out.println(name=+s1.p.name+,+age=+s1.p.age);/学生学生1的教授不改变。的教授不改变。完全拷贝在在Java语言里深复制一个对象,常常可以先使对象实现语言里深复制一个对象,常常可以先使对象实现Serializable接口,然后把对象(实际上只是对象的一个拷贝)接口,然后把对象(实际上只是对象的一个拷贝)写到一个流里(腌成干菜),再从流里读出来(把
22、干菜回鲜),便可以重建对象。写到一个流里(腌成干菜),再从流里读出来(把干菜回鲜),便可以重建对象。4,PrototypePrototype模式的结构模式的结构客客户(Client)角色角色:客客户类提出提出创建建对象的象的请求求。抽象原型抽象原型(Prototype)角色角色:这是一个抽象角色是一个抽象角色,通常由一个接口通常由一个接口类或抽象类实现。此角色给出所有的具体原型类或抽象类实现。此角色给出所有的具体原型类所需的接口所需的接口。在。在Java中,中,抽象原型角色通常抽象原型角色通常实现了了Cloneable接口接口。具体原型具体原型(ConcretePrototype)角色角色:被
23、复制的被复制的对象象。此角色需要此角色需要实现抽象原型角色所要求的接口抽象原型角色所要求的接口。4.1 PrototypePrototype模式实现模式实现-抽象原型抽象原型public abstract class AbstractSpoon implements Cloneable String spoonName;public void setSpoonName(String spoonName)this.spoonName=spoonName;public String getSpoonName()return this.spoonName;public Object clone()O
24、bject object=null;try object=super.clone();catch(CloneNotSupportedException exception)System.err.println(AbstractSpoon is not Cloneable);return object;4.2 PrototypePrototype模式实现模式实现-具体原型具体原型public class SaladSpoon extends AbstractSpoon public SaladSpoon()setSpoonName(Salad Spoon);public class SoupSp
25、oon extends AbstractSpoon public SoupSpoon()setSpoonName(Soup Spoon);4.3 PrototypePrototype模式实现模式实现-客户客户 public class Client/*param args*/public static void main(String args)AbstractSpoon spoon1=new SoupSpoon();AbstractSpoon spoon2=(AbstractSpoon)spoon1.clone();System.out.println(spoon2.getSpoonName
26、();AbstractSpoon spoon3=new SaladSpoon();AbstractSpoon spoon4=(AbstractSpoon)spoon3.clone();System.out.println(spoon4.getSpoonName();5,带原型管理器的原型模式带原型管理器的原型模式 客客户(Client)角色角色:客客户端端类向原型管理器提出向原型管理器提出创建建对象的象的请求求。抽象原型抽象原型(Prototype)角色角色:这是一个抽象角色是一个抽象角色,通常由一个接口或抽象通常由一个接口或抽象类实现。此角色此角色给出所有的具体原型出所有的具体原型类所需的接
27、口所需的接口。在。在Java中,中,抽象原型角色通常抽象原型角色通常实现了了Cloneable接口接口。具体原型具体原型(ConcretePrototype)角色角色:被复制的被复制的对象象。此角色需要此角色需要实现抽象的原型角色所要求的接口抽象的原型角色所要求的接口。原型管理器原型管理器(PrototypeManager)角色角色:创建具体原型建具体原型类的的对象象,并并记录每一个被每一个被创建的建的对象象。5.1 带原型管理器的原型模式实现带原型管理器的原型模式实现 原型管理器原型管理器import java.util.HashMap;public class SpoonManager p
28、rivate HashMap hm=null;private static SpoonManager spoonManager=null;private SpoonManager()hm=new HashMap();public static synchronized SpoonManager getSpoonManager()if(spoonManager=null)spoonManager=new SpoonManager();return spoonManager;public void register(String name,Object prototype)hm.put(name,
29、prototype);public void unRegister(String name)hm.remove(name);public Object getSpoon(String name)Object o=null;if(hm.containsKey(name)o=hm.get(name);else try/自动查找原型管理器里不存在的类,并动态生成它自动查找原型管理器里不存在的类,并动态生成它 o=Class.forName(name).newInstance();this.register(name,o);catch(Exception e)System.out.println(cl
30、ass+name+dont define+e.getMessage();e.printStackTrace();return(AbstractSpoon)o).clone();5.2 带原型管理器的原型模式实现带原型管理器的原型模式实现 客户客户public class Client/*param args*/public static void main(String args)AbstractSpoon soupSpoon=new SoupSpoon();AbstractSpoon saladSpoon=new SaladSpoon();SpoonManager spoonManager=
31、SpoonManager.getSpoonManager();spoonManager.register(prototype.test.SoupSpoon,soupSpoon);spoonManager.register(prototype.test.SaladSpoon,saladSpoon);AbstractSpoon soupSpoon2=(AbstractSpoon)spoonManager.getSpoon(prototype.test.SoupSpoon);AbstractSpoon saladSpoon2=(AbstractSpoon)spoonManager.getSpoon(
32、prototype.test.SaladSpoon);System.out.println(soupSpoon.hashCode();System.out.println(soupSpoon2.hashCode();System.out.println(saladSpoon.hashCode();System.out.println(saladSpoon2.hashCode();6,JavaScript中的prototype应用下面是一个比较实际的下面是一个比较实际的web应用中的例子,通过现有的应用中的例子,通过现有的dom元素对象,元素对象,创建了一个新的副本。这个副本创建了一个新的副本。
33、这个副本dom元素并没有使用元素并没有使用document.createElement方法创建。方法创建。TestDivvartestClone=document.getElementById(test).cloneNode(true);document.body.appendChild(testClone);6,JavaScript中的prototype应用浅拷贝functionWriter(name,sex)this.name=name;this.sex=sex;functionBook(name,pages,price,writer)this.name=name;this.pages=p
34、ages;this.price=price;this.writer=writer;Book.prototype.clone=function()/深拷贝浅拷贝的区别就在这个深拷贝浅拷贝的区别就在这个function里里returnnewBook(this.name,this.pages,this.price,this.writer);varwriter=newWriter(佘丹佘丹,男男);varbookName=深入解析原型模式深入解析原型模式;varmyBook=newBook(bookName,611,70,writer);varotherBook=myBook.clone();myBo
35、ok.name=不是深入解析原型模式不是深入解析原型模式;/值类型的修改不会相互影响值类型的修改不会相互影响alert(myBook.name,otherBook.name);/所以这里打印:不是深入解析原型模式,深入解析原型模式所以这里打印:不是深入解析原型模式,深入解析原型模式myBook.writer.name=不是佘丹不是佘丹;/引用类型的修改会相互影响,因为他们引用同个内存地址引用类型的修改会相互影响,因为他们引用同个内存地址alert(myBook.writer.name,otherBook.writer.name);/所以这里打印:不是佘丹,不是佘丹所以这里打印:不是佘丹,不是佘
36、丹6,JavaScript中的prototype应用深拷贝functionWriter(name,sex)this.name=name;this.sex=sex;functionBook(name,pages,price,writer)this.name=name;this.pages=pages;this.price=price;this.writer=writer;Book.prototype.clone=function()/深拷贝浅拷贝的区别就在这个深拷贝浅拷贝的区别就在这个function里里varwriter=newWriter(this.writer.name,this.writ
37、er.sex);returnnewBook(this.name,this.pages,this.price,writer);varwriter=newWriter(佘丹佘丹,男男);varbookName=深入解析原型模式深入解析原型模式;varmyBook=newBook(bookName,611,70,writer);varotherBook=myBook.clone();myBook.name=不是深入解析原型模式不是深入解析原型模式;/值类型的修改不会相互影响值类型的修改不会相互影响alert(myBook.name,otherBook.name);/所以这里打印:不是深入解析原型模式
38、,深入解析原型模式所以这里打印:不是深入解析原型模式,深入解析原型模式myBook.writer.name=不是佘丹不是佘丹;/深拷贝,引用已经指向不同内存地址,修改不会相互影响深拷贝,引用已经指向不同内存地址,修改不会相互影响alert(myBook.writer.name,otherBook.writer.name);/所以这里打印:不是佘丹,佘丹所以这里打印:不是佘丹,佘丹6,JavaScript中的prototype应用原型管理器原型管理器StyleTestfunctionStyleItem(fontSize,color)/类定义类定义this.fontSize=fontSize;th
39、is.color=color;StyleItem.prototype.clone=function()returnfontSize:this.fontSize,color:this.colorvarStyleManager=/原型管理器原型管理器getStyle:function(key)if(thiskey)returnthiskey.clone();/预创建类可能的状态集合对象预创建类可能的状态集合对象StyleManagerbig=newStyleItem(16px,#000);StyleManagernormal=newStyleItem(14px,#333);StyleManager
40、small=newStyleItem(12px,#666);/无需再通过无需再通过new来创建来创建StyleItem类的对象了类的对象了varel=document.getElementById(styletest);varstyle=StyleManager.getStyle(small);for(varkinstyle)el.stylek=stylek;7,深入JavaScript的prototypeJavascript中的面向对象机制是通过中的面向对象机制是通过prototype实现的,在实现的,在new一一个对象的时候,其实并没有为每一个对象创建每个个对象的时候,其实并没有为每一个对
41、象创建每个prototype成员的成员的副本,而是将对象成员指针指向副本,而是将对象成员指针指向prototype成员,下面程序可以验证成员,下面程序可以验证这点:这点:functionTest()Test.prototype.alert=function()alert(test);vartest1=newTest();vartest2=newTest();alert(test1.alert=test2.alert);test1.constructor.prototype.alert=function()alert(test1);test2.alert();7,深入JavaScript的pro
42、totype此外,此外,JScript中对象的中对象的prototype属性,是用来返回对象类型原型的引用的。所有属性,是用来返回对象类型原型的引用的。所有JScript内部对象都有只读的内部对象都有只读的prototype属性属性,但可以向其原型中动态添加功能,但可以向其原型中动态添加功能(属属性和方法性和方法),而且对象的新实例会,而且对象的新实例会“继承继承”赋予该对象原型的操作。赋予该对象原型的操作。下面是三个经典的下面是三个经典的prototype属性的使用示例。属性的使用示例。1、为脚本环境内建对象添加方法:为脚本环境内建对象添加方法:String.prototype.trim()
43、returnthis.replace(/(s)|(s$)/g,);2、为用户自定义类添加方法:为用户自定义类添加方法:functionTestObject(name)this.m_Name=name;TestObject.prototype.ShowName=function(alert(this.m_Name);3、更新自定义类的更新自定义类的prototype:functionTestObjectA()this.MethodA=function()alert(TestObjectA.MethodA();functionTestObjectB()this.MethodB=function()
44、alert(TestObjectB.MethodB();/原型继承法原型继承法TestObjectB.prototype=newTestObjectA();7,深入JavaScript的prototypeJScript的的prototype和和prototypepattern中中prototype的不同:的不同:JScript中的这个所谓的中的这个所谓的prototype属性其实是个语言本身支持的特性,这里没有发生属性其实是个语言本身支持的特性,这里没有发生任何的任何的copy,不管不管shallow还是还是deep的。的。对于对于JScript的解释引擎,它在处理的解释引擎,它在处理“.”或
45、或“keyName”引用的对象的属性和方法引用的对象的属性和方法时,先在对象本身的实例时,先在对象本身的实例(this)中查找,如果找到就返回或中查找,如果找到就返回或执行。如果没有查找到,执行。如果没有查找到,就查找对象的就查找对象的prototype(this.constructor.prototype)里是否定义了被查找的对象里是否定义了被查找的对象和方法,如果和方法,如果找到就返回或执行,如果没有查找到,就返回找到就返回或执行,如果没有查找到,就返回undefined(对于属性对于属性)或或runtimeerror(对于方法对于方法)。具体可以参照实例代码具体可以参照实例代码test1
46、.html和和test2.html。8,PrototypePrototype模式的优点与缺点模式的优点与缺点 Prototype模式的优点模式的优点:Prototype模式允许动态增加或减少产品类。由于创建产品类实例的模式允许动态增加或减少产品类。由于创建产品类实例的方法是产批类内部具有的,因此增加新产品对整个结构没有影响。方法是产批类内部具有的,因此增加新产品对整个结构没有影响。Prototype模式提供了简化的创建结构。工厂方法模式常常需要有一模式提供了简化的创建结构。工厂方法模式常常需要有一个与产品类等级结构相同的等级结构,而个与产品类等级结构相同的等级结构,而Prototype模式就不
47、需要这模式就不需要这样。样。Portotype模式具有给一个应用软件动态加载新功能的能力。由于模式具有给一个应用软件动态加载新功能的能力。由于Prototype的独立性较高,可以很容易动态加载新功能而不影响老系的独立性较高,可以很容易动态加载新功能而不影响老系统。统。产品类不需要非得有任何事先确定的等级结构,因为产品类不需要非得有任何事先确定的等级结构,因为Prototype模式模式适用于任何的等级结构。适用于任何的等级结构。Prototype模式的缺点模式的缺点Prototype模式的最主要缺点就是每一个类必须配备一个克隆方法。模式的最主要缺点就是每一个类必须配备一个克隆方法。而且这个克隆方
48、法需要对类的功能进行通盘考虑,这对全新的类来说而且这个克隆方法需要对类的功能进行通盘考虑,这对全新的类来说不是很难,但对已有的类进行改造时,不一定是件容易的事。不是很难,但对已有的类进行改造时,不一定是件容易的事。9,Prototype原型模式总结Prototype模式就是用来(通过)复制(创建)对象的。模式就是用来(通过)复制(创建)对象的。什么时候应该使用什么时候应该使用prototype模式呢?模式呢?1,当你要创建的对象与现有运行时对象相似度很大时当你要创建的对象与现有运行时对象相似度很大时2,为了避免创建类的工厂类导致层次复杂度增加时为了避免创建类的工厂类导致层次复杂度增加时3,当类的实例只有不多的几种状态时(此时需要引进原当类的实例只有不多的几种状态时(此时需要引进原型管理器)型管理器)QuestionTimeThanks!