前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >设计模式之单例和原型

设计模式之单例和原型

原创
作者头像
程序员田同学
发布于 2022-07-29 07:43:17
发布于 2022-07-29 07:43:17
21300
代码可运行
举报
文章被收录于专栏:java-springjava-spring
运行总次数:0
代码可运行

今天这篇文章我们来学习创建型设计模式的另外两个孪生兄弟,单例和原型,其中原型设计模式中我们深入到JVM的内存模型,最后顺便谈谈Java中的值传递和引用传递。

上篇文章老王买产品 我们从最原始的基本实现方法,到简单(静态)工厂,然后使用工厂方法设计模式进行改造,最后考虑产品会产生变体,我们又扩展到了抽象工厂。

设计模式所有的相关代码均已上传到码云 读者可以自行下载学习测试。

一、引出问题

今天老王又来了,还是想买我们的产品,今天老王上老就提出来一个要求,当他购买产品的时候,每次都要从货架上给他拿相同的一个。

如果用传统实现方式,当老王拿到产品以后,直接和上一个比对一下就行了,如果不一致老王就还回来。

但通过我们查阅软件的七大设计原则 ,这很明显违反了依赖倒置原则,为了避免耦合和让代码更易于维护,老王是不能依赖具体产品的。

二、单例

我们就需要将产品比对在创建产品的时候进行判断,老王就只管拿。

老王来之前应该还有两种情况,一种就是老王还没来,产品就准备好了,也即饿汉式。第二种就是老王什么时候来,什么时候给他准备产品,也即懒汉式。

我们看具体的实现代码:

懒汉式:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 懒汉式
 * @author tcy
 * @Date 29-07-2022
 */
public class LazySingletonProduct {private static volatile LazySingletonProduct instance=null;private LazySingletonProduct(){}public static synchronized LazySingletonProduct getInstance(){
        if (instance==null){
            instance=new LazySingletonProduct();}
        return instance;
    }

饿汉式:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 饿汉式
 * @author tcy
 * @Date 29-07-2022
 */
public class HungrySingletonProduct {
    private static volatile HungrySingletonProduct instance=new HungrySingletonProduct();private HungrySingletonProduct(){};public static synchronized HungrySingletonProduct getInstance(){
        if (instance==null){
            instance=new HungrySingletonProduct();
        }
        return instance;
    }
}

老王类:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * @author tcy
 * @Date 29-07-2022
 */
public class Client {
    public static void main(String[] args) {
        HungrySingletonProduct instance1 = HungrySingletonProduct.getInstance();
        HungrySingletonProduct instance2 = HungrySingletonProduct.getInstance();if (instance1==instance2){
            System.out.println("我俩一样...");
        }else {
            System.out.println("我俩不一样...");
        }
    }
}

以上就是单例设计模式中的懒汉式和饿汉式,应该是设计模式中最简单的一个,理解起来难度也不大。

为了克服老王和他儿子小王一起来拿错的尴尬,我们在方法上加synchronized锁,对象引用上加volatile共享变量,但这样会带来效率问题,如果不考虑多线程需求,读者可自行去掉。

三、原型

老王今天很明显是找茬,他继续说,如果我不想要一个了,我要每次买都要不同的,你看着办。

每次创建产品都要不同的,传统的方式肯定就是重新new一个对象,但每创建一个对象都是一个复杂的过程,而且这样还会带来一定的代码冗余。

这就需要用到创建型设计模式中的原型模式中的拷贝,其中又分为浅拷贝和深拷贝。

我们先看基本概念。

  • 浅克隆:创建一个新对象,对象种属性和原来对象的属性完全相同,对于非基本类型属性仍指向原有属性所指向的内存地址
  • 深克隆:创建一个新对象,属性中引用类型也会被克隆,不再指向原来属性所指向的内存地址

这段意思也就是,老王购买产品的时候,如果产品都是基本数据类型(byte(位)、short(短整数)、int(整数)、long(长整数)、float(单精度)、double(双精度)、char(字符)和boolean(布尔值))和String,那么我们就使用浅拷贝。

如果产品包括别的产品(对象)的引用类型就要使用深拷贝。

如果想搞明白,为什么造成深拷贝和浅拷贝这个问题,我们就要重点说说JVM的内存模型。

我们声明一个基本数据类型的变量a=2,实际上是在栈中直接存储了一个a=2,当拷贝的时候直接把值拷贝过去,也就是直接有了一份a的副本。

当我们创建一个对象时Student stu=new Student(),实际上对象的值存储在堆中,在栈中只存放了stu="对象地址",stu指向了堆中的地址,jvm拷贝的时候只复制了栈中的地址,实际上他们堆中的对象还是一个。

我们再来看String类型。String 存在于堆内存、常量池;这种比较特殊, 传递是引用地址;由本身的final性, 每次赋值都是一个新的引用地址,原对象的引用和副本的引用互不影响。因此String就和基本数据类型一样,表现出了"深拷贝"特性。

我们具体看实现代码:

浅拷贝类:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * @author tcy
 * @Date 29-07-2022
 */
public class ShallowProduct implements Cloneable{private String name;private int num;public void show(){
        System.out.println("这是浅产品..."+name+"数量:"+num);
    }public String getName() {
        return name;
    }public ShallowProduct setName(String name) {
        this.name = name;
        return this;
    }public int getNum() {
        return num;
    }public ShallowProduct setNum(int num) {
        this.num = num;
        return this;
    }
​
    @Override
    public ShallowProduct clone() throws CloneNotSupportedException {
        return (ShallowProduct) super.clone();
    }
}

如果需要哪个对象浅拷贝,需要该对象实现Cloneable接口,并重写clone()方法。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public void shallowTest()throws CloneNotSupportedException{
    ShallowProduct product1=new ShallowProduct();
    ShallowProduct product2 = product1.clone();
    product1.setName("老王");
    product2.setName("老李");
​
    product1.setNum(1);
    product2.setNum(2);
​
    product1.show();
​
    product2.show();
}

调用时输出的对象中的值直接就是两个不同的对象,实现了对象的浅拷贝。

如果该对象中包括引用类型呢?那怎么实现呢。

其实原理上也是很简单的,只需要将非基本数据类型也像浅拷贝那样操做就行了,然后在当前clone()方法中,调用非基本数据类型的clone()方法

深拷贝引用类:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * @author tcy
 * @Date 29-07-2022
 */
public class Child implements Cloneable{private String childName;public String getChildName() {
        return childName;
    }public Child setChildName(String childName) {
        this.childName = childName;
        return this;
    }
​
    @Override
    protected Child clone() throws CloneNotSupportedException {
        return (Child) super.clone();
    }
}

深拷贝类:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * @author tcy
 * @Date 29-07-2022
 */
public class DeepProduct implements Cloneable{private String name;private Integer num;private Child child;
    public String getName() {
        return name;
    }public DeepProduct setName(String name) {
        this.name = name;
        return this;
    }public Integer getNum() {
        return num;
    }public DeepProduct setNum(Integer num) {
        this.num = num;
        return this;
    }public void show(){
        System.out.println("这是深产品..."+name+"数量:"+num+"包括child:"+child.getChildName());
    }
​
​
    @Override
    public DeepProduct clone() throws CloneNotSupportedException {
        DeepProduct clone = (DeepProduct) super.clone();
        clone.child=child.clone();
        return clone;
    }public Child getChild() {
        return child;
    }public DeepProduct setChild(Child child) {
        this.child = child;
        return this;
    }
}

我们测试一下对象中的值是否发生了改变。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public void deepTest() throws CloneNotSupportedException {
    DeepProduct product1=new DeepProduct();
    Child child=new Child();
    child.setChildName("老王child");
​
    product1.setName("老王");
    product1.setNum(1);
    product1.setChild(child);//--------------
    DeepProduct product2=product1.clone();
    product2.setName("老李");
    product2.setNum(2);
    product2.getChild().setChildName("老李child");
​
    product1.show();
    product2.show();
}

老李、老王都正确的输出了,说明实现没有问题。

这样就符合了老王的要求。

既然说到了jvm的内存模型,就有必要说一下java中的值传递和引用传递。

实际上java应该就是值传递,在调用方法的时候,如果参数是基本数据类型,那么传递的就是副本,我们在方法中无论怎么给他赋值,他原本的值都不会有变化。

在调用方法的时候,如果参数是引用数据类型,那么传递的就是这个对象的地址,我们在方法中修改这个对象都会影响他原本的对象。

造成这个现象的原因其实是和浅拷贝、深拷贝的原理是一样的,都是栈、堆内存的结构导致的。

老王看他的要求都满足了,最后心满意足的拿着产品走了。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
Nuxt.js必读:轻松掌握运行时配置与 useRuntimeConfig
本文详细介绍了Nuxt.js中的运行时配置功能,包括定义和使用运行时配置的方法,以及如何通过useRuntimeConfig访问配置。同时,讲解了环境变量与.env文件的使用,特别是在不同环境下的配置管理。
8808.tw
2024/08/15
3700
Nuxt.js必读:轻松掌握运行时配置与 useRuntimeConfig
Nuxt3 实战 (十二):SEO 搜索引擎优化指南
4、/pages/ 目录中可以使用 definePageMeta 来根据当前路由设置元数据
白雾茫茫丶
2024/06/26
8490
Nuxt3 实战 (十二):SEO 搜索引擎优化指南
Taro——环境变量配置
最近准备开发移动端相关的内容,调研后选择了Taro,基于Taro+Vue3进行开发,在初始化框架后,又加入了一些前端规范限制,都完成后,打算配置环境变量,却发现按着官方的文档去配置,并没有生效;在封装的axios中去使用的使用,获取到的是undefined,所以这里做下记录; taro版本:3.6.34 模式和环境变量:https://docs.taro.zone/docs/env-mode-config/
思索
2024/08/15
2620
Vite弃坑指南之:环境文件系统
哈喽大家好,我是外卖仔,老久没静下心来卷文章了。随着Vite在前端工具链中比重越来越大,用的人也越来越多,打算出几期Vite的应用和机制研读,让小伙伴们用起来更得心应手。
南山种子外卖跑手
2022/09/02
7620
Vite弃坑指南之:环境文件系统
Taro——环境变量配置
最近准备开发移动端相关的内容,调研后选择了Taro,基于Taro+Vue3进行开发,在初始化框架后,又加入了一些前端规范限制,都完成后,打算配置环境变量,却发现按着官方的文档去配置,并没有生效;在封装的axios中去使用的使用,获取到的是undefined,所以这里做下记录;
思索
2024/08/14
3140
Vue之Axios跨域问题解决方案
背景:因为axios中只能使用get和post方法来进行请求数据,没有提供jsonp等方法进行跨域访问数据
全栈程序员站长
2022/08/14
1.7K1
Vue之Axios跨域问题解决方案
前后端分离,如何在前端项目中动态插入后端API基地址?(in docker)
开门见山,本文分享前后端分离,容器化前端项目时动态插入后端API基地址,这是一个很赞的实践,解决了前端项目容器化过程中受制后端调用的尴尬。
有态度的马甲
2020/06/04
1.4K0
WebPack高级进阶:
紧跟前文: WebPack5.0 快速入门 简单的了解了:WebPack的使用,接下来康康项目中的管理吧;
Java_慈祥
2024/08/01
2090
WebPack高级进阶:
vue前端跨域解决方案有哪些_前端能完全解决跨域问题吗
我们可以利用axios的baseUrl直接默认值是 api,这样我们每次访问的时候,自动补上这个api前缀,就不需要我们自己手工在每个接口上面写这个前缀了 在入口文件里面配置如下:
全栈程序员站长
2022/11/10
1K0
vue3+element-plus+router+vuex+axios从零开始搭建(2)
vue-cli 3.0x与vue-cli 2.0x最主要的区别是项目结构目录精简化,这也带来了许多问题,很多配置需要自己配置,
solate
2021/06/21
1.5K0
nuxt3目录结构详解
在 Nuxt.js 3 中,一个应用程序的文件夹结构具有一定的规范性。以下是 Nuxt.js 3 的文件夹结构及其用途的详细解释:
小小孩子们
2024/02/03
3.3K0
umijs环境变量问题
其打印出来的结果仍然是development,(大概是这个效果,这里是手动做的打印数据)
阿超
2023/07/21
1.6K2
umijs环境变量问题
vite基本配置教程
最近做项目要求将webpack打包方式换成vite,下面将详细讲解一下配置vite需要修改哪些文件,以及过程中踩到的奇葩坑。
程序媛夏天
2024/01/18
6650
vite基本配置教程
Vue 新增不参与打包的接口地址配置文件
vue工程项目,npm run build webpack方式打包,每次打包后如果需要更改后台接口地址(项目中,接口地址设置成变量,存放在js文件中,需要用到的地方导入),都需要重新打包,比较麻烦,所以,想给项目增加个配置文件,打包后如果要更改接口地址,修改该文件即可。
授客
2020/06/02
2.4K0
4-12 环境变量的使用
其实我么之前已经将webpack.config.js 按环境进行去了区分配置,那么在公共配置文件中我们能否知道当前所处的环境,并据此做逻辑区分呢?
love丁酥酥
2020/03/26
5610
让Node项目支持可扩展的环境配置
先来看看Vue CLI关于模式和环境变量的说明,我们看到有这么一段话:想要了解解析环境文件规则的细节,请参考 dotenv。我们也使用 dotenv-expand 来实现变量扩展 (Vue CLI 3.5+ 支持)。我们先用Vue Cli来创建一个Vue项目。
前端小鑫同学
2022/12/26
9540
让Node项目支持可扩展的环境配置
CROSS-ENV不同环境配置
项目背景 为了适应h5环境搭建需求,需要动态配置开发,测试,生产三种对应域名及其及打包命令。使用cross-env可以让配置环境更加清晰明了还好管理。 简介 cross-env的作用是不需要全局配置NODE_ENV在scripts脚本中修改NODE_ENV的值从而实现不同环境中proccess.env.NODE_ENV的不同,而config的工作原理就是基于NODE_ENV这个值的,所以推荐两者结合使用。 安装 cross-env
我不是费圆
2020/10/09
4.9K0
从零搭建一个django项目-13-多环境变量配置
每次启动serve或者buil需要配置不同的调后台地址,这里我们可以选择使用Vue中环境变量。 在项目根目录下新建或修改以下文件:.env.xxx development模式用于 vue-cli-service serve production模式用于 vue-cli-service build 这里新建的两个默认的文件分别对应调试和打包 也可以自定义一个 使用命令 npm run local执行读取local环境 新建一个config文件夹存放配置: 在index.js文件下: // 根据
怪盗LYL
2022/06/13
5350
从零搭建一个django项目-13-多环境变量配置
Vite多环境配置:让项目拥有更高定制化能力
近些年来,随着前端工程架构发展,使得前端项目中也能拥有如后端工程的模块能力。正所谓 “能力(越)越大(来),责任(越)越大(卷)”,现在的前端工程不仅仅要满足业务需求,还伴随更多复杂的环境适配问题,例如:
南山种子外卖跑手
2022/03/07
3.7K0
Vite多环境配置:让项目拥有更高定制化能力
如何用 GitHub Issues 搭建一个轻博客系统:Path Meme 项目实战
一直想随便写点什么东西,但是不想发朋友圈和微博,也没那么多的内容可以写一篇长文章,还想让内容有自主性,不担心随便说话被删,前几天正好试用了一下 Cursor,正好用它按自己的想法写一个可以随便说话的轻博客系统。 Path Meme —— 一个利用 GitHub Issues 作为 CMS 的现代化博客系统。今天,我和大家分享如何从零开始搭建这样一个博客系统。
goodspeed
2024/11/02
2180
相关推荐
Nuxt.js必读:轻松掌握运行时配置与 useRuntimeConfig
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档