当前位置: 首页 > 后端技术 > Java

设计模式[5]--原型模式

时间:2023-04-01 15:45:10 Java

先从一张图说起,剩下的就靠写了...设计模式文章合集:http://aphysia.cn/categories/...前言接触过Spring的小伙伴或者Springboot同学可能都知道Bean默认是单例的,也就是全局共享同一个对象,不会因为不同的请求而使用不同的对象。我们不会在这里讨论单例。单例模式的好处和各种有兴趣的可以了解一下:http://aphysia.cn/archives/de...。除了单例,Spring还可以设置其他作用域,即scope="prototype",即原型模式。每次请求到来时,都会创建一个新对象。该对象是根据原型实例创建的。原型模式的定义原型模式也是创建模式的一种,是指用一个原型实例指定要创建的对象的类型,通过复制这些原型来创建一个新的对象。简单来说,就是抄袭。一般适用于:实例比较复杂,全量创建成本高,直接复制比较简单,构造函数比较复杂,创建可能会生成很多不需要的对象优点:隐藏了创建实例的具体细节的时候只有少数属性需要特化,可以直接使用原型模式复制的对象,修改即可达到目的。原型模式的实现一般来说,原型模式是用来复制对象的,所以被复制的对象必须有一个原型类,即Prototype,Prototype需要实现Cloneable接口。只有实现这个接口才能复制,然后重写clone()方法,也可以根据不同的类型快速获取原型对象。我们首先定义了一个原型类Fruit:publicabstractclassFruitimplementsCloneable{Stringname;浮动价格;publicStringgetName(){返回名称;}publicvoidsetName(Stringname){this.name=name;}publicfloatgetPrice(){返回价格;}publicvoidsetPrice(floatprice){this.price=price;}publicObjectclone(){对象克隆=null;试试{clone=super.clone();}catch(CloneNotSupportedExceptione){e.printStackTrace();}返回克隆;}@OverridepublicStringtoString(){return"Fruit{"+"name='"+name+'\''+",price="+price+'}';}}以及拓宽了水果类的实体类Apple,Pear,Watermelon:publicclassAppleextendsFruit{publicApple(floatprice){name="苹果";这个。价格=价格;}}publicclassPearextendsFruit{publicPear(浮动价格){name=“悉尼”;这个。价格=价格;}}publicclassWatermelonextendsFruit{publicWatermelon(floatprice){name="Watermelon";这个。价格=价格;第一次取的时候,根据不同的类型取出来,复制一次返回:static{Appleapple=newApple(10);fruitMap.put(apple.getName(),apple);梨pear=newPear(8);fruitMap.put(pear.getName(),pear);西瓜西瓜=新西瓜(5);水果地图。放(西瓜。getName(),西瓜);}publicstaticFruitgetFruit(Stringname){Fruitfruit=fruitMap.get(name);返回(水果)fruit.clone();}}测试得到不同的Fruit,对比两次获取的同类型,可以发现两次获取的同类型不是同一个对象:publicclassTest{publicstaticvoidmain(String[]args){水果苹果=FruitCache.getFruit("苹果");System.out.println(苹果);水果梨=FruitCache.getFruit("雪梨");System.out.println(梨);水果西瓜=FruitCache.getFruit("西瓜");System.out.println(西瓜);水果apple1=FruitCache.getFruit("苹果");System.out.println("是否是同一个对象"+apple.equals(apple1));}}结果如下:Fruit{name='Apple',price=10.0}Fruit{name='Sydney',price=8.0}Fruit{name='Watermelon',price=5.0}false再测试一下,看看如果里面的name属性是同一个对象:publicclassTest{publicstaticvoidmain(String[]args){Fruitapple=FruitCache.getFruit("apple");System.out.println(苹果);水果apple1=FruitCache.getFruit("苹果");System.out.println(apple1);System.out.println("是否为同一个对象:"+apple.equals(apple1));System.out.println("是否是同一个字符串对象:"+apple.name.equals(apple1.name));}}结果如下,里面的字符串还是使用同一个对象:Fruit{name='apple',price=10.0}Fruit{name='apple',price=10.0}是否是同一个对象:是否为false是同一个字符串对象:true这是为什么?因为上面用的clone()是浅拷贝!!!但是有一点,字符串在Java中是不可变的,如果修改了,原来的字符串不会被修改,由于这个属性的存在,类似于深拷贝。如果该属性是其他自定义对象,那就要注意了。浅拷贝不会真正复制对象,而只是复制一个引用。这里不得不介绍一下浅拷贝和深拷贝的区别:浅拷贝:没有真正的拷贝数据,只是拷贝一个指向数据内存地址的指针深拷贝:不仅新建一个指针,还拷贝一份数据内存如果我们使用Fruitapple=apple1,这只是复制了对象的引用,但本质上仍然是同一个对象。上面情况虽然对象不同,但是Apple属性的副本还是属于同一个引用,地址还是一样的。它们共享原始的属性对象名称。那么如何进行深拷贝呢?一般有以下解决方案:直接新建一个对象,不需要考虑序列化和反序列化:先序列化,再反序列化回来,就可以得到一个新的对象。请注意,必须实现Serializable接口。自己重写对象的clone()方法序列化,实现深拷贝序列化。实现代码如下:创建一个Student类和一个School类:importjava.io.Serializable;publicclassStudentimplementsSerializable{字符串名称;学校学校;publicStudent(Stringname,School学校){this.name=name;this.school=学校;}}导入java.io.Serializable;公共类SchoolimplementsSerializable{Stringname;公立学校(字符串名称){this.name=name;}}序列化复制类:importjava.io.ByteArrayInputStream;importjava.io.ByteArrayOutputStream;importjava.io.ObjectInputStream;importjava.io.ObjectOutputStream;importjava.io.Serializable;publicclassCloneUtil{publicstaticTclone(Tobj){Tresult=null;尝试{ByteArrayOutputStreambyteArrayOutputStream=newByteArrayOutputStream();ObjectOutputStreamobjectOutputStream=newObjectOutputStream(byteArrayOutputStream);objectOutputStream.close();ByteArrayInputStreambyteArrayInputStream=newByteArrayInputStream(byteArrayOutputStream.toByteArray());ObjectInputStreamobjectInputStream=newObjectInputStream(byteArrayInputStream);//返回生成的新对象result=(T)objectInputStream.readObject();objectInputStream.close();}catch(Exceptione){e.printStackTrace();}返回结果;}}测试类:publicclassTest{publicstaticvoidmain(String[]args){Schoolschool=newSchool("东部小学");Studentstudent=newStudent("小明",school);学生student1=CloneUtil.clone(student);System.out.println(student.equals(student1));System.out.println(student.school.equals(student1.school));}}以上结果都是false,说明确实不是同一个对象,发生了深拷贝clone实现深圳贝前面的Student和School都实现Cloneable接口,然后重新写clone()方法:publicclassStudentimplementsCloneable{Stringname;学校学校;publicStudent(Stringname,Schoolschool){this.name=name;this.school=学校;}@OverrideprotectedObjectclone()throwsCloneNotSupportedException{Studentstudent=(Student)super.clone();student.school=(学校)school.clone();返校生;}}publicclassSchoolimplementsCloneable{Stringname;公立学校(字符串名称){this.name=name;}@OverrideprotectedObjectclone()抛出CloneNotSupportedException{returnsuper.clone();}}测试类:publicclassTest{publicstaticvoidmain(String[]args)throwsException{Schoolschool=newSchool("东方小学");Studentstudent=newStudent("小明",school);学生student1=(Student)student.clone();System.out.println(student.equals(student1));System.out.println(student.school.equals(student1.school));}}测试结果一样,都是false,不同对象之间也发生了深拷贝,只需要自定义部分属性,其他都一样。一般来说,原型模式不会单独存在,会和其他模式一起使用。值得注意的是,拷贝分为浅拷贝和深拷贝。如果数据修改发生在浅拷贝中,不同对象的数据会被修改,因为它们共享元数据。【作者简介】:公众号【秦淮杂货铺】作者秦淮,技术之路不是一时的,山高水长,纵使慢也不会停。个人写作方向:Java源码分析、JDBC、Mybatis、Spring、redis、分布式、简知Offer、LeetCode等,认真写好每篇文章,不喜欢头条党,不喜欢花里胡哨,多写系列的文章,不保证我写的是完全正确的,但我保证我写的是经过实践或搜索过的资料。如有遗漏或错误,敬请指正。剑指提供所有问答PDF2020年我写了什么?开源编程笔记关注公众号“秦淮杂货铺”领取剑指OfferV1版PDF解法,V2版增加题型,还在持续更新中,每道题增加C++题型,敬请期待。