设计模式之原型模式

1.前言

概念

原型模式(Prototype Pattern)是用于创建重复的对象同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。工作原理是将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝自己来实现创建过程。

使用原型模式非常简单,实现一个接口,重写一个方法即完成了原型模式。

使用场景

  • 创建新对象成本较大,新的对象可以通过原型模式对已有对象进行复制来获得
  • 如果系统要保存对象的状态,做备份使用

2.原型模式核心组成

  • Prototype: 声明克隆方法的接口,是所有具体原型类的公共父类,Cloneable接口
  • ConcretePrototype : 具体原型类
  • Client: 让一个原型对象克隆自身从而创建一个新的对象

UML图

请添加图片描述

因为克隆比较常用,所以Java提供了Cloneable接口,所以我们并不需要去编写Prototype接口,只需要实现Cloneable接口即可。不过Cloneable实现的是浅拷贝,如果想实现深拷贝,我们可以使用Serializable 接口。下面我们会细说分别使用浅拷贝与深拷贝实现原型模式。

3.浅拷贝与深拷贝

基本类型与引用类型

在Java中,基本类型有整数类型(long、int、short、byte),浮点类型(float、double),字符类型(char),布尔类型(boolean)。

除此之外所有的类型都是引用类型

从存储来说,基本类型与引用类型的区别在于:

  • 在方法中定义的非全局基本数据类型变量的具体内容是存储在栈中的
  • 引用数据类型变量,栈中存放的是其具体内容所在内存的地址,而其具体内容都是存放在堆中的

浅拷贝

在浅拷贝中:

  • 如果原型对象的成员变量是基本数据类型,将复制一份给克隆对象;
  • 如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象, 也就是说原型对象和克隆对象的成员变量指向相同的内存地址

代码演示

接下来我们使用Resume类来讲解。

具体原型类

package ProtoType.ShallowCopy;

import java.util.List;

/**
 * @Author: 少不入川
 * @Date: 2022/11/28 21:19
 */
public class Resume implements Cloneable{
    private String name;
    private int age;
    private List<String> technologyStack;

    public Resume(){}

    public Resume(String name, int age, List<String> technologyStack){
        this.name = name;
        this.age = age;
        this.technologyStack = technologyStack;
    }

    public List<String> getTechnologyStack() {
        return technologyStack;
    }

    public void setTechnologyStack(List<String> technologyStack) {
        this.technologyStack = technologyStack;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Resume{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", technologyStack=" + technologyStack +
                '}';
    }

    // 使用public修饰符方便调用
    @Override
    public Object clone(){
        Resume resume = null;
        try{
            resume = (Resume) super.clone();
        }catch (Exception e){
            e.printStackTrace();
        }
        return resume;
    }
}

测试类

package ProtoType.ShallowCopy;

import java.util.ArrayList;

/**
 * @Author: 少不入川
 * @Date: 2022/11/28 21:58
 */
public class ShallowCopyTest {
    public static void main(String[] args) {
        Resume resume1 = new Resume("张三", 20, new ArrayList<>());
        Resume resume2 = (Resume) resume1.clone();

        // 引用型变量使用==判等判断的是引用型变量指向地址是否相同
        System.out.println("两个对象所指向内存地址是否相同:" + (resume1 == resume2));
        System.out.println("两个对象的成员变量name所指向的内存地址是否相同:" + (resume1.getTechnologyStack() == resume2.getTechnologyStack()));
    }
}

控制台结果

两个对象所指向内存地址是否相同:false
两个对象的成员变量name所指向的内存地址是否相同:true

深拷贝

在深拷贝中:

  • 无论原型对象的成员变量是基本数据类型还是引用类型,都将复制一份给克隆对象

代码演示

具体原型类

package ProtoType.DeepCopy;

import java.io.*;
import java.util.List;

/**
 * @Author: 少不入川
 * @Date: 2022/11/28 21:58
 */
public class Resume implements Serializable {
    private String name;
    private int age;
    private List<String> technologyStack;

    public Resume(){}

    public Resume(String name, int age, List<String> technologyStack){
        this.name = name;
        this.age = age;
        this.technologyStack = technologyStack;
    }

    public List<String> getTechnologyStack() {
        return technologyStack;
    }

    public void setTechnologyStack(List<String> technologyStack) {
        this.technologyStack = technologyStack;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Resume{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", technologyStack=" + technologyStack +
                '}';
    }

    public Object deepClone() {
        //创建流对象
        ByteArrayOutputStream bos = null;
        ObjectOutputStream oos = null;
        ByteArrayInputStream bis = null;
        ObjectInputStream ois = null;

        try {
            //序列化
            bos = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(bos);
            //当前这个对象以对象流的方式输出
            oos.writeObject(this);

            //反序列化
            bis = new ByteArrayInputStream(bos.toByteArray());
            ois = new ObjectInputStream(bis);
            Resume resume = (Resume) ois.readObject();

            return resume;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        } finally {
            try {
                bos.close();
                oos.close();
                bis.close();
                ois.close();
            } catch (Exception e2) {
                System.out.println(e2.getMessage());
            }
        }
    }

}

测试类

package ProtoType.DeepCopy;

import java.util.ArrayList;

/**
 * @Author: 少不入川
 * @Date: 2022/11/28 22:09
 */
public class DeepCopyTest {
    public static void main(String[] args) {

        Resume resume1 = new Resume("张三", 20, new ArrayList<>());
        Resume resume2 = (Resume) resume1.deepClone();

        // 引用型变量使用==判等判断的是引用型变量指向地址是否相同
        System.out.println("两个对象所指向内存地址是否相同:" + (resume1 == resume2));
        System.out.println("两个对象的成员变量name所指向的内存地址是否相同:" + (resume1.getTechnologyStack() == resume2.getTechnologyStack()));
    }
}

控制台输出

两个对象所指向内存地址是否相同:false
两个对象的成员变量name所指向的内存地址是否相同:false

4.原型模式的优点与缺点

  • 优点
    • 当创建新的对象实例较为复杂时,使用原型模式可以简化对象的创建过程,可以提高新实例的创建效率
    • 可辅助实现撤销操作,使用深克隆的方式保存对象的状态,使用原型模式将对象复制一份并将其状态保存起来,以便在需要的时候使用恢复到历史状态
  • 缺点
    • 需要为每一个类配备一个克隆方法,对已有的类进行改造时,需要修改源代码,违背了“开闭原则”
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

少不入川。

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值