开发者社区> 江夏、> 正文

从零开始学设计模式(三):原型模式(Prototype Pattern)

简介: 前面的一篇文章从零开始学设计模式(二):单例模式介绍了什么是单例模式以及单例模式的几种常见的实现方式。今天这篇文章接着介绍设计模式中的原型模式Prototype。
+关注继续查看

1 前言


前面的一篇文章从零开始学设计模式(二):单例模式介绍了什么是单例模式以及单例模式的几种常见的实现方式。今天这篇文章接着介绍设计模式中的原型模式Prototype。


2 原型模式Prototype Pattern


1、什么是原型模式


原型模式(Prototype Pattern)是 Java 中最简单的设计模式之一,属于创建型模式。原型模式使用原型实例指定创建对象的种类,并且通过拷贝原型对象创建新的对象。原型模式实际上就是从一个对象再创建另外一个可定制的对象,而且不需要知道任何创建的细节。这就好比模具的使用,我们可以通过螺丝的模具(原型实例)创建一个个具体的螺丝(新的对象),而不需要知道螺丝具体的创建过程。下图就很好的表现了原型模式使用的过程,图源自网络,侵删:


7eec316010e5437181fbd91da09ef84f~tplv-k3u1fbpfcp-zoom-in-crop-mark_1304_0_0_0.webp.jpg


2、原型模式的优点


a、使用原型模型创建一个对象比直接new一个对象更有效率,因为new产生一个对象需要非常繁琐的数据准备或访问权限,原型模式则直接操作内存中的二进制流,特别是复制大对象时,性能的差别非常明显。


b、原型模式隐藏了制造新实例的复杂性,使得创建对象就像复制粘贴一样简单,所以效率高。


3、原型模式的缺点


a、由于使用原型模式复制对象时不会调用类的构造方法,所以原型模式无法和单例模式组合使用,因为原型类需要将clone方法的作用域修改为public类型,那么单例模式的条件就无法满足了。


b、使用原型模式时不能有final对象。


c、Object类的clone方法只会拷贝对象中的基本数据类型,对于数组,引用对象等只能另行拷贝。这里涉及到深拷贝和浅拷贝的概念。


3 原型模式实现方式


前面说到原型模式的优点就是原型模式隐藏了制造新实例的复杂性,使得创建对象就像复制粘贴一样简单,所以效率高。 所以原型模式中实现起来最困难的地方就是内存复制操作,但是Java中就提供了clone()方法替我们做了绝大部分事情,不需要我们自己操作。所以原型模式的实现方式是:实现Cloneable接口和Object类中的clone方法:


1、实现Cloneable接口


克隆类似于new,但是又不同于new。new创建新的对象属性采用的是默认值;而克隆出的对象的属性值完全和原型对象相同,然后,再修改克隆对象的值,并且克隆出的新对象改变不会影响原型对象。


Cloneable接口的作用是在运行时通知虚拟机可以安全地在实现了此接口的类上使用clone方法。在java虚拟机中,只有实现了这个接口的类才可以被拷贝,否则在运行时会抛出CloneNotSupportedException异常。


2、重写Object类中的clone方法


Java中,所有类的父类都是Object类,Object类中有一个clone方法,作用是返回对象的一个拷贝,但是其作用域protected类型的,一般的类无法调用,因此,原型类需要将clone方法的作用域修改为public类型。


栗子:


作为一个程序员需要女朋友,我们可以随时给自己new一个prefect的对象,我们知道作为女朋友,那么性别肯定是女的(当然也可能不是),但是如果换了女朋友之后姓名、年龄、身高、体重等,这些数据一般来说是不一样的,那么我们可以通过原型模式来给自己创建对象了:


定义一个女朋友类:


package com.jiangxia.Prototype;
/**
 * @Author: 江夏
 * @Date: 2021/10/24/9:17
 * @Description:
 */
public class PrototypeDemo1 implements Cloneable{
    //性别
    private String gender;
    //年龄
    private int age;
    //姓名
    private String name;
    //体重
    private int weight;
    //身高
    private int height;
    public PrototypeDemo1(String gender){
        this.gender = gender;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getWeight() {
        return weight;
    }
    public void setWeight(int weight) {
        this.weight = weight;
    }
    public int getHeight() {
        return height;
    }
    public void setHeight(int height) {
        this.height = height;
    }
    public String getGender() {
        return gender;
    }
    public void setGender(String gender) {
        this.gender = gender;
    }
    @Override
    public PrototypeDemo1 clone(){
        PrototypeDemo1 prototypeDemo1 = null;
        try{
            //Object类的clone方法来完成内存中复制数据
            prototypeDemo1 = (PrototypeDemo1) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return prototypeDemo1;
    }
}
复制代码


测试下:


package com.jiangxia.Prototype;
/**
 * @Author: 江夏
 * @Date: 2021/10/24/9:27
 * @Description:
 */
public class PrototypeTest {
    public static void main(String[] args) {
        //对象性别肯定是女,这个是一致的不变的数据
        String gender = "女";
        PrototypeDemo1 prototypeDemo1 = new PrototypeDemo1(gender);
        //clone prototypeDemo1 并且设置clone部分的值,这块数据是可变的,每个地方不一样
        PrototypeDemo1 cloneprototypeDemo1 = prototypeDemo1.clone();
        cloneprototypeDemo1.setAge(20);
        cloneprototypeDemo1.setHeight(160);
        cloneprototypeDemo1.setWeight(50);
        cloneprototypeDemo1.setName("韩梅梅");
        PrototypeDemo1 cloneprototypeDemo2 = prototypeDemo1.clone();
        cloneprototypeDemo2.setAge(19);
        cloneprototypeDemo2.setHeight(168);
        cloneprototypeDemo2.setWeight(45);
        cloneprototypeDemo2.setWeight(45);
        cloneprototypeDemo2.setName("李华");
        System.out.println("第一个女朋友的数据是:她叫:"+cloneprototypeDemo1.getName()+";年龄:"+cloneprototypeDemo1.getAge()+";身高:"+cloneprototypeDemo1.getHeight()+";体重:"+cloneprototypeDemo1.getWeight()+";性别是:"+cloneprototypeDemo1.getGender());
        System.out.println("新女朋友的数据是:她叫:"+cloneprototypeDemo2.getName()+";年龄:"+cloneprototypeDemo2.getAge()+";身高:"+cloneprototypeDemo2.getHeight()+";体重:"+cloneprototypeDemo2.getWeight()+";性别肯定还是:"+cloneprototypeDemo2.getGender());
    }
}
复制代码


那么你女朋友的结果如下:


db8e1f80e5e54573a055669dfe61b163~tplv-k3u1fbpfcp-zoom-in-crop-mark_1304_0_0_0.webp.jpg


4 深拷贝与浅拷贝


在原型模式中还有两个概念:深拷贝和浅拷贝,也叫深克隆和浅克隆!这里克隆和拷贝是一样的!


浅拷贝:将一个对象复制后,基本数据类型的变量都会重新创建,而引用类型,而所有的对其他对象的引用都仍然指向原来的对象,所以这样是不安全的。


深拷贝:将一个对象复制后,不论是基本数据类型还有引用类型,都是重新创建的。把引用的变量指向复制过的新对象,而不是原有的被引用的对象。


那么深拷贝如何具体实现呢?


基本数据类型和String能够自动实现深拷贝(值的复制),其他的引用类型可以让已实现Clonable接口的类中的属性也实现Clonable接口。深拷贝实现方式有两种:


1、重写 clone 方法来实现深拷贝


2:通过对象序列化实现深拷贝


还是上面的例子,先看第一种:


package com.jiangxia.Prototype;
import java.io.Serializable;
/**
 * @Author: 江夏
 * @Date: 2021/10/24/10:21
 * @Description: 深拷贝重写clone方法来实现深拷贝
 */
public class DeepClonePrototypeDemo2 implements  Cloneable{
    //年龄
    private int age;
    //姓名
    private String name;
    //体重
    private int weight;
    //身高
    private int height;
    //构造器
    public DeepClonePrototypeDemo2(int age, String name, int weight, int height) {
        this.age = age;
        this.name = name;
        this.weight = weight;
        this.height = height;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getWeight() {
        return weight;
    }
    public void setWeight(int weight) {
        this.weight = weight;
    }
    public int getHeight() {
        return height;
    }
    public void setHeight(int height) {
        this.height = height;
    }
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
复制代码


package com.jiangxia.Prototype;
import java.io.Serializable;
/**
 * @Author: 江夏
 * @Date: 2021/10/24/10:24
 * @Description:
 */
public class DeepClonePrototypeDemo22 implements Cloneable,Serializable {
    //性别字段
    String gender;
    //其他数据引用类型
    DeepClonePrototypeDemo2 deepClonePrototypeDemo2;
    public DeepClonePrototypeDemo22(){
        super();
    }
    //深拷贝第一种方式:重写clone方法
    @Override
    protected Object clone() throws CloneNotSupportedException {
        Object deepclone = null;
        //这里完成了对基本数据类型(属性)和 String 的克隆,也就是值的复制
        deepclone = super.clone();
        //这里进行对引用类型的属性的复制进行处理
        DeepClonePrototypeDemo22 deepClonePrototypeDemo22 = (DeepClonePrototypeDemo22) deepclone;
        deepClonePrototypeDemo22.deepClonePrototypeDemo2 = (DeepClonePrototypeDemo2) deepClonePrototypeDemo2.clone();
        return deepClonePrototypeDemo22;
    }
}
复制代码


测试代码:


package com.jiangxia.Prototype;
/**
 * @Author: 江夏
 * @Date: 2021/10/24/10:36
 * @Description: 深拷贝测试代码
 */
public class DeepCloneTest {
    public static void main(String[] args) throws CloneNotSupportedException {
        DeepClonePrototypeDemo22 deep = new DeepClonePrototypeDemo22();
        deep.gender = "女";
        deep.deepClonePrototypeDemo2 = new DeepClonePrototypeDemo2(21, "韩梅梅",50,168);
        //重写clone方法 完成深拷贝
        DeepClonePrototypeDemo22 deep2 = (DeepClonePrototypeDemo22) deep.clone();
        System.out.println("性别:" + deep.gender + ";姓名:" +deep.deepClonePrototypeDemo2.getName()+ ";年龄:" +deep.deepClonePrototypeDemo2.getAge()+ ";身高:" +deep.deepClonePrototypeDemo2.getHeight()+ ";体重:" +deep.deepClonePrototypeDemo2.getWeight()+"||||"+deep.deepClonePrototypeDemo2.hashCode());
        System.out.println("深拷贝后性别:" + deep.gender + ";姓名:" +deep2.deepClonePrototypeDemo2.getName()+ ";年龄:" +deep2.deepClonePrototypeDemo2.getAge()+ ";身高:" +deep2.deepClonePrototypeDemo2.getHeight()+ ";体重:" +deep2.deepClonePrototypeDemo2.getWeight()+"||||"+deep2.deepClonePrototypeDemo2.hashCode());
    }
}
复制代码


结果如下:


97e198958f6b4f9e8d636afff450784c~tplv-k3u1fbpfcp-zoom-in-crop-mark_1304_0_0_0.webp.jpg


可以发现其他属性都一样,但是hashcode的值已经变了,不一样,说明对象的地址不是同一个,引用的已经不是同一个对象了!


继续上面的例子,看看使用序列化实现深拷贝:


//深拷贝第一种方式:使用序列化和反序列化实现深复制
    public Object deepClone() {
        //创建流对象,需要继承Serializable接口
        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);
            DeepClonePrototypeDemo22 copyObj = (DeepClonePrototypeDemo22) ois.readObject();
            return copyObj;
        }
        catch (Exception e) {
            return null;
        }
        finally {
            //关闭流
            try {
                bos.close();
                oos.close();
                bis.close();
                ois.close();
            }
            catch (Exception e2) {
                System.out.println(e2.getMessage());
            }
        }
    }
复制代码


测试用例代码:


//序列化实现深拷贝
DeepClonePrototypeDemo22 p3 = (DeepClonePrototypeDemo22) deep.deepClone();
System.out.println("性别:" + deep.gender + ";姓名:" +deep.deepClonePrototypeDemo2.getName()+ ";年龄:" +deep.deepClonePrototypeDemo2.getAge()+ ";身高:" +deep.deepClonePrototypeDemo2.getHeight()+ ";体重:" +deep.deepClonePrototypeDemo2.getWeight()+"||||"+deep.deepClonePrototypeDemo2.hashCode());
System.out.println("第二种深拷贝后性别:" + deep3.gender + ";姓名:" +deep3.deepClonePrototypeDemo2.getName()+ ";年龄:" +deep3.deepClonePrototypeDemo2.getAge()+ ";身高:" +deep3.deepClonePrototypeDemo2.getHeight()+ ";体重:" +deep3.deepClonePrototypeDemo2.getWeight()+"||||"+deep3.deepClonePrototypeDemo2.hashCode());
复制代码


运行结果如下:


0676cff46bf3492ebab02364fb6e26e5~tplv-k3u1fbpfcp-zoom-in-crop-mark_1304_0_0_0.webp.jpg


5 常见应用场景


原型模式很少单独出现,一般是和工厂方法模式一起出现,通过clone的方法创建一个对象,然后由工厂方法提供给调用者。在spring中bean的创建实际就是两种:单例模式和原型模式,并且原型模式需要和工厂模式搭配起来。


6 总结


以上就是我对于原型模式的一些简单的理解。


使用原型模式创建对象比直接new一个对象在性能上要好的多,因为Object类的clone方法是一个本地方法,它直接操作内存中的二进制流,特别是复制大对象时,性能的差别非常明显。


如果你觉得本文不错,就点赞分享给更多的人吧!


如果你觉得文章有不足之处,或者更多的想法和理解,欢迎指出讨论!

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
如何设置阿里云服务器安全组?阿里云安全组规则详细解说
阿里云安全组设置详细图文教程(收藏起来) 阿里云服务器安全组设置规则分享,阿里云服务器安全组如何放行端口设置教程。阿里云会要求客户设置安全组,如果不设置,阿里云会指定默认的安全组。那么,这个安全组是什么呢?顾名思义,就是为了服务器安全设置的。安全组其实就是一个虚拟的防火墙,可以让用户从端口、IP的维度来筛选对应服务器的访问者,从而形成一个云上的安全域。
19808 0
阿里云服务器如何登录?阿里云服务器的三种登录方法
购买阿里云ECS云服务器后如何登录?场景不同,阿里云优惠总结大概有三种登录方式: 登录到ECS云服务器控制台 在ECS云服务器控制台用户可以更改密码、更换系.
29161 0
阿里云服务器如何登录?阿里云服务器的三种登录方法
购买阿里云ECS云服务器后如何登录?场景不同,大概有三种登录方式:
13575 0
阿里云服务器安全组设置内网互通的方法
虽然0.0.0.0/0使用非常方便,但是发现很多同学使用它来做内网互通,这是有安全风险的,实例有可能会在经典网络被内网IP访问到。下面介绍一下四种安全的内网互联设置方法。 购买前请先:领取阿里云幸运券,有很多优惠,可到下文中领取。
22538 0
阿里云服务器ECS登录用户名是什么?系统不同默认账号也不同
阿里云服务器Windows系统默认用户名administrator,Linux镜像服务器用户名root
16451 0
阿里云服务器端口号设置
阿里云服务器初级使用者可能面临的问题之一. 使用tomcat或者其他服务器软件设置端口号后,比如 一些不是默认的, mysql的 3306, mssql的1433,有时候打不开网页, 原因是没有在ecs安全组去设置这个端口号. 解决: 点击ecs下网络和安全下的安全组 在弹出的安全组中,如果没有就新建安全组,然后点击配置规则 最后如上图点击添加...或快速创建.   have fun!  将编程看作是一门艺术,而不单单是个技术。
20699 0
腾讯云服务器 设置ngxin + fastdfs +tomcat 开机自启动
在tomcat中新建一个可以启动的 .sh 脚本文件 /usr/local/tomcat7/bin/ export JAVA_HOME=/usr/local/java/jdk7 export PATH=$JAVA_HOME/bin/:$PATH export CLASSPATH=.
14900 0
+关注
江夏、
一个写代码的....
146
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
JS零基础入门教程(上册)
立即下载
性能优化方法论
立即下载
手把手学习日志服务SLS,云启实验室实战指南
立即下载