写点什么

设计模式【5】-- 原型模式

作者:秦怀杂货店
  • 2021 年 12 月 11 日
  • 本文字数:4464 字

    阅读完需:约 15 分钟

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


设计模式文章集合: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; }
@Override public 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; }
@Override protected 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; }
@Override protected 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++解法,敬请期待。




发布于: 4 小时前阅读数: 5
用户头像

纵使缓慢,驰而不息。 2018.05.17 加入

慢慢走,比较快。公众号:秦怀杂货店

评论

发布
暂无评论
设计模式【5】-- 原型模式