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

前言

        软件设计模式和设计原则是十分重要的,所有的开发框架和组件几乎都使用到了,比如在这小节中的单例模式就在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.hqwc.cn/news/100326.html

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

相关文章

YOLOv5:对yolov5n模型进一步剪枝压缩

YOLOv5&#xff1a;对yolov5n模型进一步剪枝压缩 前言前提条件相关介绍具体步骤修改yolov5n.yaml配置文件单通道数据&#xff08;黑白图片&#xff09;修改models/yolo.py文件修改train.py文件 剪枝后模型大小 参考 前言 由于本人水平有限&#xff0c;难免出现错漏&#xff0c;…

elk安装篇之 Kibana安装

Kibana是一个开源的分析与可视化平台&#xff0c;设计出来用于和Elasticsearch一起使用的。你可以用kibana搜索、查看存放在Elasticsearch中的数据。是es的可视化客户端之一。 一&#xff1a;下载 https://www.elastic.co/cn/kibana 我的es是elasticsearch-7.10.2版本&#x…

Postern配置HTTP和HTTPS的步骤

Postern是一款强大的Android代理工具&#xff0c;它允许您在设备上配置全局代理来实现安全、隐私保护和自由上网。本文将详细介绍如何使用Postern在Android设备上配置HTTP和HTTPS代理&#xff0c;为您提供更便捷的上网体验。 步骤1&#xff1a;下载和安装Postern应用 首先&am…

upload-labs1-21关文件上传通关手册

upload-labs文件上传漏洞靶场 目录 upload-labs文件上传漏洞靶场第一关pass-01&#xff1a;第二关Pass-02第三关pass-03&#xff1a;第四关pass-04&#xff1a;第五关pass-05&#xff1a;第六关pass-06&#xff1a;第七关Pass-07第八关Pass-08第九关Pass-09第十关Pass-10第十一…

Android高通 8.1 老化apk打开摄像头花屏问题

1、最近由于公司VR 3D系统要做双Camera老化测试apk&#xff0c;同时老化4小时需要轮询切换二个摄像头&#xff0c;保证后面camera标定精度数据更准确。 2、一开始我尝试用之前方案移植过去然后同时打开双摄像头 突然发现花屏 如下图所示 3、于是一第一时间想到是不是分辨率不兼…

【vue2第十五章】VueRouter 路由配置(VueRouter)与使用 和 router-link与router-view标签使用

单页面应用 与 多页面应用 单页面应用&#xff08;Single-Page Application&#xff0c;SPA&#xff09;和多页面应用&#xff08;Multi-Page Application&#xff0c;MPA&#xff09;是 Web 应用程序的两种不同架构方式。它们在页面加载和交互方式上有所区别。 单页面应用&a…

【Hive SQL 每日一题】统计用户连续下单的日期区间

文章目录 测试数据需求说明需求实现 测试数据 create table test(user_id string,order_date string);INSERT INTO test(user_id, order_date) VALUES(101, 2021-09-21),(101, 2021-09-22),(101, 2021-09-23),(101, 2021-09-27),(101, 2021-09-28),(101, 2021-09-29),(101, 20…

Ubutnu允许ssh连接使用root与密码登录

文章目录 1. 修改sshd_config2. 设置root密码3. 重启SSH服务 1. 修改sshd_config 修改/etc/ssh/sshd_config文件&#xff0c;找到 #Authentication&#xff0c;将 PermitRootLogin 参数修改为 yes。如果 PermitRootLogin 参数被注释&#xff0c;请去掉首行的注释符号&#xff…

激光焊接汽车尼龙塑料配件透光率测试仪

激光塑性成型技术是近年来塑性加工界出现的一种新技术。通常塑料主要是通过加热加压依赖模具成型。这对于单品种、大批量生产是有效的&#xff1b;而对于各种不同形状的塑料制件则需要昂贵的模具‚装置也较庞大。 高度聚焦的激光束垂直照射在待变形的板料上‚由于塑料直接吸收激…

【MySQL】事务 详解

事务 详解 一. 为什么使用事务二. 事务的概念三. 使用四. 事务的特性原子性&#xff08;Atomicity&#xff09;一致性&#xff08;Consistency&#xff09;隔离性&#xff08;Isolation&#xff09;持久性&#xff08;Durability&#xff09; 五. 事务并发所带来的问题脏读问题…

MT4移动端应用指南:随时随地进行交易

如今&#xff0c;随着科技的不断发展&#xff0c;我们可以随时随地通过手机进行各种操作&#xff0c;包括进行金融交易。本文将为大家介绍一款优秀的金融交易软件——MT4&#xff08;可在mtw.so/6gwPno这点下&#xff09;移动端应用&#xff0c;并提供详细的使用指南&#xff0…

ubuntu 20.04 设置 authorized_keys 让 VS Code ssh 远程免密连接

相关文章 VSCode SSH 连接远程ubuntu Linux 主机 前言 前面记录了 VS Code 可以通过 SSH 远程连接 ubuntu Linux 主机&#xff0c;比如代码放在远程 ubuntu 主机上&#xff0c; windows 端 VS Code 通过 ssh 远程连接 ubuntu&#xff0c;并打开 远程主机上的 代码 如果不设置…