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

前言

        软件设计模式和设计原则是十分重要的,所有的开发框架和组件几乎都使用到了,比如在这小节中的单例模式就在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泛型来自定义一些策略以供选择。


总结

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

朋友圈大佬都去读研了,这份备考书单我码住了

作者简介&#xff1a; 辭七七&#xff0c;目前大二&#xff0c;正在学习C/C&#xff0c;Java&#xff0c;Python等 作者主页&#xff1a; 七七的个人主页 文章收录专栏&#xff1a; 七七的闲谈 欢迎大家点赞 &#x1f44d; 收藏 ⭐ 加关注哦&#xff01;&#x1f496;&#x1f…

React如何实现国际化?

目录 一、Redux准备工作 commonTypes.js commonActions.js commonReducer.js rootReducer.js 二、然后定义SelectLang组件 index.js index.less 三、创建语言包 welcomeLocale.js index.js 四、使用 react的入口文件 App.js welcome.js 附 关于如何实现国际…

进程地址空间(Linux虚拟内存机制)

文章目录 一.Linux进程地址空间的结构二.Linux管理进程地址空间的方式三.Linux进程使用物理内存的模型四.进程地址空间的存在意义 本章理论基于32位平台的Linux–kernel 2.6.32版本内核 一.Linux进程地址空间的结构 为了保证内存安全,现代操作系统不允许应用程序(进程)直接访问…

Redis总结(二)

目录 Redis线程模型 Redis是单线程吗&#xff1f; Redis采用单线程为什么那么快&#xff1f; I/O多路复用模型 Redis持久化 Redis如何保证数据不丢失&#xff1f; AOF日志 AOF三种写回策略 AOF重写机制 触发机制 重写原理 RDB快照 执行快照时&#xff0c;数据能被…

实现 js 中所有对象的深拷贝(包装对象,Date 对象,正则对象)

通过递归可以简单实现对象的深拷贝&#xff0c;但是这种方法不管是 ES6 还是 ES5 实现&#xff0c;都有同样的缺陷&#xff0c;就是只能实现特定的 object 的深度复制&#xff08;比如数组和函数&#xff09;&#xff0c;不能实现包装对象 Number&#xff0c;String &#xff0…

180B参数的Falcon登顶Hugging Face,vs chatGPT 最好开源大模型使用体验

文章目录 使用地址使用体验test1:简单喜好类问题test2:知识性问题test3:开放性问题test4:中文支持test5:问题时效性test6:学术问题使用地址 https://huggingface.co/spaces/tiiuae/falcon-180b-demo 使用体验 相比Falcon-7b,Falcon-180b拥有1800亿的参数量

【Axure高保真原型】日历日期原型模板

今天和大家分享日历日期的原型模板&#xff0c;包括月计划、周计划、日计划的原型案例&#xff0c;以及日期、时间、月份、区间选择器……具体效果可以点击下方视频观看 【原型预览及下载地址】 Axure 原型 备用地址&#xff1a;Untitled Document 【原型效果】 【原型效果…

Unity技术手册-UGUI零基础详细教程-Canvas详解

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例点击跳转>软考全系列点击跳转>蓝桥系列 &#x1f449;关于作者 专注于Android/Unity和各种游…

【Unity】Unity坑的集锦之RenderTexture打包黑屏

问题&#xff1a;Camera Output Texture设置RenderTexture后&#xff0c;打包用来Save PNG&#xff0c;黑屏 如果你打AB 包&#xff0c;然后是相机的OutputTexture是拖拽的话&#xff0c;记得将包一起打入 或者你可以代码赋值 Camera.targetTexture await Loader.LoadAsset&l…

【算法系列 | 8】深入解析查找算法之—二分查找

序言 心若有阳光&#xff0c;你便会看见这个世界有那么多美好值得期待和向往。 决定开一个算法专栏&#xff0c;希望能帮助大家很好的了解算法。主要深入解析每个算法&#xff0c;从概念到示例。 我们一起努力&#xff0c;成为更好的自己&#xff01; 今天第8讲&#xff0c;讲一…

IO day7

1->x.mind 2-> A进程 B进程

[Google DeepMind] LARGE LANGUAGE MODELS AS OPTIMIZERS

Large Language Models as Optimizers 文章链接 总体架构Optimization by PROmpting (OPRO)&#xff1a;最开始输入meta-prompt&#xff0c;这个初始的meta-prompt基本上只是对优化任务进行了描述(也会有few-shot example)。输入后LLM便会生成一个solution&#xff0c;这个sol…

Vue3:proxy数据取值proxy[Target]取值

vue3底层是使用proxy进行代理的&#xff0c;而proxy中[[Target]]才是想要的值。 获取target值的方式一&#xff1a; <script setup>//先引入toRawimport { toRaw } from vue;//再使用console.log(toRaw(数据名))</script> 获取target值的方式二&#xff1a; <…

NIO基础

一、NIO基础 Java New IO是从Java1.4版本开始引入的一个新的IO api&#xff0c;可以替代以往的标准IO&#xff0c;NIO相比原来的IO有同样的作用和目的&#xff0c;但是使用的方式完全不一样&#xff0c;NIO是面向缓冲区的&#xff0c;基于通道的IO操作&#xff0c;这也让它比传…

Java学习之--类和对象

&#x1f495;粗缯大布裹生涯&#xff0c;腹有诗书气自华&#x1f495; 作者&#xff1a;Mylvzi 文章主要内容&#xff1a;Java学习之--类和对象 类和对象 类的实例化&#xff1a; 1.什么叫做类的实例化 利用类创建一个具体的对象就叫做类的实例化&#xff01; 当我们创建了…

Vulnhub系列靶机---HarryPotter-Fawkes-哈利波特系列靶机-3

文章目录 信息收集主机发现端口扫描dirsearch扫描gobuster扫描 漏洞利用缓冲区溢出edb-debugger工具msf-pattern工具 docker容器内提权tcpdump流量分析容器外- sudo漏洞提权 靶机文档&#xff1a;HarryPotter: Fawkes 下载地址&#xff1a;Download (Mirror) 难易程度&#xff…

工厂除静电除尘设备--离子风枪

静电无处不在&#xff0c;区别在于静电的多少而已。特别是工业生产过程中&#xff0c;大量的静电会有很多危害。 静电的危害有几点&#xff1a;1.引起电子设备的故障或误动作&#xff0c;造成电磁干扰。2.击穿集成电路和精密的电子元件&#xff0c;或使元件老化&#xff0c;拉…

关于一个left join的易错点

很多人在学习mysql的时候应该都出现过很多问题&#xff0c;特别是连接方面的问题应该最多&#xff0c;希望这篇文章帮助到正在找bug的你 Java报错数据返回数量出现错误 遇到这种问题一定要看日志 很明显通过left join查询除了两条数据并且为空 马上思考错误的原因&#xff0c;…

【Java基础篇 | 面向对象】--- 聊聊什么是多态(上篇)

个人主页&#xff1a;兜里有颗棉花糖 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 兜里有颗棉花糖 原创 收录于专栏【JavaSE_primary】 本专栏旨在分享学习JavaSE的一点学习心得&#xff0c;欢迎大家在评论区讨论&#x1f48c; 目录 一、什么是多态二、多…

数据结构基础7:二叉树【链式结构】实现和递归思想。

二叉树的链式结构实现 一.二叉树链式结构的实现&#xff1a;1.前置说明&#xff1a;1.创建二叉树&#xff1a;2.二叉树的结构&#xff1a; 2.二叉树的遍历&#xff1a;1.二叉树的前中后序遍历&#xff1a;2.内容拓展&#xff1a; 二.二叉树链式(题目)题目一&#xff1a;计算节点…