23种设计模式 —— 原型模式【克隆羊、浅拷贝、深拷贝】

本文涉及的产品
云原生大数据计算服务MaxCompute,500CU*H 100GB 3个月
云原生大数据计算服务 MaxCompute,5000CU*H 100GB 3个月
简介: 23种设计模式 —— 原型模式【克隆羊、浅拷贝、深拷贝】

前言

前面学习了工厂模式,今天给大家带来另一种Java设计模式:原型设计模式。这个模式较比于工厂模式,我用克隆羊的Java案例来进行讲解,就比较容易理解和使用,内容不多,希望大家喜欢💕

前提引用

假设有一只羊,叫做“多莉”(就是高中学的那个克隆羊多莉),年龄是3岁,颜色是白色。现在用编程实现对多莉的克隆:即克隆一只跟它一模一样的小羊(名字、年龄和颜色相同)

💨解决方式

一、🍂传统方式

🔥设计代码

先创建多莉这个小羊:

package java设计模式.prototype;

public class Sheep {
    private String name;

    private int age;

    private String color;

    public Sheep(String name, int age, String color) {

        this.name = name;

        this.age = age;

        this.color = color;

    }

    public String getName() {

        return name;

    }

    public void setName(String name) {

        this.name = name;

    }

    public int getAge() {

        return age;

    }

    public void setAge(int age) {

        this.age = age;

    }

    public String getColor() {

        return color;

    }

    public void setColor(String color) {

        this.color = color;

    }

    @Override

    public String toString() {

        return "Sheep [name=" + name + ", age=" + age + ", color=" + color + "]";

    }

}

创建类Client当做生物学家,操作克隆的整个过程

package java设计模式.prototype;

public class Client {
    public static void main(String[] args) {
        //原型小羊

        Sheep oldSheep = new Sheep("多莉", 3, "白色");

        //开始克隆

        Sheep newSheep = new Sheep(oldSheep.getName(), oldSheep.getAge(), oldSheep.getColor());

        System.out.println(oldSheep);

        System.out.println(newSheep);
    }
}

是如何操作的呢?由代码可以看出,当我们要克隆一个新的小羊的时候,我们在构造器中直接引用了原型小羊的getAge()getName()getColor()的方法,实现了全部的克隆。

运行结果 image.png

🔥优缺点比较

**优点:**

优点是比较好理解,简单易操作

**缺点:**

在创建新的对象时,总是需要重新获取原始对象的属性,如果创建的对象比较复杂时,效率较低

总是需要重新初始化对象,而不是动态地获得对象运行时的状态, 不够灵活

二、🍂原型模式

🔥思路分析

那么,该如何避免传统方式的缺点呢?

我们知道,Java中Object类是所有类的父类,Object类提供了一个clone()方法,该方法可以将一个Java对象复制一份,但是需要实现clone的Java类要实现一个接口Cloneable,该接口表示该类能够复制且具有复制的能力,由此来引出我们的原型模式。

🔥基本介绍

• 原型模式(Prototype模式)是指:用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象
• 原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象, 无需知道如何创建的细节
• `工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建
的对象通过请求原型对象拷贝它们自己来实施创建,即 对象.clone()`

🔥类图分析

image.png

原理结构说明:
Prototype:原型类,声明一个克隆自己的接口
ConcreteProtptype:具体的原型类,实现一个克隆自己的操作
Client:让一个原型对象克隆自己,从而创建一个新对象(属性一样)

🔥代码设计

让上面的Sheep类实现Cloneable接口后重写clone()方法,代码如下:

package java设计模式.prototype.improve;

public class Sheep implements Cloneable{

        private String name;

        private int age;

        private String color;

    public Sheep(String name, int age, String color) {

            this.name = name;

            this.age = age;

            this.color = color;

        }

        public String getName() {

            return name;

        }

        public void setName(String name) {

            this.name = name;

        }

        public int getAge() {

            return age;

        }

        public void setAge(int age) {

            this.age = age;

        }

        public String getColor() {

            return color;

        }
        //克隆该实例,使用默认的clone方法完成

        @Override
        protected Object clone()  {
            Sheep sheep = null;

            try {

                sheep= (Sheep) super.clone();

            } catch (Exception e) {

                System.out.println(e.getMessage());

            }

            return sheep;
        }

        public void setColor(String color) {

            this.color = color;

        }

        @Override

        public String toString() {

            return "Sheep [name=" + name + ", age=" + age + ", color=" + color + "]";

        }

    }

最后建一个Client类,用来测试:

package java设计模式.prototype.improve;

public class Client {
    public static void main(String[] args) {
        Sheep oldSheep = new Sheep("多莉", 3, "白色");
        Sheep sheep1 = (Sheep) oldSheep.clone();
        Sheep sheep2 = (Sheep) oldSheep.clone();
        System.out.println(sheep1);
        System.out.println(sheep2);
    }
}

image.png

💨方式比较

<font color=#AAA00>那么小伙伴们可能就要问了,这不跟方式一一样吗?没什么简便的地方啊,那么问题来了,假如那只多莉小羊来自于北京,我的克隆羊也必须来自于北京,用方式一的办法,是不是还需要从构造器中手动创建?如果要克隆一百只,一万只,一千万只小羊呢?用方式一是不是很麻烦!而用方式二,设定好需要克隆的小羊后,只需要使用clone()方法就可以一键克隆小羊了,再多的小羊,也不怕!

原型模式在Spring中的源码分析


浅拷贝

image.png

image.png

image.png

image.png

image.png

深拷贝

image.png

public class Sheep implements Cloneable {
    private String name;
    private int age;
    private String color;
    private String address="内蒙古";
    private Sheep friend;
    //....
}

public class Client {
    public static void main(String[] args) {
        Sheep sheep1 = new Sheep("Tom", 3, "白色");
        sheep1.setFriend(new Sheep("Bob", 2, "黑色"));
        Sheep sheep2 = (Sheep) sheep1.clone();
        Sheep sheep3 = (Sheep) sheep1.clone();
        //...
        System.out.println(sheep1.getFriend().hashCode());
        System.out.println(sheep2.getFriend().hashCode());
        System.out.println(sheep3.getFriend().hashCode());
    }
}

方式一:重新clone()方法(这里懒得写set、get了,就直接把变量声明为public)

package design_partten.prototype.type1.improve;

class Bird implements Cloneable{
    public String name;

    public Bird(String name) {
        this.name = name;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

public class Sheep implements Cloneable {
    public String name;
    public int age;
    public String color;
    public Bird friend;

    public Sheep(String name, int age, String color, Bird friend) {
        this.name = name;
        this.age = age;
        this.color = color;
        this.friend = friend;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {

        //1. 先拷贝基本数据类型的成员变量
        Sheep sheep = (Sheep) super.clone();

        //2. 拷贝引用类型的成员变量(即对引用类型成员再进行一次拷贝,然后赋值给刚刚拷贝的克隆羊)
        Bird friend = (Bird) this.friend.clone();
        sheep.friend = friend;

        return sheep;
    }
}

测试:

public class Client {
    public static void main(String[] args) throws CloneNotSupportedException {
        Sheep sheep1 = new Sheep("Tom", 3, "白色", new Bird("鸟"));
        Sheep sheep2 = (Sheep) sheep1.clone();
        Sheep sheep3 = (Sheep) sheep1.clone();
        //...
        System.out.println(sheep1.friend.hashCode());
        System.out.println(sheep2.friend.hashCode());
        System.out.println(sheep3.friend.hashCode());
    }
}

结果:

评价:通过重新clone()方法来实现深拷贝,虽然看上去简单,代码较少,但这是因为我们成员变量只有一个是引用类型,如果有很多成员变量都是引用类型,那么我们每个变量都要去写。不推荐。

方式二:通过对象的序列化方式实现(推荐)

//在Sheep类添加该方法,且Sheep和Bird实现Serialized接口
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);
        Sheep sheep = (Sheep)ois.readObject();

        return sheep;
    }catch (Exception e){
        e.getStackTrace();
        return null;
    }finally {
        //关闭流
        try {
            ois.close();
            bis.close();
            oos.close();
            bos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

📕总结

创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能够提高效率

不用重新初始化对象,而是动态地获得对象运行时的状态

如果原始对象发生变化(增加或者减少属性),其它克隆对象的也会发生相应的变化,无需修改代码

在实现深克隆的时候可能需要比较复杂的代码

缺点:需要为每一个类配备一个克隆方法,这对全新的类来说不是很难,但对已有的类进行改造时,需要修改其源代码,违背了ocp原则,这点需要特别注意.

相关实践学习
基于MaxCompute的热门话题分析
本实验围绕社交用户发布的文章做了详尽的分析,通过分析能得到用户群体年龄分布,性别分布,地理位置分布,以及热门话题的热度。
SaaS 模式云数据仓库必修课
本课程由阿里云开发者社区和阿里云大数据团队共同出品,是SaaS模式云原生数据仓库领导者MaxCompute核心课程。本课程由阿里云资深产品和技术专家们从概念到方法,从场景到实践,体系化的将阿里巴巴飞天大数据平台10多年的经过验证的方法与实践深入浅出的讲给开发者们。帮助大数据开发者快速了解并掌握SaaS模式的云原生的数据仓库,助力开发者学习了解先进的技术栈,并能在实际业务中敏捷的进行大数据分析,赋能企业业务。 通过本课程可以了解SaaS模式云原生数据仓库领导者MaxCompute核心功能及典型适用场景,可应用MaxCompute实现数仓搭建,快速进行大数据分析。适合大数据工程师、大数据分析师 大量数据需要处理、存储和管理,需要搭建数据仓库?学它! 没有足够人员和经验来运维大数据平台,不想自建IDC买机器,需要免运维的大数据平台?会SQL就等于会大数据?学它! 想知道大数据用得对不对,想用更少的钱得到持续演进的数仓能力?获得极致弹性的计算资源和更好的性能,以及持续保护数据安全的生产环境?学它! 想要获得灵活的分析能力,快速洞察数据规律特征?想要兼得数据湖的灵活性与数据仓库的成长性?学它! 出品人:阿里云大数据产品及研发团队专家 产品 MaxCompute 官网 https://www.aliyun.com/product/odps&nbsp;
相关文章
|
4月前
|
设计模式 Java 关系型数据库
【Java笔记+踩坑】设计模式——原型模式
对比原型模式和传统方式的实现思路、代码方案、优缺点,阐述原型模式的使用场景,以及深拷贝、浅拷贝等相关概念,并扩展原型模式在Spring源码中的应用。
|
4月前
|
设计模式 Java
Java设计模式-原型模式(3)
Java设计模式-原型模式(3)
Java设计模式-原型模式(3)
|
6月前
|
设计模式
iLogtail设计模式问题之iLogtail中的原型模式是什么
iLogtail设计模式问题之iLogtail中的原型模式是什么
iLogtail设计模式问题之iLogtail中的原型模式是什么
|
6月前
|
设计模式 JavaScript
js设计模式【详解】—— 原型模式
js设计模式【详解】—— 原型模式
61 6
|
7月前
|
设计模式 Java
Java设计模式之原型模式详解
Java设计模式之原型模式详解
|
7月前
|
设计模式
原型模式-大话设计模式
原型模式-大话设计模式
|
7月前
|
设计模式 Java Spring
设计模式——原型模式
设计模式——原型模式
|
6天前
|
设计模式 前端开发 搜索推荐
前端必须掌握的设计模式——模板模式
模板模式(Template Pattern)是一种行为型设计模式,父类定义固定流程和步骤顺序,子类通过继承并重写特定方法实现具体步骤。适用于具有固定结构或流程的场景,如组装汽车、包装礼物等。举例来说,公司年会节目征集时,蜘蛛侠定义了歌曲的四个步骤:前奏、主歌、副歌、结尾。金刚狼和绿巨人根据此模板设计各自的表演内容。通过抽象类定义通用逻辑,子类实现个性化行为,从而减少重复代码。模板模式还支持钩子方法,允许跳过某些步骤,增加灵活性。
|
2月前
|
设计模式 安全 Java
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
|
4月前
|
设计模式 数据库连接 PHP
PHP中的设计模式:提升代码的可维护性与扩展性在软件开发过程中,设计模式是开发者们经常用到的工具之一。它们提供了经过验证的解决方案,可以帮助我们解决常见的软件设计问题。本文将介绍PHP中常用的设计模式,以及如何利用这些模式来提高代码的可维护性和扩展性。我们将从基础的设计模式入手,逐步深入到更复杂的应用场景。通过实际案例分析,读者可以更好地理解如何在PHP开发中应用这些设计模式,从而写出更加高效、灵活和易于维护的代码。
本文探讨了PHP中常用的设计模式及其在实际项目中的应用。内容涵盖设计模式的基本概念、分类和具体使用场景,重点介绍了单例模式、工厂模式和观察者模式等常见模式。通过具体的代码示例,展示了如何在PHP项目中有效利用设计模式来提升代码的可维护性和扩展性。文章还讨论了设计模式的选择原则和注意事项,帮助开发者在不同情境下做出最佳决策。