【设计模式】原型模式

news2025/1/23 1:10:41

原型模式属于创建型模式,主要作用是利用一个原型对象的克隆方法,在保证性能的情况下创建多个重复的对象,本质就是通过克隆一个原有的对象来复制出一个新对象。

文章目录

  • 原型模式的介绍
    • 使用场景
  • 原型模式的实现
    • 类图
    • 实现方法
      • 第一步,编写Sheep类,实现Cloneable接口
          • Sheep
      • 第二步,编写Test类测试
          • Test
          • 测试结果
  • 原型模式的扩展
    • 带原型管理器的原型模式
      • 类图
      • 第一步,编写Animal接口
          • Animal
      • 第二步,编写Sheep、Cat、Dog类
          • Sheep
          • Cat
          • Dog
      • 第三步,编写PrototypeManager
          • PrototypeManager
      • 第四步,编写Test测试
          • Test
          • 测试结果
    • 浅拷贝与深拷贝
      • 浅拷贝试验
        • 试验方法
          • Sheep类
          • CloneTest类
          • 试验结果
      • 深拷贝试验
        • 试验方法
          • Sheep类
          • CloneTest类
          • 试验结果

原型模式的介绍

​ 原型模式主要是为了解决创建一个新对象时产生的大量资源消耗(如硬件资源或网络资源)问题,它首先记录一个创建好的对象作为原型对象,之后需要创建新对象时直接通过该原型对象的克隆方法快速的复制出一个新对象,这样就跳过了对象的初始化过程,自然减少了初始化的资源消耗。当创建新对象的成本比较大,而同类型的对象间差别不大时,就可以使用原型模式来创建。

使用场景

  • 在循环体中创建大量对象
  • 初始化时需要消耗大量硬件或网络资源
  • 构建对象时需要进行大量的数据准备和权限校验操作
  • 一个对象存在多个访问者,而这些访问者都要修改其属性,此时考虑到性能与安全问题,可以使用原型模式来复制出多个该对象的副本供访问者修改,之后再通过复制对象来修改原型对象。

原型模式的实现

​ 在JAVA中可以使用Object clone()方法实现对象的克隆操作,但是,一个类需要实现Cloneable接口才能使用克隆方法,否则会报CloneNotSupportedException 异常。在此我将创建一个Sheep类,并要求循环创建一百万对象,为节省资源消耗,我决定使用原型模式进行实现。

类图

image-20221122105151685

实现方法

第一步,编写Sheep类,实现Cloneable接口

Sheep
package 设计模式.原型模式;

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


    public Sheep(String name, String color, int age) {
        this.name = name;
        this.color = color;
        this.age = age;
        // 模拟对构造方法进行数据准备操作,不做的话JVM会对new方法进行优化,那样new的效率肯定高于clone
        if (name != null){
            name += "羊";
            name = name.substring(name.length()-1);
        }
    }

    @Override
    protected Sheep clone(){
        try {
            return (Sheep) super.clone();
        }catch (CloneNotSupportedException e){
            // 没有实现Cloneable接口还调用clone()方法会抛CloneNotSupportedException异常
            System.out.println("克隆失败, 可能是未实现Cloneable接口!");
        }
        return null;
    }

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

第二步,编写Test类测试

Test
package 设计模式.原型模式;

public class Test {
    public static void main(String[] args) {
        long time = System.currentTimeMillis();
        // 模拟在循环体中大量创建对象
        for (int i = 0; i < 1000000 ; i++){
            Sheep sheep = new Sheep("Dolly","white", 5);
        }
        System.out.println("新建对象运行时间:" + (System.currentTimeMillis() - time) + "ms");
        
		// 准备一个原型对象
        Sheep prototypeObj =  new Sheep("Dolly","white", 5);
        time = System.currentTimeMillis();
        // 模拟在循环体中大量创建对象
        for (int i = 0; i < 1000000 ; i++){
            Sheep sheep = prototypeObj.clone();
        }
        System.out.println("原型模式创建对象运行时间:" + (System.currentTimeMillis() - time) + "ms");
    }
}

测试结果

image-20221122104930773




原型模式的扩展

​ 原型模式还可以扩展为带有原型管理器的原型模式,并且克隆方法具有浅拷贝深拷贝的区别。

带原型管理器的原型模式

​ 原型管理器可以管理多个类的原型对象,方便这些类通过原型模式创建对象。其原理是通过一个HashMap集合来管理每个原型对象,同时,为符合依赖倒置原则,多个原型类应当具实现同一个接口类,而管理器只需要对这个接口类进行管理即可。

类图

images202211221627593.png

第一步,编写Animal接口

Animal
package 设计模式.原型模式.原型管理器;

/**
 * 继承克隆接口,这样实现该接口的类都能正常使用克隆方法
 */
public interface Animal extends Cloneable {
    Animal clone();
    String toString();
}

第二步,编写Sheep、Cat、Dog类

Sheep
package 设计模式.原型模式;

import 设计模式.原型模式.原型管理器.Animal;

public class Sheep implements Animal {
	…… 内部属性与方法都和之前写的一样
}
Cat
package 设计模式.原型模式.原型管理器;

public class Cat implements Animal {
    private String name;
    private String color;
    private int age;

    public Cat(String name, String color, int age) {
        this.name = name;
        this.color = color;
        this.age = age;
        // 模拟对构造方法进行数据准备操作,不做的话JVM会对new方法进行优化,那样new的效率肯定高于clone
        if (name != null){
            name += "猫";
            name = name.substring(name.length()-1);
        }
    }

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

    @Override
    public Cat clone() {
        try {
            return (Cat) super.clone();
        } catch (CloneNotSupportedException e){
        }
        return null;
    }
}
Dog
package 设计模式.原型模式.原型管理器;

public class Dog implements Animal {
    private String name;
    private String color;
    private int age;

    public Dog(String name, String color, int age) {
        this.name = name;
        this.color = color;
        this.age = age;
        // 模拟对构造方法进行数据准备操作,不做的话JVM会对new方法进行优化,那样new的效率肯定高于clone
        if (name != null){
            name += "狗";
            name = name.substring(name.length()-1);
        }
    }

    @Override
    public Dog clone() {
        try {
            return (Dog) super.clone();
        } catch (CloneNotSupportedException e) {
        }
        return null;
    }

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

第三步,编写PrototypeManager

PrototypeManager
package 设计模式.原型模式.原型管理器;

import 设计模式.原型模式.Sheep;

import java.util.HashMap;

public class PrototypeManager {

    private HashMap<String, Animal> prototypeMap = new HashMap<>();

    public PrototypeManager(){
        put("cat", new Cat("Tom", "black", 7));
        put("dog", new Dog("福贵", "黑白相间", 23));
        put("sheep", new Sheep("Dolly","white", 5));
    }

    public void put(String key, Animal value){
        prototypeMap.put(key, value);
    }

    public Animal get(String key){
        Animal animal = prototypeMap.get(key);
        if (animal != null){
            return animal.clone();
        }
        return null;
    }
}

第四步,编写Test测试

Test
package 设计模式.原型模式.原型管理器;

import 设计模式.原型模式.Sheep;

public class Test {
    public static void main(String[] args) {
        PrototypeManager prototypeManager = new PrototypeManager();
        Cat cat = null;
        Dog dog = null;
        Sheep sheep = null;
        long time = System.currentTimeMillis();
        // 模拟在循环体中大量创建对象
        for (int i = 0; i < 1000000; i++) {
            cat = new Cat("Tom", "black", 7);
            dog = new Dog("Dog", "white", 5);
            sheep = new Sheep("Dolly", "white", 5);
        }
        System.out.println("通过构造方法new:");
        System.out.println(cat);
        System.out.println(dog);
        System.out.println(sheep);
        System.out.println("新建对象运行时间:" + (System.currentTimeMillis() - time) + "ms");

        time = System.currentTimeMillis();
        // 模拟在循环体中大量创建对象
        for (int i = 0; i < 1000000; i++) {
            cat = (Cat) prototypeManager.get("cat");
            dog = (Dog) prototypeManager.get("dog");
            sheep = (Sheep) prototypeManager.get("sheep");
        }
        System.out.println("原型管理器创建:");
        System.out.println(cat);
        System.out.println(dog);
        System.out.println(sheep);
        System.out.println("原型模式创建对象运行时间:" + (System.currentTimeMillis() - time) + "ms");
    }
}
测试结果

image-20221122114512138




浅拷贝与深拷贝

​ 原型模式下可以分为浅拷贝和深拷贝。Java中存在基础类型引用类型两种数据,浅拷贝在拷贝时会复制基础类型的值到新的变量中,但是,引用类型的数据是地址,就算把值复制到新的变量,地址值也还是指向原来的数据空间,所以浅拷贝下,引用数据不会被克隆,而是直接引用。若需改变引用地址,需要使用深拷贝。

浅拷贝试验

​ 浅拷贝无法为引用型属性复制一份新的数据地址空间,只是创建一个新的变量空间,然后将旧的引用数据地址复制给该变量。

试验方法

Sheep类
package 设计模式.原型模式;

import 设计模式.原型模式.原型管理器.Animal;

public class Sheep implements Animal {
    public String name;
    public String color;
    public int age;


    public Sheep(String name, String color, int age) {
        this.name = name;
        this.color = color;
        this.age = age;
        // 模拟对构造方法进行数据准备操作,不做的话JVM会对new方法进行优化,那样new的效率肯定高于clone
        if (name != null){
            name += "羊";
            name = name.substring(name.length()-1);
        }
    }
	
    /**
     * 浅拷贝
     * @return 通过浅拷贝复制的新对象
     */
    @Override
    public Sheep clone(){
        try {
            return (Sheep) super.clone();
        }catch (CloneNotSupportedException e){
            // 没有实现Cloneable接口还调用clone()方法会抛CloneNotSupportedException异常
            System.out.println("克隆失败, 可能是未实现Cloneable接口!");
        }
        return null;
    }

    @Override
    public String toString() {
        return "Sheep{" +
                "name='" + name + '\'' +
                ", color='" + color + '\'' +
                ", age=" + age +
                '}';
    }
}
CloneTest类
package 设计模式.原型模式.原型管理器;

import 设计模式.原型模式.Sheep;

public class CloneTest {
    public static void main(String[] args) {
        // 使用new String()可以创建一个新的引用对象,以此来区分浅拷贝下的引用变量是否被重新创建,但使用字面量都会指字符串常量池中的地址
        Sheep sheep = new Sheep(new String("Dolly"), "white", 5);
        Sheep sheep2 = new Sheep(new String("Dolly"), "white", 5);
        // 使用浅拷贝创建一个对象
        Sheep sheep3 = sheep.clone();

        System.out.println("sheep和sheep2是否指向同一个地址:" + (sheep == sheep2));
        System.out.println("sheep的引用属性和sheep2的引用属性是否指向同一个地址:" + (sheep.name == sheep2.name));
        System.out.println("因为使用了字面量,肯定都指向常量池中的地址:" + (sheep.color == sheep2.color));
        System.out.println("sheep和sheep3是否指向同一个地址:" + (sheep == sheep3));
        System.out.println("sheep的引用属性和sheep3的引用属性是否指向同一个地址:" + (sheep.name == sheep3.name));
        System.out.println("因为使用了字面量,肯定都指向常量池中的地址:" + (sheep.color == sheep3.color));

    }
}
试验结果

在这里插入图片描述

从截图结果来看,使用浅拷贝创建的新对象,所持有的引用型变量(String类型的name属性)于原型对象的引用型变量地址是相同的,那么就证明浅拷贝并不会复制一个新的引用型数据。



深拷贝试验

​ 在Java中,深拷贝需要使用序列化与反序列化来实现。通过序列化写入到流中的对象,不仅其本身要实现序列化,而且它所有的引用型属性也要实现序列化操作,此时通过序列化复制到流中的对象,不仅克隆了自身也克隆了它的所有引用属性。当把此对象从流中反序列化读取出来时,就已经完成了深拷贝。

试验方法

Sheep类
package 设计模式.原型模式;

import 设计模式.原型模式.原型管理器.Animal;

import java.io.*;

public class Sheep implements Animal,Serializable {
    
    /**
     * 深拷贝
     * @return 通过深拷贝复制的对象
     */
    public Sheep deepClone(){
        // 写入流的对象必须实现Serializable序列化接口,其引用型属性也要实现,这里的引用型属性只有String类型,已经自动实现过序列化接口
        ByteArrayOutputStream bos = null;
        ByteArrayInputStream bis = null;
        ObjectOutputStream oos = null;
        ObjectInputStream ois = null;
        Sheep sheep = null;
        try {
            bos = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(bos);
            oos.writeObject(this);
            bis = new ByteArrayInputStream(bos.toByteArray());
            ois = new ObjectInputStream(bis);
            sheep =  (Sheep) ois.readObject();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
          try {
              bos.close();
              bis.close();
              oos.close();
              ois.close();
          } catch (IOException e) {
          }
        }
        return sheep;
    }
    
    …… 其他属性和方法与浅拷贝的Sheep类相同

}

CloneTest类
package 设计模式.原型模式.原型管理器;

import 设计模式.原型模式.Sheep;

public class CloneTest {
    public static void main(String[] args) {
        // 使用new String()可以创建一个新的引用对象,以此来区分浅拷贝下的引用变量是否被重新创建,但使用字面量都会指字符串常量池中的地址
        Sheep sheep = new Sheep(new String("Dolly"), "white", 5);
        Sheep sheep2 = new Sheep(new String("Dolly"), "white", 5);
        // 使用浅拷贝创建一个对象
        Sheep sheep3 = sheep.clone();
        // 使用深拷贝创建一个对象
        Sheep sheep4 = sheep.deepClone();
        System.out.println("普通新建对象:");
        System.out.println("    sheep和sheep2是否指向同一个地址:" + (sheep == sheep2));
        System.out.println("    sheep的引用属性和sheep2的引用属性是否指向同一个地址:" + (sheep.name == sheep2.name));
        System.out.println("    因为使用了字面量,肯定都指向常量池中的地址:" + (sheep.color == sheep2.color));
        System.out.println("浅拷贝:");
        System.out.println("    sheep和sheep3是否指向同一个地址:" + (sheep == sheep3));
        System.out.println("    sheep的引用属性和sheep3的引用属性是否指向同一个地址:" + (sheep.name == sheep3.name));
        System.out.println("    因为使用了字面量,肯定都指向常量池中的地址:" + (sheep.color == sheep3.color));
        System.out.println("深拷贝:");
        System.out.println("    sheep和sheep4是否指向同一个地址:" + (sheep == sheep4));
        System.out.println("    sheep的引用属性和sheep4的引用属性是否指向同一个地址:" + (sheep.name == sheep4.name));
        System.out.println("    因为使用了字面量,肯定都指向常量池中的地址:" + (sheep.color == sheep4.color));

    }
}
试验结果

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/26027.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

一文解决 Go 安装和常用环境变量的配置

一文解决 Go 安装和常用环境变量的配置下载链接Windows 操作系统下安装Linux 操作系统下安装安装多个 Go 版本常用环境变量的解释结尾耐心和持久胜过激烈和狂热。 下载链接 官网&#xff1a;https://golang.org/dl中国大陆的镜像站点&#xff1a;https://golang.google.cn/dl/…

跨境电商卖家只青睐亚马逊?其实你不知道,“备胎”早已选好!(Starday)

跨境电商平台作为一个新的国际贸易形态&#xff0c;打破传统贸易形态的方式&#xff0c;将互联网与外贸交易相结合&#xff0c;从而大大降低外贸的运营成本&#xff0c;因此在这两年逐渐燃爆电商行业&#xff0c;让许多行业的人趋之若鹜&#xff0c;从观察中我们可以发现&#…

线性插值方法

插值&#xff0c;通俗来说当在一个离散的事件中&#xff0c;想知道某一个位置确定的值时&#xff0c;就可以利用插值方式计算得到&#xff0c;即利用已知数据估计未知位置数值。插值的方式有很多&#xff0c;下面介绍几种常用的插值方式。 一、最近邻插值(Nearest Neighbour …

矩阵分析:特征值分解都在这里了

矩阵分析&#xff1a;特征值分解前置知识空间变换伸缩旋转对称矩阵对称矩阵对角化正交矩阵向量的基基变换不同基下的向量变换逆矩阵不同基下的空间变换内积的几何意义特征值、特征向量特征值分解代码前置知识 空间变换 伸缩 一个矩阵其实就是一个线性变换&#xff0c;因为一个…

借道元宇宙 一汽-大众揽巡打造沉浸式上市体验

11月18日&#xff0c;一汽-大众以元宇宙科技为载体&#xff0c;举行了行业首场元宇宙游戏形式的“巡梦之旅”线上虚拟发布会&#xff0c;宣布旗下“硬核大五座SUV”揽巡Tavendor正式上市。全新揽巡共提供330TSI 精英巡行版、330TSI 豪华巡逸版、380TSI 四驱R-Line 巡游版、380T…

代码库制作与使用

静态库 假定有以下目录结构&#xff0c;main.c 为src测试文件&#xff0c;所以要调用src目录下的四个文件。我们可以把src打包成一个静态库供main.c使用 tree 命令展示目录结构制作 1.先把目标代码编译生成.o文件 需要包含头文件&#xff0c;否则会报错&#xff0c;使用-I来…

dB family cheat sheet: dB, dBW, dBm, dBi, dBc, etc

目录 1. dB 2. dBi 3. dBW和dBm 4. dBc(Decibel relative to carrier power level) 5. dBuV(dB over 1 microvolt), dBmV(dB over 1 millivolt) 6. 功率、电压、匹配阻抗 7. dBFS 1. dB dB用于表示功率之间的对数比率&#xff0c;所以它没有单位&#xff0c;其定义为&am…

【2022.11最新】Python疫情数据采集 + 可视化展示

嗨害大家好鸭&#xff01;我是小熊猫 最近我又又又在家里居家办公了 闲来无事&#xff0c;用python采集一下 再做个可视化 康康现在疫情如何 源码、资料素材电子书点击这里 知识点: 1. 爬虫基本流程 2. requests 发送请求 3. re 正则表达式 4. json 结构化数据解析 5. pyech…

【深度学习】CycleGAN开源项目学习笔记 | 完整流程 | 报错总结 | pytorch

文章目录前言一、下载项目&#xff0c;文件结构观察二、数据集下载三、训练3.1、训练初体验3.2、命令行输入参数3.3、继续训练命令四、预测4.1、使用自己训练完的权重文件进行预测4.2、使用网上的预训练文件进行预测前言 你敢想象&#xff0c;就是这么一个简单的开源网站&…

南卡电容笔和绿联哪款更好用?性价比高的电容笔推荐

随着生活节奏逐渐加快&#xff0c;从而Apple Pencil的出现引来了不少热度&#xff0c;不过Apple Pencil由于价格昂贵&#xff0c;让不少用户望而止步。然而现在出现了平替电容笔&#xff0c;而且品牌众多&#xff0c;有很多百元左右的平替电容笔&#xff0c;性能和配置都做的不…

适合运动的耳机有哪些?六款相当不错的运动耳机分享

户外运动是现代青年最常见的一种生活方式&#xff0c;无论是在公交地铁上&#xff0c;还是晚上散步的公园里&#xff0c;都能看到很多人在运动&#xff0c;然而他们在做运动的时候都会佩戴上一款运动耳机&#xff0c;音乐能够让我们沉醉于运动中。也越来越多人会问&#xff0c;…

一款可以阻止网络钓鱼诈骗的解决方案?

“你继承了一笔财富。要转账&#xff0c;我需要你的银行账户凭证。” 你是否也遇见过此类的电话诈骗话术。 根据2022年数据泄露调查报告&#xff0c;25%的数据泄露涉及网络钓鱼。 这是怎么发生的&#xff1f;参与网络钓鱼的欺诈者一般都是心理方面的高手。他们知道如何营造紧…

蓄电池电压检测单元 电池监控模块 24路电池电压采样模块电源检测

24路巡检单元的主要功能是对串联蓄电池组的单电池电压进行实时在线巡回检测&#xff0c;并智能分析电池的使用状况&#xff0c;根据用户设置不同的上下限、平均偏差自动判断并输出告警。本电池巡检单元主要应用于发电厂、变电站或其它行业中的直流电源、UPS电源的蓄电池组的电压…

载紫杉醇D-α-生育酚聚乙二醇1000琥珀酸酯/纳米粒包裹紫杉醇的肝素纳米粒

下面整理了载紫杉醇D-α-生育酚聚乙二醇1000琥珀酸酯/纳米粒包裹紫杉醇的肝素纳米粒&#xff0c;一起看&#xff01; 包裹紫杉醇的肝素纳米粒制备方法&#xff1a; 以肝素为原料制备生物素化包裹紫杉醇的肝素纳米粒,动态光散射仪测定其粒径及电位,透射电镜观察其形态,紫外分光…

cuda 编程:矩阵运算讲解

本文主要介绍用CUDA实现矩阵运算(C A x B)的几个基本方法&#xff0c;帮助大家理解矩阵在GPU上面的运算与CPU上的有何异同&#xff0c;通过实践上手CUDA的优化计算&#xff0c;相比基础方法&#xff0c;能提速10倍以上。 本文内容涉及到CUDA矩阵1D运算,2D运算,共享内存,CUBLAS…

808. 分汤 : 挺有意思的 DP 题

题目描述 这是 LeetCode 上的 808. 分汤 &#xff0c;难度为 中等。 Tag : 「数学」、「动态规划」、「线性 DP」 有 A 和 B 两种类型 的汤。一开始每种类型的汤有 n 毫升。有四种分配操作&#xff1a; 提供 100ml 的 汤A 和 0ml 的 汤B 。提供 75ml 的 汤A 和 25ml 的 汤B…

还在用Python爬壁纸网站?不如来试试搜索引擎的图片库

前言 嗨嗨&#xff0c;又来给你们带来爬美女照片的文章了 今天就不去搞什么壁纸网站了&#xff0c;之前也发了蛮多了 今天搞点不一样的&#xff0c;来试试搜索引擎的图片库 话不多说&#xff0c;直接用Python来开发一下此处资源&#xff01; 开发环境 & 第三方模块 环…

k3s安装

文章目录1. 文件准备2. 安装3. 查看4. rancher接入官方文档https://docs.rancher.cn/docs/k3s/installation/airgap/_index/1. 文件准备 从&#xff1a; https://github.com/k3s-io/k3s/releases 下载如下两个文件 二进制文件: k3s 文件镜像包&#xff1a;k3s-airgap-images-…

route -n 路由详情

添加网段路由,想要只显示U route add -net 192.168.1.0 netmask 255.255.255.0 dev ens192 [rootht23 k8snode]# route -n Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface 192.168.1.0 0.0.0.0 255.255.255.0 U 0 0 0 ens192 表示192.168.1.…

修改ZABBIX的logo,最终版方法,简单好用,适用于所有ZABBIX版本,一学即会!!!!!

前言&#xff1a;大家好&#xff0c;我是菜鸟阿贵&#xff0c;好久不见&#xff0c;最近我发现一款很不错的开源网络监控软件&#xff0c;他的优点就不说了&#xff0c;但是遇到一个问题&#xff0c;我们项目上需要用它集成&#xff0c;所以上面的ZABBIX的标志不好看&#xff0…