05【原型设计模式】

追求适度,才能走向成功;人在顶峰,迈步就是下坡;身在低谷,抬足既是登高;弦,绷得太紧会断;人,思虑过度会疯;水至清无鱼,人至真无友,山至高无树;适度,不是中庸,而是一种明智的生活态度。

导读:本篇文章讲解 05【原型设计模式】,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文


五、原型设计模式

5.1 原型设计模式简介

5.1.1 什么是原型设计模式

  • 原型模式(Prototype Pattern):使用一个已经创建的实例对象作为原型实例,并且通过拷贝这些原型来创建新的对象;

原型设计模式的核心在于复制原型对象,讨论对象如何拷贝(克隆)才能更方便,简洁,高效等。以及解决拷贝过程中遇到的问题。有了原型设计模式就不需要我们来创建对象了,直接基于原型的实例对象进行克隆就可以;

场景举例-对一个对象进行复制:

package com.pattern;

import lombok.Data;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@Data
public class Resume {
    private String name;
    private Integer age;
    private String education;
    private String experience;

    public Resume resumeCopy(){
        Resume resume=new Resume();
        resume.setName(this.name);
        resume.setAge(this.age);
        resume.setEducation(this.education);
        resume.setExperience(this.experience);
        
        return this;
    }
}

上面的代码就是没有原型设计模式之前的对象复制,使用起来非常繁琐,麻烦。原型设计模式就是来帮助我们避免上述代码来负责对象,采用更加优雅的方式进行对象的复制;

  • 原型设计模式主要适用于以下应用场景:
    • 1)创建一个对象的成本大,例如初始化时间长,消耗资源大等;
    • 2)创建一个对象过程非常繁琐,麻烦;需要准备大量数据来创建这个对象

5.1.2 创建对象的方式

设计一个简历类,打印(创建)三份简历;

1)基于new的方式

  • 示例代码:
package com.pattern.demo01_类的拷贝;


import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@AllArgsConstructor
@NoArgsConstructor
public class Resume {
    private String name;
    private Integer age;
    private String education;
    private String experience;
    
    public void show() {
        System.out.println("姓名【" + name + "】,年龄【" + age + "】,学历【" + education + "】,经验【" + experience + "】");
        System.out.println("---------------");
    }
}
  • 准备个对象:
package com.pattern.demo01_类的拷贝;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01_直接new对象 {
    public static void main(String[] args) {
        Resume r1 = new Resume("小明", 20,"本科","3年");
        Resume r2 = new Resume("小明", 20,"本科","3年");
        Resume r3 = new Resume("小明", 20,"本科","3年");

        System.out.println(r1.hashCode());          // 21685669(没有重写的hashCode默认根据内存地址值来计算)
        System.out.println(r2.hashCode());          // 2133927002
        System.out.println(r3.hashCode());          // 1836019240
    }
}

2)基于反射的方式

  • 示例代码:
package com.pattern.demo01_类的拷贝;

import java.lang.reflect.Constructor;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo02_使用反射来创建对象 {
    public static void main(String[] args) throws Exception {
        // 获取类的字节码对象
        Class<Resume> resumeClass = Resume.class;

        // 获取空参构造方法
        Constructor<Resume> resumeConstructor = resumeClass.getDeclaredConstructor(String.class, Integer.class,String.class,String.class);

        Resume r1 = resumeConstructor.newInstance("小明", 20,"本科","3年");
        Resume r2 = resumeConstructor.newInstance("小明", 20,"本科","3年");
        Resume r3 = resumeConstructor.newInstance("小明", 20,"本科","3年");

        System.out.println(r1.hashCode());          // 21685669(没有重写的hashCode默认根据内存地址值来计算)
        System.out.println(r2.hashCode());          // 2133927002
        System.out.println(r3.hashCode());          // 1836019240
    }
}

可见,不管是基于new的方式创建对象,还是基于反射来创建对象都是比较繁琐的;

5.2.3 clone方法

在JDK中,提供有clone()方法来帮助我们对一个对象进行克隆操作,该对象必须实现Cloneable接口,代表该对象是可以被克隆的,否则将被抛出CloneNotSupportedException异常。clone()方法被native修饰;

在这里插入图片描述

  • 修改Resumer类,重写clone()方法:
package com.pattern.demo01_类的拷贝;


import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@AllArgsConstructor
@NoArgsConstructor
public class Resume implements Cloneable {           // 被克隆的类必须要实现Cloneable接口
    private String name;
    private Integer age;
    private String education;
    private String experience;

    public void show() {
        System.out.println("姓名【" + name + "】,年龄【" + age + "】,学历【" + education + "】,经验【" + experience + "】");
        System.out.println("---------------");
    }

    /**
     * 重写Object类完成对对象的克隆
     *
     * @return
     * @throws CloneNotSupportedException
     */
    @Override
    public Resume clone() throws CloneNotSupportedException {
        return (Resume) super.clone();
    }
}
  • 使用clone方法复制三份简历:
package com.pattern.demo01_类的拷贝;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo03_使用clone方法 {
    public static void main(String[] args) throws Exception {
        Resume r1 = new Resume("小明", 20, "本科", "3年");
        Resume r2 = r1.clone();
        Resume r3 = r1.clone();

        r1.show();
        r2.show();
        r3.show();

        System.out.println(r1 == r2);           // false
        System.out.println(r1 == r3);           // false
        System.out.println(r2 == r3);           // false
    }
}

运行结果如下:

在这里插入图片描述

需要注意的是:clone方法的本质是克隆,在堆内存中开辟了新的内存空间;就好比如每一份简历都是独立的存在;不存在第一份简历的内容上写着”内容在第二份简历上”

另外,clone方法是直接从堆内存中以二进制流的方式进行复制,并且重新分配一个内存块,因此效率非常高。由于clone方法基于内存复制,因此不会调用对象的构造函数,也就是不会经过对象的初始化过程;

  • 修改类:
package com.pattern.demo01_类的拷贝;


/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Resume implements Cloneable {           // 被克隆的类必须要实现Cloneable接口
    private String name;
    private Integer age;
    private String education;
    private String experience;

    public Resume() {
        System.out.println("Resume()...");
    }

    public Resume(String name, Integer age, String education, String experience) {
        System.out.println("Resume(String name, Integer age, String education, String experience)...");
        this.name = name;
        this.age = age;
        this.education = education;
        this.experience = experience;
    }

    public void show() {
        System.out.println("姓名【" + name + "】,年龄【" + age + "】,学历【" + education + "】,经验【" + experience + "】");
        System.out.println("---------------");
    }

    /**
     * 重写Object类完成对对象的克隆
     *
     * @return
     * @throws CloneNotSupportedException
     */
    @Override
    public Resume clone() throws CloneNotSupportedException {
        return (Resume) super.clone();
    }
}

  • 示例代码:
package com.pattern.demo01_类的拷贝;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo04_clone不是使用构造方法来创建对象 {
    public static void main(String[] args) throws Exception {
        // 执行有参构造方法
        Resume r1 = new Resume("小明", 20, "本科", "3年");

        // 底层并不是通过构造方法来创建对象(不会执行任何的构造方法)
        Resume r2 = r1.clone();
        Resume r3 = r1.clone();
    }
}

5.2 深克隆与浅克隆

在克隆对象时分为深克隆与浅克隆:

  • 浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。

  • 深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。

Object中的clone方法就是采用浅克隆;

5.2.1 浅克隆

  • 定义一个Person对象:
package com.pattern.demo02_浅克隆;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Person implements Cloneable{
    private String name;
    private Integer age;

    private Child child;

    @Override
    protected Person clone() throws CloneNotSupportedException {
        return (Person) super.clone();
    }
}
  • 定义一个Child对象:
package com.pattern.demo02_浅克隆;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Child {
    private String name;
    private Integer age;
}
  • 测试浅克隆:
package com.pattern.demo02_浅克隆;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01_测试浅克隆 {
    public static void main(String[] args) throws Exception {

        Person person = new Person("王刚", 20, new Child("王铁", 5));
        Person clone = person.clone();

        // 对应引用数据类型,都是引用同一块内存地址值
        System.out.println(person.getChild() == clone.getChild());          // true
        System.out.println("----------------");

        System.out.println(person.getName());
        System.out.println(person.getAge());
        System.out.println(person.getChild().getName());
        System.out.println(person.getChild().getAge());
        System.out.println("-----------------------------");

        clone.setName("李刚");
        clone.setAge(30);
        Child child = clone.getChild();

        /*
         对于引用数据类型(String除外),clone对象和person对象指向的是同一块内存地址值
         */
        child.setName("李铁");
        child.setAge(8);

        System.out.println(person.getName());                       // 王刚
        System.out.println(person.getAge());                        // 20
        System.out.println(person.getChild().getName());            // 李铁
        System.out.println(person.getChild().getAge());             // 8
        System.out.println("-----------------------------");
    }
}

运行结果:

在这里插入图片描述

5.2.2 深克隆

1)clone实现深克隆

深克隆的目的是对于对象中的每个引用数据类型都需要克隆一份新的数据;这样修改克隆出来的对象时不会影响原型对象;

  • 修改Child对象:
package com.pattern.demo03_深克隆;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Child implements Cloneable {
    private String name;
    private Integer age;

    @Override
    public Child clone() throws CloneNotSupportedException {
        return (Child) super.clone();
    }
}
  • 修改Person对象:
package com.pattern.demo03_深克隆;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Person implements Cloneable{
    private String name;
    private Integer age;

    private Child child;

    @Override
    protected Person clone() throws CloneNotSupportedException {
        Person person = (Person) super.clone();
        // 为每一个person对象都单独克隆一份child对象
        person.setChild(child.clone());
        return person;
    }
}
  • 测试类:
package com.pattern.demo03_深克隆;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01_测试深克隆 {
    public static void main(String[] args) throws Exception {

        Person person = new Person("王刚", 20, new Child("王铁", 5));
        Person clone = person.clone();

        // 对应引用数据类型,都是引用同一块内存地址值
        System.out.println(person.getChild() == clone.getChild());          // true
        System.out.println("----------------");

        System.out.println(person.getName());
        System.out.println(person.getAge());
        System.out.println(person.getChild().getName());
        System.out.println(person.getChild().getAge());
        System.out.println("-----------------------------");

        clone.setName("李刚");
        clone.setAge(30);

        Child child = clone.getChild();

        // 修改clone对象中的Child对象内容不会对原型对象中的Child对有影响
        child.setName("李铁");
        child.setAge(8);

        System.out.println(person.getName());                       // 王刚
        System.out.println(person.getAge());                        // 20
        System.out.println(person.getChild().getName());            // 李铁
        System.out.println(person.getChild().getAge());             // 8
        System.out.println("-----------------------------");
    }
}

运行结果:

在这里插入图片描述

使用clone()方法来完成深克隆会比较麻烦,如果Child中还存在一个Gradnson类,那么同样需要在Child的clone方法中设置Grandson类;

  • 添加Grandson:
package com.pattern.demo03_深克隆;

import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@AllArgsConstructor
@NoArgsConstructor
public class Grandson implements Cloneable{
    private String name;
    private Integer age;

    @Override
    protected Grandson clone() throws CloneNotSupportedException {
        return (Grandson) super.clone();
    }
}
  • 修改Child:
package com.pattern.demo03_深克隆;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Child implements Cloneable {
    private String name;
    private Integer age;

    private Grandson grandson;

    @Override
    public Child clone() throws CloneNotSupportedException {
        Child child = (Child) super.clone();
        child.setGrandson(grandson.clone());

        return child;
    }
}
  • 测试代码:
package com.pattern.demo03_深克隆;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01_测试深克隆 {
    public static void main(String[] args) throws Exception {

        Person person = new Person("王刚", 20, new Child("王铁", 5,new Grandson("王铜",1)));
        Person clone = person.clone();

        Child child = person.getChild();
        Grandson grandson = child.getGrandson();

        Child cloneChild = clone.getChild();
        Grandson cloneChildGrandson = cloneChild.getGrandson();


        System.out.println(child == cloneChild);                    // false    
        System.out.println(grandson == cloneChildGrandson);         // false
    }
}

2)序列化实现深克隆

  • Grandson:
package com.pattern.demo04_序列化实现深克隆;

import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;

import java.io.Serializable;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@AllArgsConstructor
@NoArgsConstructor
public class Grandson implements Serializable {         // 需要实现序列化接口
    private String name;
    private Integer age;
}
  • Child:
package com.pattern.demo04_序列化实现深克隆;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Child implements Serializable {        // 需要实现序列化接口
    private String name;
    private Integer age;

    private Grandson grandson;
}
  • Person:
package com.pattern.demo04_序列化实现深克隆;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Person implements Serializable {           // 需要实现序列化接口
    private String name;
    private Integer age;

    private Child child;
}
  • 测试代码:
package com.pattern.demo04_序列化实现深克隆;


import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01_测试序列化实现深克隆 {
    public static void main(String[] args) throws Exception {

        Person person = new Person("王刚", 20, new Child("王铁", 5, new Grandson("王铜", 1)));

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);

        // 把对象序列化到内存中
        oos.writeObject(person);

        // 从内存中反序列化出来
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));
        Person clone = (Person) ois.readObject();

        Child child = person.getChild();
        Grandson grandson = child.getGrandson();

        Child cloneChild = clone.getChild();
        Grandson cloneChildGrandson = cloneChild.getGrandson();

        System.out.println(child == cloneChild);                    // false
        System.out.println(grandson == cloneChildGrandson);         // false
    }
}

5.3 原型设计模式的应用

  • 简历类:
package com.pattern.demo05_原型设计模式的应用;


import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Resume implements Cloneable,Serializable {

    // 姓名
    private String name;

    // 年龄
    private Integer age;

    // 学历
    private String education;

    // 工作经历
    private WorkExperience workExperience;

    public void show() {
        System.out.println(
                "姓名【" + name + "】," +
                        "年龄【" + age + "】," +
                        "学历【" + education + "】," +
                        "公司【" + workExperience.getCompany() + "】," +
                        "薪资【" + workExperience.getSalary() + "】");
    }

    @Override
    protected Resume clone() throws CloneNotSupportedException {
        return (Resume) super.clone();
    }
}
  • 工作经历类:
package com.pattern.demo05_原型设计模式的应用;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@AllArgsConstructor
@NoArgsConstructor
@Data
public class WorkExperience implements Serializable {
    private String company;
    private String salary;
}
  • 浅克隆:
package com.pattern.demo05_原型设计模式的应用;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01_原型设计模式的应用_浅克隆 {
    public static void main(String[] args) throws Exception {

        Resume r1 = new Resume("小明", 25, "本科", new WorkExperience("京东", "12K"));
        r1.show();

        Resume r2 = r1.clone();
        r2.show();
    }
}

  • 深克隆:
package com.pattern.demo05_原型设计模式的应用;


import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo02_原型设计模式的应用_深克隆 {
    public static void main(String[] args) throws Exception {

        Resume r1 = new Resume("小明", 25, "本科", new WorkExperience("京东", "12K"));
        r1.show();

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);

        // 把对象序列化到内存中
        oos.writeObject(r1);

        // 从内存中反序列化出来
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));
        Resume r2 = (Resume) ois.readObject();

        r2.show();
    }
}

5.4 单例与原型设计模式

我们之前学习过单例设计模式,即在JVM进程中只会存在一个实例对象,如果复制的目标对象恰好是单例对象,那会不会破坏单例对象呢?当然,实际情况下我们肯定不会把单例类使用原型设计模式来拷贝;我们来测试一下单例类与原型设计模式,试看是否会破坏单例模式;

  • 示例代码:
package com.pattern.demo06_单例与原型;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class TestObj implements Cloneable {

    private static TestObj testObj=new TestObj();

    private TestObj(){}

    public static TestObj getInstance(){
        return testObj;
    }

    @Override
    public TestObj clone() throws CloneNotSupportedException {
        return (TestObj) super.clone();
    }
}
  • 测试类:
package com.pattern.demo06_单例与原型;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01 {
    public static void main(String[] args) throws Exception{
        TestObj test1=TestObj.getInstance();

        // false
        System.out.println( test1 == test1.clone());
    }
}

其实答案不难发现,肯定是会破坏单例设计模式的,因为我们前面提到过clone方法其实是使用内存技术在堆内存直接进行拷贝,然后重新分配一个内存块,并不会调用对象的构造函数,不会经过对象的初始化过程;

5.5 原型设计模式的优缺点

  • 优点:
    • 1)使用原型设计模式来创建对象,简化了创建对象的过程
    • 2)JDK自带的原型模式基于内存二进制流的赋值,性能上比直接new一个对象更加优良
  • 缺点:
    • 1)clone方法位于类的内部,当对已有类进行改造时,需要修改源代码,违背了开闭原则;

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/131720.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!