当前位置: 首页 > news >正文

弄懂软件设计模式(一):单例模式和策略模式

前言

        软件设计模式和设计原则是十分重要的,所有的开发框架和组件几乎都使用到了,比如在这小节中的单例模式就在SpringBean中被使用。在这篇文章中荔枝将会仔细梳理有关单例模式和策略模式的相关知识点,其中比较重要的是掌握单例模式的常规写法。希望对有需要的小伙伴有帮助~~~


文章目录

前言

一、单例模式singleton

1.1 饿汉式

1.2 懒汉式

1.3 懒汉式+悲观锁

1.4 双重检查锁

1.5 静态内部类写法

1.6 枚举单例 

二、策略模型Strategy

总结


一、单例模式singleton

        单例模式确保仅创建一个实例且避免在同一个项目中创建多个实例。其实就是在一次类加载中,只会对当前的类对象创建一次实例,我们不能通过new方法来实例化对象,而是只能调用类对象提供的getInstance方法获取已实例化的对象。

1.1 饿汉式

饿汉式相对来说是使用的比较多的一种单例模式的写法,在类中静态定义一个私有变量并在类加载的时候实例化该类对象。通过在getInstance方法中返回INSTANCE实例对象。

package com.mashibing.dp.singleton;public class Mgr01 {private static final Mgr01 INSTANCE = new Mgr01();private Mgr01() {};public static Mgr01 getInstance() {return INSTANCE;}public void m() {System.out.println("m");}public static void main(String[] args) {Mgr01 m1 = Mgr01.getInstance();Mgr01 m2 = Mgr01.getInstance();System.out.println(m1 == m2);}
}

饿汉式是立即加载的,除了预防反序列化的问题之外几乎没有缺点,而且它是线程安全的,操作简单。

1.2 懒汉式

懒汉式不会在加载类的时候就实例化对象,是懒加载的(按需加载),但是会出现线程安全的问题。 

比如这里两个线程同时打到INSTANCE上,就可能会有同时new出实例对象的风险,因此线程不安全。下面的示例demo中可以看到一个lambda表达式描述的方法。Lambda表达式是对线程Runnable接口匿名内部类的一种简写,这是因为我们这里在Runnable中只写一种内部方法

package com.mashibing.dp.singleton;public class Mgr03 {private static Mgr03 INSTANCE;private Mgr03() {}/*** 懒汉式*/public static Mgr03 getInstance() {if (INSTANCE == null) {try {Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}INSTANCE = new Mgr03();}return INSTANCE;}public void m() {System.out.println("m");}public static void main(String[] args) {for(int i=0; i<100; i++) {new Thread(()->System.out.println(Mgr03.getInstance().hashCode())).start();}}
}

 这里的new Thread()原本写法是

    new Thread(new Runnable() {@Overridepublic void run() {System.out.println(Mgr03.getInstance().hashCode())}}).start();

1.3 懒汉式+悲观锁

懒汉式加锁其实比较简单,直接使用synchronized关键字修饰加上悲观锁就可以了,操作比较简单,也比较完好地解决了线程安全问题,但这却是以牺牲效率为前提的,同时也并非序列化安全和反射安全的。

package com.mashibing.dp.singleton;public class Mgr04 {private static Mgr04 INSTANCE;private Mgr04() {}/*** 懒汉式+同步锁* @return*/public static synchronized Mgr04 getInstance() {if (INSTANCE == null) {try {Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}INSTANCE = new Mgr04();}return INSTANCE;}public void m() {System.out.println("m");}public static void main(String[] args) {for(int i=0; i<100; i++) {new Thread(()->{System.out.println(Mgr04.getInstance().hashCode());}).start();}}
}

1.4 双重检查锁

        前面我们为了解决懒汉式带来的线程安全问题加入了锁机制,但却带来了代码效率的下降。这里可以使用双重检查的机制来解决代码效率的问题,优化了代码性能,同时也保证了线程安全和懒加载的机制。但实现起来确实略显复杂,调试也比较困难。

package com.mashibing.dp.singleton;public class Mgr06 {//这里需要加上volatile的原因是因为Java中在编译中指令重排比较频繁,如果不加volatile会出现问题,private static volatile Mgr06 INSTANCE; //JITprivate Mgr06() {}/***双重检查单例写法* @return*/public static Mgr06 getInstance() {if (INSTANCE == null) {//双重检查synchronized (Mgr06.class) {if(INSTANCE == null) {try {Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}INSTANCE = new Mgr06();}}}return INSTANCE;}public void m() {System.out.println("m");}public static void main(String[] args) {for(int i=0; i<100; i++) {new Thread(()->{System.out.println(Mgr06.getInstance().hashCode());}).start();}}
}

这里需要注意的是在静态变量中INSTANCE需要加上volatile关键字修饰!

volatile关键子的作用

  • 确保INSTANCE变量的可见性,防止出现空指针异常的问题

        被volatile修饰的变量在线程访问时会被强制从主内存中读取变量的值而不从本地缓存中读取,保证共享变量的可见性和有序性,在对该变量进行修改后线程会强制将更新后的值刷回主内存,而不仅仅更新线程的本地缓存。出现空指针异常的问题可能是因为其它线程无立即获取修改后的未被volatile关键字修饰的变量值。

  • 防止指令重排

        指令重排是CPU为了提高程序执行效率而执行的操作,如果INSTANCE变量未被volatile修饰,那么可能无法保证线程安全。

1.5 静态内部类写法

         可以看到静态内部类的写法会在对象类中自定义一个私有的静态内部类,在其中实例化对象并赋值给一个静态常量。既实现了实例化对象的懒加载,同时也保证了线程安全。该类的缺点是对于传参的限制在某些场景下可能不太使用。

package com.mashibing.dp.singleton;public class Mgr07 {private Mgr07() {}private static class Mgr07Holder {private final static Mgr07 INSTANCE = new Mgr07();}/*** 静态内部类的写法* @return*/public static Mgr07 getInstance() {return Mgr07Holder.INSTANCE;}public void m() {System.out.println("m");}public static void main(String[] args) {for(int i=0; i<100; i++) {new Thread(()->{System.out.println(Mgr07.getInstance().hashCode());}).start();}}}

1.6 枚举单例 

枚举单例是最完美的单例模式,有效的解决了Java类的反序列化问题,实现了序列安全和反射安全。但枚举单例并不是懒加载的,也不能被继承。

package com.mashibing.dp.singleton;/*** 不仅可以解决线程同步,还可以防止反序列化。*/
public enum Mgr08 {INSTANCE;public void m() {}public static void main(String[] args) {for(int i=0; i<100; i++) {new Thread(()->{System.out.println(Mgr08.INSTANCE.hashCode());}).start();}}}

非枚举类的单例模式会出现反序列化的问题,这时因为我们可以利用Java的反射机制通过Java中的.class文件加载class对象

枚举单例不能够被反序列化的原因:枚举类没有构造方法。

这里有关双重检查锁参考了掘金大佬的文章,出处如下:

https://juejin.cn/post/7206529406612062268?searchId=20230905212839127143297911190A3F76#heading-16


二、策略模型Strategy

        策略模型比较简单,在日常开发中的使用也比较多,策略模型中一般封装的是实现一个方法的不同执行方式。策略模型将对象和行为分开,属于行为型模式。行为被分为了行为策略接口和实现行为的类。

main文件 

主程序调用比较类Sort,传入类对象和相应的比较器接口的实现即可。 

package com.mashibing.dp.strategy;import java.util.Arrays;/*** writing tests first!* extreme programming*/
public class Main {public static void main(String[] args) {Cat[] a = {new Cat(3, 3), new Cat(5, 5), new Cat(1, 1)};Sorter<Cat> sorter = new Sorter<>();
//        Dog[] b = {new Dog(3), new Dog(5), new Dog(1)};
//        Sorter<Dog> sorter = new Sorter<>();/*** 策略模式的选择,通过类加载的方式实现功能,代码的拓展性更强*/sorter.sort(a,new CatWeightComparator());System.out.println(Arrays.toString(a));sorter.sort(a,new CatHeightComparator());System.out.println(Arrays.toString(a));}
}

Sort类

自定义一个策略选择类,在其中调用已经被重写了的comparator接口中的compare方法实现对传入的比较类的策略模型的调用。 

package com.mashibing.dp.strategy;public class Sorter<T> {public void sort(T[] arr, Comparator<T> comparator) {for(int i=0; i<arr.length - 1; i++) {int minPos = i;for(int j=i+1; j<arr.length; j++) {minPos = comparator.compare(arr[j],arr[minPos])==-1 ? j : minPos;}swap(arr, i, minPos);}}}

策略实现类

策略接口需要实现Java.util中的Comparator接口并重写其中的compare方法实现对象类的策略逻辑封装。

package com.mashibing.dp.strategy;public class CatHeightComparator implements Comparator<Cat> {@Overridepublic int compare(Cat o1, Cat o2) {if(o1.height > o2.height) return -1;else if (o1.height < o2.height) return 1;else return 0;}
}

        其实最简单的策略模型的应用就是通过 if...else... 来判断执行策略,但是这种方式相比而言比较混乱,可拓展性不是很好,因此需要通过接口实现类和Java泛型来自定义一些策略以供选择。


总结

        上面的内容中荔枝主要梳理了单例模式和策略模式,这是二十三种软件设计模式中的两种,理解几种典型的单例模式的写法,接下来的文章中荔枝也会持续学习并整理输出,希望未来越来越好哈哈哈哈哈~~~

今朝已然成为过去,明日依然向往未来!我是小荔枝,在技术成长的路上与你相伴,码文不易,麻烦举起小爪爪点个赞吧哈哈哈~~~ 比心心♥~~~

相关文章:

弄懂软件设计模式(一):单例模式和策略模式

前言 软件设计模式和设计原则是十分重要的&#xff0c;所有的开发框架和组件几乎都使用到了&#xff0c;比如在这小节中的单例模式就在SpringBean中被使用。在这篇文章中荔枝将会仔细梳理有关单例模式和策略模式的相关知识点&#xff0c;其中比较重要的是掌握单例模式的常规写法…...

Redis----布隆过滤器

目录 背景 解决方案 什么是布隆过滤器 布隆过滤器的原理 一些其他运用 背景 比如我们在观看新闻或者刷微博的时候&#xff0c;会不停地给我们推荐新的内容&#xff0c;我们发现几乎没有重复的&#xff0c;说明后台已经进行了去重处理&#xff0c;基于如何去重&#xff0c…...

day 49 | 647. 回文子串 ● 516.最长回文子序列

647. 回文子串 dp含义&#xff1a;dp如果是表示i-j的序列中回文子串的个数的话&#xff0c;当新来一个后只能判定出来是整体的回文&#xff0c;内部的无法判断&#xff0c;所以用bool表示整体比较恰当。 递推公式&#xff1a;由于i&#xff0c;j是由i1,j-1决定的&#xff0c;所…...

【网络编程】C++实现网络通信服务器程序||计算机网络课设||Linux系统编程||TCP协议(附源码)

TCP网络服务器 &#x1f40d; 1.程序简洁&#x1f98e;2. 服务端ServerTcp程序介绍&#x1f996;3.线程池ThreadPool介绍&#x1f995; 4.任务类Task介绍&#x1f419;5. 客户端Client介绍&#x1f991;6.运行结果&#xff1a;&#x1f990; 7. 源码&#x1f99e;7.1 serverTcp…...

C语言类型占内存大小

C语言类型占内存大小 C语言数据类型sizeof测试基本数据类型所占字符大小运行结果数据模型 C语言数据类型 sizeof测试基本数据类型所占字符大小 #include <stdio.h>int main() {char a;short b;int c;long d;float e;double f;printf("char %d\n", sizeof (a…...

使用GPT-4生成训练数据微调GPT-3.5 RAG管道

OpenAI在2023年8月22日宣布&#xff0c;现在可以对GPT-3.5 Turbo进行微调了。也就是说&#xff0c;我们可以自定义自己的模型了。然后LlamaIndex就发布了0.8.7版本&#xff0c;集成了微调OpenAI gpt-3.5 turbo的功能 也就是说&#xff0c;我们现在可以使用GPT-4生成训练数据&a…...

RUST 每日一省:模式匹配

我们经常使用let 语句创建新的变量绑定——但是 let 的功能并不仅限于此。事实上&#xff0c; let 语句是一个模式匹配语句。它允许我们根据内部结构对值进行操作和判断&#xff0c;或者可以用于从代数数据类型中提取值。 let tuple (1_i32, false, 3f32); let (head, center…...

利用Jmeter做接口测试(功能测试)全流程分析

利用Jmeter做接口测试怎么做呢&#xff1f;过程真的是超级简单。 明白了原理以后&#xff0c;把零碎的知识点填充进去就可以了。所以在学习的过程中&#xff0c;不管学什么&#xff0c;我一直都强调的是要循序渐进&#xff0c;和明白原理和逻辑。这篇文章就来介绍一下如何利用…...

依赖导入失败场景和解决方案

在使用 Maven 构建项目时&#xff0c;可能会发生依赖项下载错误的情况&#xff0c;主要原因有以下几种&#xff1a; 下载依赖时出现网络故障或仓库服务器宕机等原因&#xff0c;导致无法连接至 Maven 仓库&#xff0c;从而无法下载依赖。 依赖项的版本号或配置文件中的版本号错…...

DiffBIR: Towards Blind Image Restoration with Generative Diffusion Prior

DiffBIR: 基于生成扩散先验的盲图像恢复 论文链接&#xff1a;https://arxiv.org/abs/2308.15070 项目链接&#xff1a;https://github.com/XPixelGroup/DiffBIR Abstract 我们提出了DiffBIR&#xff0c;它利用预训练的文本到图像扩散模型来解决盲图像恢复问题。我们的框架采…...

pycharm如何配置 .gitignore 文件

参考&#xff1a;https://zongweizhou1.github.io/2019/06/16/pycharm-gitignore/ .gitignore 文件本身不需要纳入版本控制&#xff0c;在 .gitignore 文件中写入“.gitignore"忽略即可...

【Spring面试题】AOP相关面试题:概念?使用场景?如何使用?核心?

什么是AOP AOP是面向切面&#xff0c;面向切面编程&#xff0c;是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。对多个对象共同行为封装成一个模块叫切面,然后某个方法为切点。 通俗的讲&#xff1a;就是在一些代码中做重复操作的时候&#xff0c;我们为了…...

Yolov5的tensorRT加速(python)

地址&#xff1a;https://github.com/wang-xinyu/tensorrtx/tree/master/yolov5 下载yolov5代码 方法一&#xff1a;使用torch2trt 安装torch2trt与tensorRT 参考博客&#xff1a;https://blog.csdn.net/dou3516/article/details/124538557 先从github拉取torch2trt源码 ht…...

设计模式(1) - UML类图

1、前言 最近在阅读 Android 源码&#xff0c;时常碰到代码中有一些巧妙的写法&#xff0c;简单的如 MediaPlayerService 中的 IFactory&#xff0c;我知道它是工厂模式&#xff0c;但是却不十分清楚它为什么这么用&#xff1b;复杂点的像 NuPlayer 中的 DeferredActions 机制…...

3D异常检测论文笔记 | Shape-Guided Dual-Memory Learning for 3D Anomaly Detection

文章目录 摘要一、介绍三、方法3.1. 形状引导专家学习3.2. Shape-Guided推理 摘要 我们提出了一个形状引导的专家学习框架来解决无监督的三维异常检测问题。我们的方法是建立在两个专门的专家模型的有效性和他们的协同从颜色和形状模态定位异常区域。第一个专家利用几何信息通…...

如何将枯燥的大数据进行可视化处理?

在数字时代&#xff0c;大数据已经成为商业、科学、政府和日常生活中不可或缺的一部分。然而&#xff0c;大数据本身往往是枯燥的、难以理解的数字和文字&#xff0c;如果没有有效的方式将其可视化&#xff0c;就会错失其中的宝贵信息。以下是一些方法&#xff0c;可以将枯燥的…...

linux bash中 test命令详解

test命令用于检查某个条件是否成立。它可以进行数值、字符和文件三方面的测试。 1、数值测试 -eq 等于-ne 不等于-gt 大于-ge 大于或等于-lt 小于-le 小于或等于 例如&#xff0c;我们可以测试两个变量是否相等&#xff1a; num1100 num2200 if test $num1 -eq $num2 thene…...

获取当前时间并转换为想要的格式

转换为YYYY-MM-DD格式 function getCurrentDate() {var today new Date();var year today.getFullYear();var month today.getMonth() 1; // 月份从0开始&#xff0c;需要加1var day today.getDate();return year - (month < 10 ? (0 month) : month) - (day &…...

如何实现自动化测试?

一、首先我们要清楚自动化测试的分类 以实现方式可分为UI自动化和接口自动化。UI自动化可用selenium等工具实现&#xff0c;接口自动化可用使用RobotFramework和Jmeter等工具实现&#xff0c;Jmeter也可做性能自动化&#xff0c;压力测试。 二、平时自动化测试怎么做 1. UI和…...

c++中的对齐问题

c中的对齐问题 需要对齐的原因 尽管内存是以字节为单位&#xff0c;但是大部分处理器并不是按字节块来存取内存的.它一般会以双字节,四字节,8字节,16字节甚至32字节为单位来存取内存&#xff0c;我们将上述这些存取单位称为内存存取粒度. 现在考虑4字节存取粒度的处理器取in…...

KubeSphere 容器平台高可用:环境搭建与可视化操作指南

Linux_k8s篇 欢迎来到Linux的世界&#xff0c;看笔记好好学多敲多打&#xff0c;每个人都是大神&#xff01; 题目&#xff1a;KubeSphere 容器平台高可用&#xff1a;环境搭建与可视化操作指南 版本号: 1.0,0 作者: 老王要学习 日期: 2025.06.05 适用环境: Ubuntu22 文档说…...

地震勘探——干扰波识别、井中地震时距曲线特点

目录 干扰波识别反射波地震勘探的干扰波 井中地震时距曲线特点 干扰波识别 有效波&#xff1a;可以用来解决所提出的地质任务的波&#xff1b;干扰波&#xff1a;所有妨碍辨认、追踪有效波的其他波。 地震勘探中&#xff0c;有效波和干扰波是相对的。例如&#xff0c;在反射波…...

测试markdown--肇兴

day1&#xff1a; 1、去程&#xff1a;7:04 --11:32高铁 高铁右转上售票大厅2楼&#xff0c;穿过候车厅下一楼&#xff0c;上大巴车 &#xffe5;10/人 **2、到达&#xff1a;**12点多到达寨子&#xff0c;买门票&#xff0c;美团/抖音&#xff1a;&#xffe5;78人 3、中饭&a…...

图表类系列各种样式PPT模版分享

图标图表系列PPT模版&#xff0c;柱状图PPT模版&#xff0c;线状图PPT模版&#xff0c;折线图PPT模版&#xff0c;饼状图PPT模版&#xff0c;雷达图PPT模版&#xff0c;树状图PPT模版 图表类系列各种样式PPT模版分享&#xff1a;图表系列PPT模板https://pan.quark.cn/s/20d40aa…...

C++ Visual Studio 2017厂商给的源码没有.sln文件 易兆微芯片下载工具加开机动画下载。

1.先用Visual Studio 2017打开Yichip YC31xx loader.vcxproj&#xff0c;再用Visual Studio 2022打开。再保侟就有.sln文件了。 易兆微芯片下载工具加开机动画下载 ExtraDownloadFile1Info.\logo.bin|0|0|10D2000|0 MFC应用兼容CMD 在BOOL CYichipYC31xxloaderDlg::OnIni…...

Mac下Android Studio扫描根目录卡死问题记录

环境信息 操作系统: macOS 15.5 (Apple M2芯片)Android Studio版本: Meerkat Feature Drop | 2024.3.2 Patch 1 (Build #AI-243.26053.27.2432.13536105, 2025年5月22日构建) 问题现象 在项目开发过程中&#xff0c;提示一个依赖外部头文件的cpp源文件需要同步&#xff0c;点…...

【电力电子】基于STM32F103C8T6单片机双极性SPWM逆变(硬件篇)

本项目是基于 STM32F103C8T6 微控制器的 SPWM(正弦脉宽调制)电源模块,能够生成可调频率和幅值的正弦波交流电源输出。该项目适用于逆变器、UPS电源、变频器等应用场景。 供电电源 输入电压采集 上图为本设计的电源电路,图中 D1 为二极管, 其目的是防止正负极电源反接, …...

【C++特殊工具与技术】优化内存分配(一):C++中的内存分配

目录 一、C 内存的基本概念​ 1.1 内存的物理与逻辑结构​ 1.2 C 程序的内存区域划分​ 二、栈内存分配​ 2.1 栈内存的特点​ 2.2 栈内存分配示例​ 三、堆内存分配​ 3.1 new和delete操作符​ 4.2 内存泄漏与悬空指针问题​ 4.3 new和delete的重载​ 四、智能指针…...

2025年渗透测试面试题总结-腾讯[实习]科恩实验室-安全工程师(题目+回答)

安全领域各种资源&#xff0c;学习文档&#xff0c;以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具&#xff0c;欢迎关注。 目录 腾讯[实习]科恩实验室-安全工程师 一、网络与协议 1. TCP三次握手 2. SYN扫描原理 3. HTTPS证书机制 二…...

Golang——6、指针和结构体

指针和结构体 1、指针1.1、指针地址和指针类型1.2、指针取值1.3、new和make 2、结构体2.1、type关键字的使用2.2、结构体的定义和初始化2.3、结构体方法和接收者2.4、给任意类型添加方法2.5、结构体的匿名字段2.6、嵌套结构体2.7、嵌套匿名结构体2.8、结构体的继承 3、结构体与…...