开局一张图,剩下全靠写…

设计模式文章集合:http://aphysia.cn/categories/designpattern

前言

接触过 Spring 或者 Springboot 的同学或许都了解, Bean 默认是单例的,也就是全局共用同一个对象,不会因为请求不同,使用不同的对象,这里我们不会讨论单例,前面已经讨论过单例模式的好处以及各种实现,有兴趣可以了解一下:http://aphysia.cn/archives/designpattern1。除了单例以外,Spring还可以设置其他的作用域,也就是scope="prototype",这就是原型模式,每次来一个请求,都会新创建一个对象,这个对象就是按照原型实例创建的。

原型模式的定义

原型模式,也是创建型模式的一种,是指用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象,简单来说,就是拷贝。一般适用于:

  • 实例比较复杂,完全创建成本高,直接复制比较简单
  • 构造函数比较复杂,创建可能产生很多不必要的对象

优点:

  • 隐藏了创建实例的具体细节
  • 创建对象效率比较高
  • 如果一个对象大量相同的属性,只有少量需要特殊化的时候,可以直接用原型模式拷贝的对象,加以修改,就可以达到目的。

原型模式的实现方式

一般来说,原型模式就是用来复制对象的,那么复制对象必须有原型类,也就是Prototype,Prototype需要实现Cloneable接口,实现这个接口才能被拷贝,再重写clone()方法,还可以根据不同的类型来快速获取原型对象。

我们先定义一个原型类Fruit

public abstract class Fruit implements Cloneable{String name;float price;public String getName() {return name;}public void setName(String name) {this.name = name;}public float getPrice() {return price;}public void setPrice(float price) {this.price = price;}public Object clone() {Object clone = null;try {clone = super.clone();} catch (CloneNotSupportedException e) {e.printStackTrace();}return clone;}@Overridepublic String toString() {return "Fruit{" +"name='" + name + '\'' +", price=" + price +'}';}
}

以及拓展了Fruit类的实体类Apple,Pear,Watermelon:

public class Apple extends Fruit{public Apple(float price){name = "苹果";this.price = price;}
}
public class Pear extends Fruit{public Pear(float price){name = "雪梨";this.price = price;}
}
public class Watermelon extends Fruit{public Watermelon(float price){name = "西瓜";this.price = price;}
}

创建一个获取不同水果类的缓存类,每次取的时候,根据不同的类型,取出来,拷贝一次返回即可:

public class FruitCache {private static ConcurrentHashMap<String,Fruit> fruitMap =new ConcurrentHashMap<String,Fruit>();static {Apple apple = new Apple(10);fruitMap.put(apple.getName(),apple);Pear pear = new Pear(8);fruitMap.put(pear.getName(),pear);Watermelon watermelon = new Watermelon(5);fruitMap.put(watermelon.getName(),watermelon);}public static Fruit getFruit(String name){Fruit fruit = fruitMap.get(name);return (Fruit)fruit.clone();}
}

测试一下,分别获取不同的水果,以及对比两次获取同一种类型,可以发现,两次获取的同一种类型,不是同一个对象:

public class Test {public static void main(String[] args) {Fruit apple = FruitCache.getFruit("苹果");System.out.println(apple);Fruit pear = FruitCache.getFruit("雪梨");System.out.println(pear);Fruit watermelon = FruitCache.getFruit("西瓜");System.out.println(watermelon);Fruit apple1 = FruitCache.getFruit("苹果");System.out.println("是否为同一个对象" + apple.equals(apple1));}
}

结果如下:


Fruit{name='苹果', price=10.0}
Fruit{name='雪梨', price=8.0}
Fruit{name='西瓜', price=5.0}
false

再测试一下,我们看看里面的name属性是不是同一个对象:

public class Test {public static void main(String[] args) {Fruit apple = FruitCache.getFruit("苹果");System.out.println(apple);Fruit apple1 = FruitCache.getFruit("苹果");System.out.println(apple1);System.out.println("是否为同一个对象:" + apple.equals(apple1));System.out.println("是否为同一个字符串对象:" + apple.name.equals(apple1.name));}
}

结果如下,里面的字符串确实还是用的是同一个对象:

Fruit{name='苹果', price=10.0}
Fruit{name='苹果', price=10.0}
是否为同一个对象:false
是否为同一个字符串对象:true

这是为什么呢?因为上面使用的clone()是浅拷贝!!!不过有一点,字符串在Java里面是不可变的,如果发生修改,也不会修改原来的字符串,由于这个属性的存在,类似于深拷贝。如果属性是其他自定义对象,那就得注意了,浅拷贝不会真的拷贝该对象,只会拷贝一份引用。

这里不得不介绍一下浅拷贝与深拷贝的区别:

  • 浅拷贝:没有真正的拷贝数据,只是拷贝了一个指向数据内存地址的指针
  • 深拷贝:不仅新建了指针,还拷贝了一份数据内存

如果我们使用Fruit apple = apple1,这样只是拷贝了对象的引用,其实本质上还是同一个对象,上面的情况虽然对象是不同的,但是Apple属性的拷贝还属于同一个引用,地址还是一样的,它们共享了原来的属性对象name

**那如何进行深拷贝呢?**一般有以下方案:

  • 直接 new 对象,这个不用考虑了

  • 序列化与反序列化:先序列化之后,再反序列化回来,就可以得到一个新的对象,注意必须实现Serializable接口。

  • 自己重写对象的clone()方法

序列化实现深拷贝

序列化实现代码如下:

创建一个Student类和School类:

import java.io.Serializable;public class Student implements Serializable {String name;School school;public Student(String name, School school) {this.name = name;this.school = school;}
}
import java.io.Serializable;public class School implements Serializable {String name;public School(String name) {this.name = name;}
}

序列化拷贝的类:

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;public class CloneUtil {public static <T extends Serializable> T clone(T obj) {T result = null;try {ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);objectOutputStream.writeObject(obj);objectOutputStream.close();ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);// 返回生成的新对象result = (T) objectInputStream.readObject();objectInputStream.close();} catch (Exception e) {e.printStackTrace();}return result;}
}

测试类:


public class Test {public static void main(String[] args) {School school = new School("东方小学");Student student =new Student("小明",school);Student student1= CloneUtil.clone(student);System.out.println(student.equals(student1));System.out.println(student.school.equals(student1.school));}
}

上面的结果均是false,说明确实不是同一个对象,发生了深拷贝。

clone实现深拷贝

前面的StudentSchool都实现Cloneable接口,然后重写clone()方法:


public class Student implements Cloneable {String name;School school;public Student(String name, School school) {this.name = name;this.school = school;}@Overrideprotected Object clone() throws CloneNotSupportedException {Student student = (Student) super.clone();student.school = (School) school.clone();return student;}
}

public class School implements Cloneable {String name;public School(String name) {this.name = name;}@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}
}

测试类:

public class Test {public static void main(String[] args) throws Exception{School school = new School("东方小学");Student student =new Student("小明",school);Student student1= (Student) student.clone();System.out.println(student.equals(student1));System.out.println(student.school.equals(student1.school));}
}

测试结果一样,同样都是false,也是发生了深拷贝。

总结

原型模式适用于创建对象需要很多步骤或者资源的场景,而不同的对象之间,只有一部分属性是需要定制化的,其他都是相同的,一般来说,原型模式不会单独存在,会和其他的模式一起使用。值得注意的是,拷贝分为浅拷贝和深拷贝,浅拷贝如果发生数据修改,不同对象的数据都会被修改,因为他们共享了元数据。

【作者简介】
秦怀,公众号【秦怀杂货店】作者,技术之路不在一时,山高水长,纵使缓慢,驰而不息。个人写作方向:Java源码解析JDBCMybatisSpringredis分布式剑指OfferLeetCode等,认真写好每一篇文章,不喜欢标题党,不喜欢花里胡哨,大多写系列文章,不能保证我写的都完全正确,但是我保证所写的均经过实践或者查找资料。遗漏或者错误之处,还望指正。

剑指Offer全部题解PDF

2020年我写了什么?

开源编程笔记

关注公众号 ”秦怀杂货店“ 可以领取剑指 Offer V1版本的 PDF解法,V2版本增加了题目,还在哼哧哼哧的更新中,并且为每道题目增加了C++解法,敬请期待。

设计模式【5】-- 原型模式相关推荐

  1. 【GOF23设计模式】原型模式

    [GOF23设计模式]原型模式 来源:http://www.bjsxt.com/  一.[GOF23设计模式]_原型模式.prototype.浅复制.深复制.Cloneable接口  浅复制 1 pa ...

  2. 乐在其中设计模式(C#) - 原型模式(Prototype Pattern)

    [索引页] [源码下载] 乐在其中设计模式(C#) - 原型模式(Prototype Pattern) 作者:webabcd 介绍 用原型实例指定创建对象的种类,并且通过拷贝这个原型来创建新的对象. ...

  3. 教你如何一篇博客读懂设计模式之—--原型模式

    教你如何一篇博客读懂设计模式之----原型模式 what:是什么 原型模式: 用于创建重复的对象,既不用一个属性一个属性去set和get,又不影响性能,原型模式产生的对象和原有的对象不是同一个实例,他 ...

  4. 二十三种设计模式之原型模式

    今天继续探讨GOF二十三种设计模式的原型模式,原型模式也是属于创建型模式的一种 原型模式通俗的讲就是对象复制的过程,即通过一个原型对象,我可以得到一个该对象的克隆. 下面来看下原型模式的第一种写法-- ...

  5. C#设计模式(6)——原型模式(Prototype Pattern)

    一.引言 在软件系统中,当创建一个类的实例的过程很昂贵或很复杂,并且我们需要创建多个这样类的实例时,如果我们用new操作符去创建这样的类实例,这未免会增加创建类的复杂度和耗费更多的内存空间,因为这样在 ...

  6. 设计模式回顾——原型模式(C++)

    文章目录 1 前言 2 什么是原型模式 2.1 原型模式组成 2.2 原型模式UML图 2.3 原型模式作用 3 原型模式优缺点 4 什么地方使用原型模式 5 原型模式实现 6 原型模式与构造函数 1 ...

  7. 设计模式之 原型模式

    原型模式应用场景举例:  GG和MM经常在QQ上聊天,但是GG打字的速度慢如蜗牛爬行,每次MM在瞬间完成恢复或者问候是,GG都会很紧张的去尽力快速打字,尽管如此,还是让MM有些不高心,MM说回复信息这 ...

  8. 设计模式之原型模式(Prototype)摘录

    23种GOF设计模式一般分为三大类:创建型模式.结构型模式.行为模式. 创建型模式包括:1.FactoryMethod(工厂方法模式):2.Abstract Factory(抽象工厂模式):3.Sin ...

  9. golang设计模式之原型模式

    原型模式 wiki:原型模式是创建型模式的一种,其特点在于通过"复制"一个已经存在的实例来返回新的实例,而不是新建实例.被复制的实例就是我们所称的"原型",这个 ...

  10. 【设计模式】原型模式 ( 概念简介 | 使用场景 | 优缺点 | 基本用法 )

    文章目录 I . 原型模式 概念简介 II . 原型模式 使用场景 III . 原型模式 优缺点 IV . 原型模式 实现及 简单示例 I . 原型模式 概念简介 原型模式 : 用原型实例指定创建对象 ...

最新文章

  1. php ajax session失效,PHP中解决ajax请求session过期退出登录问题
  2. 有什么类型的MPLS?
  3. 四十四种Javascript技巧大全
  4. 1.9 Java转换流:InputStreamReader和OutputStreamWriter
  5. window mobile 防止系统休眠代码
  6. JDK的动态代理深入解析(Proxy,InvocationHandler)(转)
  7. iOS 之UITextFiled/UITextView小结
  8. UVA 11210 中国麻将
  9. numpy.linspace()的使用方法
  10. jQuery+AJAX+PHP+MySQL数据库开发搜索功能,无跳转无刷新搜索。
  11. FreeRTOS源码分析与应用开发05:信号量
  12. typora工具的使用方法-一款非常适合程序员的工具
  13. 学习PHP的必备开发工具
  14. python爬虫下载文件到指定文件夹_python - 图片爬虫时候遇到问题 urllib.request.urlretrieve 下载到指定文件夹不成功?...
  15. DirectX修复工具
  16. iOS打包ipa无签名打包企业签
  17. 福特汉姆大学计算机科学专业,福特汉姆大学研究生学院
  18. python day46
  19. jason by gson复习
  20. 大数乘法(快速傅立叶变换)上

热门文章

  1. adb填充安卓手机的内存
  2. 教你如何在Sco Unix5.05安装大硬盘(启动输入硬盘参数方法)
  3. 简易命令行界面的C/S聊天室
  4. 文本输入框input实现字母大小写转换
  5. 超好用的免费PDF转换器,各种互相转换,功能齐全到你不敢相信!
  6. pdf转json_pdf转长图工具
  7. docker service
  8. 如何使用python画一个爱心
  9. windows下如何用python控制打印机打印_巧用win32print来控制windows系统打印机并推送打印任务...
  10. php 朋友圈留言,php实例-PHP仿qq空间或朋友圈发布动态、评论动态、回复评论、删除动态或评论的功能(上)...