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

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

前言

接触过 Spring 或者 Springboot 的同学或者都理解, Bean 默认是单例的,也就是全局共用同一个对象,不会因为申请不同,应用不同的对象,这里咱们不会探讨单例,后面曾经探讨过单例模式的益处以及各种实现,有趣味能够理解一下:http://aphysia.cn/archives/de...。除了单例以外,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++解法,敬请期待。