设计模式 - 原型模式,就该这样学!

 

目录

开始

为什么要引入原型模式

原型模式概述

原型模式代码实现(浅拷贝)

浅拷贝和深拷贝的区别

原型模式代码实现(深拷贝)

方式一:直接 copy

方式二:序列化和反序列化(推荐)


开始


为什么要引入原型模式

a)问题:现在有一个小猪猪,名字为 lyj,年龄为:3,颜色为:粉色,请编写程序创建和 lyj 猪猪 属性完全相同的 10 头猪猪.

b)传统的处理方式如下:

基于 lyj 这个猪猪,直接 new 出 10 只一样的猪猪.

data class Pig(
    val name: String,
    val age: Int,
    val color: String,
)

fun main() {
    val pig = Pig("lyj", 3, "粉色")

    val p1 = Pig(pig.name, pig.age, pig.color)
    val p2 = Pig(pig.name, pig.age, pig.color)
    val p3 = Pig(pig.name, pig.age, pig.color)
    val p4 = Pig(pig.name, pig.age, pig.color)
    //...
    
    println(p1)
    println(p2)
    println(p3)
    println(p4)
    //...

}

c)传统方式创建的优缺点:

优点:

  • 好理解,操作简单.

缺点:

  • 创建新的对象时,总是需要重新获取原始对象的属性,如果创建的对象比较复杂时,效率比较低(可能含有大量的计算操作).
  • 总是需要重新初始化对象,而不是动态的获取对象运行时的状态,不够灵活.

原型模式概述

原型模式(Prototype Pattern)是一种创建型的设计模式,运行一个对象通过复制自身的实例来创建新的对象.   简而言之,提供了一个快速创建新对象的方法,这个新对象是已有对象的精确复制品(根据具体的业务场景,决定是深拷贝 还是 浅拷贝).

优点:

  • 简化对象创建:复制复杂对象时,可以通过原型模式简化对象的复制过程,并且可以动态的配置对象状态.
  • 性能提升:同时提升了对象创建的速度(某些情况下,复制现有对象比创建新对象更节省资源,例如大型对象创建可能涉及到大量的计算 或者 I/O 操作,原型模式可以避免这些重复初始化的资源开销,直接复制即可).
  • 解耦合,高扩展:如果原始对象发生变化(增加或者减少属性),其他克隆对象也会发生响应的变化,无需修改代码.

缺点:

  • 由于要对被复制的类配备一个克隆方法,因此如果是对老代码的改造,就需要修改代码,违背了开闭原则.

角色如下:

  • 原型类(Prototype):原型类,声明一个克隆自己的接口.
  • 具体的原型类(ConcretePrototype):实现一个克隆自己的操作.
  • 客户端(Cline):让一个原型对象克隆自己,从而创建一个新的对象(属性一样).

原型模式代码实现(浅拷贝)

a)猪猪类先实现克隆接口

data class Pig(
    val name: String,
    val age: Int,
    val color: String,
) {

    /**
     * Kotlin 的数据类中默认提供了 copy 方法用于浅拷贝
     */
    fun clone(): Any {
        return this.copy()
    }

}

b)客户端

fun main() {
    val pig = Pig("lyj", 3, "pink")

    val p1 = pig.clone()
    val p2 = pig.clone()
    val p3 = pig.clone()
    //...

    println("$p1, ${p1.hashCode()}")
    println("$p2, ${p2.hashCode()}")
    println("$p3, ${p3.hashCode()}")
    //...
}

c)运行结果如下:

 

d)注意:

kotlin 的数据类默认提供了 copy() 方法,用于实现对这个对象的 浅拷贝,但如果在 copy 时传入其他参数,就会为当前对象创建新对象,但是对内的引用变量还是传入地址(Pig 的 hashCode 不一致,单 child 的hashCode 一致)

例如,复制当前对象并修改 name

data class Pig(
    var name: String,
    val age: Int,
    val color: String,
    val child: PigChild
) {

    /**
     * kotlin 的数据类默认提供了 copy() 方法,用于实现对这个对象的 浅拷贝
     * 但如果在 copy 时传入其他参数,就会为当前对象创建新对象,但是对内的引用变量还是传入地址(Pig 的 hashCode 不一致,单 child 的hashCode 一致)
     * 例如,复制当前对象并修改 name
     */
    fun copyWithId(name: String): Pig {
        return this.copy(name = name)
    }

}

data class PigChild (
    var name: String,
)

fun main() {
    val p = Pig("lyj", 1, "pink", PigChild("aaa"))

    val p2 = p.copy(name = "ccc")

    println("${p.hashCode()}, ${p.child.hashCode()}")
    println("${p2.hashCode()}, ${p2.child.hashCode()}")
}

执行如下: 

 

 

浅拷贝和深拷贝的区别

成员变量中有 基本数据类型 和 引用数据类型.

a)浅拷贝

基本数据类型:复制 值 ,给新开辟空间的成员变量.

引用数据类型:复制 地址,给新开辟空间的成员变量.

b)深拷贝

基本数据类型:复制 值 ,给新开辟空间的成员变量.

引用数据类型:复制 该引用对象中所有的成员变量所引用的对象,直到所有可达的对象.  也就是对整个对象进行拷贝.

c)可以这样理解,浅拷贝只是对最外层的对象进行了 1 层拷贝,而深拷贝则是对象中无论有多少嵌套对象,都会进行拷贝(进行了 n 层拷贝).

原型模式代码实现(深拷贝)

方式一:直接 copy

实际上就是对成员变量中的所有 引用类型 都使用 copy 进行一遍处理.

data class Child(val name: String)

data class Parent(val name: String, val child: Child) {
    fun deepCopy(): Parent {
        return this.copy(child = this.child.copy())
    }
}

fun main() {
    val p = Parent("John", Child("New York"))

    val p1 = p.deepCopy()

    println(p == p1) //true
    println(p === p1) //false
    println(p.child === p1.child) //false
}

 

但是这种方式的缺点就是,将来如果 Parent 中有很多个成员变量都是引用对象 ,将来必须要一个个都进行 copy.

Ps:kotlin 中 == 表示比较两个对象的内容是否相等.  === 表示两个引用是否指向同一个对象.

方式二:序列化和反序列化(推荐)

推荐使用这种方式.

如果类中有多个引用对象,就不需要一个个处理了.

data class Child(
    val name: String
): Serializable

data class Parent(
    val name: String,
    val child: Child
): Serializable {

    fun deepCopy(): Parent {
        //序列化
        val bo = ByteArrayOutputStream()
        ObjectOutputStream(bo).use { it.writeObject(this) }
        //反序列化
        val bi = ByteArrayInputStream(bo.toByteArray())
        return ObjectInputStream(bi).use { it.readObject() as Parent }
    }

}

fun main() {
    val p = Parent("John", Child("New York"))

    val p1 = p.deepCopy()

    println(p == p1) //true
    println(p === p1) //false
    println(p.child === p1.child) //false
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ApolloClient GraphQL 与 ReactNative

要在 React Native 应用程序中设置使用 GraphQL 的简单示例,您需要遵循以下步骤: 设置一个 React Native 项目。安装 GraphQL 必要的依赖项。创建一个基本的 GraphQL 服务器(或使用公共 GraphQL 端点)。从 React Native 应用中的…

window下git bash设置启动后默认路径进入自己的工程

方法一:更改快捷方式 方法二:修改~/.bashrc

c++类和对象(三)日期类

类和对象 一.拷贝构造函数定义二.拷贝构造函数特征三.const成员函数权限权限的缩小权限的缩放大 四.隐式类型转换 一.拷贝构造函数定义 拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存 在的类类型对象…

期末模拟题---期末复习3

头插法建立单链表 #include <stdio.h> #include <stdlib.h>struct Node //定义结构体 {char data; //数据域struct Node * next; //指针域 };/* 请在这里填写答案 */ struct Node * CreateList (struct Node * head) {struct Node *p;char ch;scanf(&…

Json与Java类

简介 JSON&#xff08;JavaScript Object Notation&#xff09;是一种轻量级的数据交换格式&#xff0c;易于人阅读和编写&#xff0c;同时也易于机器解析和生成。JSON数据由键值对构成&#xff0c;并以易于阅读的文本形式展现&#xff0c;支持数组、对象、字符串、数字、布尔值…

第十一节:学习通过动态调用application.properties参数配置实体类(自学Spring boot 3.x的第二天)

大家好&#xff0c;我是网创有方。这节实现的效果是通过代码灵活地调用application.properties实现配置类参数赋值。 第一步&#xff1a;编写配置类 package cn.wcyf.wcai.config;import org.springframework.beans.factory.annotation.Value; import org.springframework.boo…

ManicTime(屏幕时间统计工具) 专业版值得购买吗

ManicTime 是 Windows 平台上&#xff0c;一款支持跟踪、标记用户在每个软件上所花时间的工具&#xff0c;它能自动归类生成时间使用报表&#xff0c;帮助用户分析及改善工作效率。 ManicTime 不仅会在后台记录、统计所有窗口的使用时间&#xff0c;还能自动截图存档到本地&a…

Matlab|【需求响应】空调负荷需求响应模型

1主要内容 程序主要复现《溫控负荷的需求响应潜力评估及其协同优化管理研究_谢敦见》2.5部分章节的内容&#xff0c;建立空调负荷的聚合模型&#xff0c;考虑调节空调温度对空调响应潜力的影响&#xff0c;程序结果充分说明随着上调温度增大&#xff0c;响应程度逐渐增大。 具…

【算法训练记录——Day36】

Day36——贪心Ⅳ 1.leetcode_452用最少数量的箭引爆气球2.leetcode_435无重叠区间3.leetcode_763划分字母区间4.leetcode_ 1.leetcode_452用最少数量的箭引爆气球 思路&#xff1a;看了眼题解&#xff0c;局部最优&#xff1a;当气球出现重叠&#xff0c;一起射&#xff0c;所用…

[数据集][目标检测]围栏破损检测数据集VOC+YOLO格式1196张1类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;1196 标注数量(xml文件个数)&#xff1a;1196 标注数量(txt文件个数)&#xff1a;1196 标注…

【操作系统期末速成】EP05 | 学习笔记(基于五道口一只鸭)

文章目录 一、前言&#x1f680;&#x1f680;&#x1f680;二、正文&#xff1a;☀️☀️☀️2.1 考点十一&#xff1a;死锁的概念与预防2.2 考点十二&#xff1a;死锁的避免一银行间算法2.1 考点十三&#xff1a;死锁的检测与解除 一、前言&#x1f680;&#x1f680;&#x…

【小沐学AI】Python实现语音识别(faster-whisper-webui)

文章目录 1、简介1.1 whisper1.2 faster-whisper 2、安装3、测试结语 1、简介 1.1 whisper https://github.com/openai/whisper Whisper 是一种通用语音识别模型。它是在各种音频的大型数据集上训练的&#xff0c;也是一个多任务模型&#xff0c;可以执行多语言语音识别、语音…

C语言 | Leetcode C语言题解之第205题同构字符串

题目&#xff1a; 题解&#xff1a; struct HashTable {char key;char val;UT_hash_handle hh; };bool isIsomorphic(char* s, char* t) {struct HashTable* s2t NULL;struct HashTable* t2s NULL;int len strlen(s);for (int i 0; i < len; i) {char x s[i], y t[i]…

51单片机第11步_在C语言中插入汇编语言

本章重点介绍如何在C语言中插入汇编语言。要不是有记录&#xff0c;真不知道怎么搞。 /* 你在 Project Workspace窗口中,将光标移到DELAY.c处,点下鼠标右键,选择"Options for file DELAY.c", 点击右边的"Generate Assembler SRC File"和“Assemble SRC …

【VMware】VMware 开启的虚拟机无法联网的解决方案

目录 &#x1f30a;1. 问题说明 &#x1f30a;2. 解决方案 &#x1f30d;2.1 查看虚拟网络编辑器 &#x1f30d;2.2 设置 vmnet &#x1f30d;2.3 设置虚拟机网络 &#x1f30d;2.4 Xshell连接虚拟机 &#x1f30a;1. 问题说明 虚拟机 ping 其他网页显示失败,比如&#…

Python逻辑控制语句 之 判断语句--石头剪刀布案例

需求&#xff1a; 1. 从控制台输入要出的拳 —— 石头&#xff08;1&#xff09;&#xff0f;剪刀&#xff08;2&#xff09;&#xff0f;布&#xff08;3&#xff09; 2. 电脑随机出拳 —— 先假定电脑只会出石头&#xff0c;完成整体代码功能 3. 比较胜负 胜负规则&#x…

【PL理论深化】(12) Ocaml 语言:高阶函数 | map 函数 | filter 函数 | fold 函数

&#x1f4ac; 写在前面&#xff1a;在函数式编程中&#xff0c;除了递归函数外&#xff0c;还经常使用高阶函数。高阶函数是指接收其他函数作为参数或返回另一个函数的函数。高阶函数通过抽象编程模式以实现重用&#xff0c;使程序可以在更高层次上进行编写。让我们重点看看常…

Webpack: 核心配置结构

概述 Webpack 是一种 「配置」 驱动的构建工具&#xff0c;所以站在应用的角度&#xff0c;必须深入学习 Webpack 的各项配置规则&#xff0c;才能灵活应对各种构建需求。本文将作为小册应用系列的一个总结&#xff0c;汇总与应用配置相关的各项知识点&#xff0c;包括&#x…

酒店客房管理系统(Java+MySQL)

技术栈 Java: 作为主要编程语言。Swing GUI: 用于开发图形用户界面。MySQL: 作为数据库管理系统。JDBC: 用于连接和操作MySQL数据库。 功能要点 管理登录认证 系统提供管理员登录认证功能。通过用户名和密码验证身份&#xff0c;确保只有授权的用户可以访问和管理酒店客房信…

数据结构复习指南

数据结构复习指南 本文中列举了数据结构期末考试可能存在的考点 绪论 数据的基本单位 数据元素是数据的基本单位 数据项 数据项是组成数据的、有独立含义的、不可分割的最小单位。 数据对象 数据对象是性质相同的数据元素的集合&#xff0c;是数据的一个子集。 数据结…