常用的设计模式(单例模式、工厂模式等)

news/2024/5/4 2:11:07/文章来源:https://blog.csdn.net/weixin_71243923/article/details/130308963

1.单例模式

概述:    在有些系统中,为了节省内存资源、保证数据内容的一致性,对某些类要求只能创建一个实例,这就是所谓的单例模式. 例如,Windows 中只能打开一个任务管理器,这样可以避免因打开多个任务管理器窗口而造成内存资源的浪费,或出现各个窗口显示内容的不一致等错误。

单例模式有 3 个特点:
1. 单例类只有一个实例对象;
2. 该单例对象必须由单例类自行创建;
3. 单例类对外提供一个访问该单例的全局访问点;
单例模式的两种实现方式:
(1)饿汉式单例
该模式的特点就是类一旦加载就会创建一个实例,  并且只有一份,  而且不会存在任何线程安全问题。
以下是饿汉式单例的代码演示:
永远只有一个window实例
public class Window {//饿汉式单例,  类在初始化时就会加载,  就会初始化对象,只有一份//不会存在任何线程安全的问题private static Window window = new Window();private Window(){}public static Window getWindow(){return window;}public static void main(String[] args) {for (int i = 0; i < 10; i++) {new Thread(()->{System.out.println(Window.getWindow());}).start();}}
}

(2)懒汉式单例

       该模式在类加载时不创建对象, 只有在使用时才创建对象,   这时生成对象的数量需要我们来控制,  所以会存在线程安全问题。

以下是懒汉式单例的代码演示:

当有多个线程同时进入到第一个if中时,  第一个线程去创建对象, 后来获得锁的线程就不会再创建对象,  所以这里利用了两个if,  也就是双重检索+synchronized

public class Window {private static Window window;private Window(){}//懒汉式单例,在类加载的时候不创建对象,在使用时创建对象//这时,生成的对象的数量需要我们自己来控制//懒汉式单例会出现线程安全问题://    在多线程访问时,可能会出现多个线程同时进入到if,就会创建出多个对象//如何解决?//  1.给方法加锁,但是效率太低,一次只能有一个线程进入//  2.给代码块加锁,双重检索+synchronizedpublic static Window getWindow(){if(window==null){synchronized (Window.class){if(window==null){window=new Window();}}}return window;}
}

懒汉式单例双重检索+volatile

   在我们创建对象时,  编译后的汇编指令码正常的执行顺序如下:

1. new  (申请内存空间) 
2. dup
3. invokespecial  <init> :  //调用构造方法
4. astore_1  (将对象地址赋给引用变量)
5. return
线程 1 开始执行,先执行 new,在内存中申请内存空间

此时指令可能发生重排序,先把半成品对象引用地址赋给引用变量 t 

线程 1暂停执行,线程 2进入到 cpu执行,引用变量t 不为空,指向的是半成品对象.

 所以说如果在检索时用if(t  !=  null)来进行判断就会出现问题,  虽然不为空,  但是是一个半成品对象。

2.工厂模式(Factory Pattern)

(1)简单工厂模式

     简单工厂模式并不是 23 种设计模式之一,因为它并不符合开闭原则。主要目的是为了引出工厂方法模式,适合产品子类比较少的、创建操作比较简单的情况。

 由一个工厂类根据传入的参数(一般是字符串参数),动态决定应该创建哪一个产品子类的实例,并以父类形式返回。

以下是代码演示(汽车厂造汽车):

首先创建一个Car接口

public interface Car {void run();}

其次创建几个具体的汽车(奥迪、宝马)

  奥迪汽车:

public class Aodi implements Car{@Overridepublic void run() {System.out.println("奥迪汽车行驶");}
}

宝马汽车:

public class Bmw implements Car{@Overridepublic void run() {System.out.println("宝马汽车行驶");}
}

再创建一个造汽车的工厂

/*汽车工厂*/
public class CarFactory {public static Car createCar(String name){if(name.equals("aodi")){Aodi aodi = new Aodi();//aodi.return aodi;}if(name.equals("bmw")){return new Bmw();}if(name.equals("bc")){return new BC();}return null;}
}

最后创建一个Test类进行造汽车

public class Test {public static void main(String[] args) {Car bmw  = CarFactory.createCar("bmw");Car aodi  = CarFactory.createCar("aodi");bmw.run();aodi.run();}
}

根据我们传入的内容造相应的汽车,  所以很明显这是不满足开闭原则的,  每添加一个种类的汽车都需要更改原代码。

优点:
  • 客户端不负责对象的创建,而是由专门的工厂类完成;
  • 客户端只负责对象的调用,实现了创建和调用的分离,降低了客户端代码的难度;
缺点:
  • 如果增加和减少产品子类,需要修改简单工厂类,违背了开闭原则如果产品子类过多,会导致工厂类非常的庞大,违反了高内聚原则,不利于后期维护.
适用场景:
  • 所有的产品子类都有同一个父类(或接口),属于同一个产品系列产品子类比较少的、创建操作比较简单。

(2)工厂方法模式

       与简单工厂模式不同,工厂方法模式的对工厂也进行了抽象。有一个抽象的Factory 类(可以是抽象类和接口),这个类将不在负责具体的产品生产,而是只制定一些规范,将实际创建工作推迟到子类去完成。
 
       在这个模式中,工厂类和产品类往往可以——对应。即一个抽象工厂对应一个抽象产品,一个具体工厂对应一个具体产品,这个具体的工厂就负责生产对应的产品。

代码演示如下(还是同样的造汽车):

先创建一个Car接口

public interface Car {void run();}

其次创建一个工厂接口

public interface CarFactory {Car createCar();}

再创建两个具体的汽车(奥迪和宝马),分别都实现Car

奥迪:

public class Aodi implements Car {@Overridepublic void run() {System.out.println("奥迪汽车行驶");}
}

宝马:

public class Bmw implements Car {@Overridepublic void run() {System.out.println("宝马汽车行驶");}}

再创建具体的工厂(奥迪工厂和宝马工厂), 分别实现工厂接口(CarFactory)

奥迪工厂

public class AodiFactory implements  CarFactory{@Overridepublic Car createCar() {return new Aodi();}}

宝马工厂

public class BmwFactory implements  CarFactory{@Overridepublic Car createCar() {return new Bmw();}}

最后创建一个Test类进行测试(造车)

public class Test {public static void main(String[] args) {CarFactory aodicarFactory = new AodiFactory();Car aodi =  aodicarFactory.createCar();aodi.run();CarFactory bmwcarFactory = new BmwFactory();Car bmw = bmwcarFactory.createCar();bmw.run();CarFactory  bcf =  new BCFactroy();Car bc =   bcf.createCar();bc.run();}
}
优点:
  • 客户端不负责对象的创建,而是由专门的工厂类完成;
  • 客户端只负责对象的调用, 实现了创建和调用的分离,降低了客户端代码的难度;
  • 若增加和减少产品子类,不需修改工厂类,只增加产品子类和工厂子类,符合开闭原则即使产品子类过多, 不会导致工厂类的庞大,利于后期维护
适用场景:
所有的产品子类都有同一个父类(或接口),属于同一个产品系列产品子类比较多的、创建操作比较复杂。
(3)抽象工厂模式
 
     抽象工厂模式中,一个具体的工厂负责创建一系列相互关联的产品。会简化客户端的调一用。并且更换产品系列非常方便,更换一个工厂类即可。

 代码演示如下:

比如一个工厂可以造一类的所有产品,  这里我们创建车和手机

首先分别创建Car和Phone接口

public interface Car {void run();
}
public interface Phone {void  call();}

其次创建一个抽象工厂接口(可以造手机和汽车)

public interface AbstractFactory {Car getCar();Phone getPhone();}

咋子分别创建具体的工厂(奥迪工厂和宝马工厂)

奥迪工厂(造奥迪汽车和奥迪手机)

public class AodiFactory implements  AbstractFactory{@Overridepublic Car getCar() {return new AodiCar();}@Overridepublic Phone getPhone() {return new AodiPhone();}}

宝马工厂(造宝马汽车和宝马手机)

public class BmwFactory implements AbstractFactory{@Overridepublic Car getCar() {return new BmwCar();}@Overridepublic Phone getPhone() {return new BmwPhone();}
}
public class BmwFactory implements AbstractFactory{@Overridepublic Car getCar() {return new BmwCar();}@Overridepublic Phone getPhone() {return new BmwPhone();}
}

紧接着就是分别创建两个奥迪品牌的Car和Phone以及两个宝马品牌的Car和Phone, 这里就不创建了。

最后一个Test类进行测试

public class Test {public static void main(String[] args) {AbstractFactory aodiFactory = new AodiFactory();Car aodiCar = aodiFactory.getCar();Phone aodiphone = aodiFactory.getPhone();aodiCar.run();aodiphone.call();AbstractFactory bmwFactory = new BmwFactory();Car bmwCar = bmwFactory.getCar();Phone bmwPhone = bmwFactory.getPhone();bmwCar.run();bmwPhone.call();}
}

3.原型模式

        有时候,我们需要多个实例,但是创建这个实例的过程比较复杂,比如构造函数非常的复杂,执行这个构造函数时会消耗较长的时间,但另外一方面,这个构造函数中的一些信息又没有什么变化(也就是说创建第一个实例时初始化信息是这样的,创建第二个实例时初始化信息还是还是这样的),那么直接使用 new 再创建这样一个实例就显得太昂贵了,此时可以使用克隆,也就是复制,就是通过复制现在已经有了的实例来创建新的实例,提高创建速度。
例如:  spring中bean的作用域
  • singleton: 单例bean 一个类,创建一个对象,spring启动时,就创建单例对象 
  • prototype: 原型的,多例 每次获取时,都会创建一个新的对象

4.代理模式

       代理模式就是给一个对象提供一个代理,并由代理对象控制对原对象的引用。它使得客户不能直接与真正的目标对象通信。代理对象是目标对象的代表,其他需要与这个目标对象打交道的操作都是和这个代理对象在交涉。

比如在spring框架中的aop(面向切面编程):

在不修改代码的情况下,如果扩展一个新的功能(与业务没有直接联系),不修改代码可以实现, 底层实现就是通过一个代理对象来实现对扩展功能的调用。

代理模式的主要优点有:
  • 1. 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
  • 2. 代理对象可以扩展目标对象的功能;
  • 3. 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度;

代理模式的实现可以分为静态代理和动态代理

静态代理

       静态代理模式的特点,代理类接受一个 Subject 接口的对象,任何实现该接口的对象,都可以通过代理类进行代理,增加了通用性。

优点:可以做到在符合开闭原则的情况下对目标对象进行功能扩展。
缺点:一个代理类只能代理一个接口,工作量太大;代理类是运行前编码已经完成的;必须先有接口,再有代理;接口一旦发生变量,代理类也要修改。
动态代理
        在动态代理中我们不再需要再手动的创建代理类,我们只需要编写一个动态处理器就可以了。真正的代理对象在运行时为我们动态的来创建。
动态代理又分为jdk代理和Cglib代理
(1)jdk代理

要求目标类,必须实现一个接口,  在运行时,使用反射,动态获取接口信息,调用某个方法,获取到你要调用的方法,最终执行invoke()方法。将生成的代理对象,调用的方法,参数等信息传递过来,  这样就实现了代理对象,可以代理任何一个目标类,   但是要求所代理的目标类,必须要实现一个接口。

       虽然相对于静态代理,动态代理大大减少了我们的开发任务,同时减少了对业务接口的依赖,降低了耦合度。 但是还是有一点点小小的遗憾之处,那就是它始终无法摆脱仅支持 interface 代理的桎梏,因为它的设计注定了这个遗憾。
(2)Cglib代理
Cglib 子类代理实现方法:
1.需要引入 cglib 的 jar 文件,但是 Spring 的核心包中已经包括了 Cglib 功能,所以直接引入 spring-core-xxx.jar 即可.
2.引入功能包后,就可以在内存中动态构建子类
3.代理的类不能为 final,否则报错
4.目标对象的方法如果为 final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法.
CGLIB 创建的动态代理对象比JDK 创建的动态代理对象的性能更高,但是 CGLIB创建代理对象时所花费的时间却比 JDK 多得多。所以对于单例的对象,因为无需频繁创建对象,用 CGLIB 合适,反之使用 JDK 方式要更为合适一些。同时由于 CGLib 由于是采用动态创建子类的方法,对于 final 修饰的方法无法进行代理。

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

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

相关文章

战争教育策略游戏 MiracleGame,开启新阶段重塑生态和玩法

香港 Web3 区块链周刚刚在一片喧嚣中结束。各路大V、KOL 们的 report 都对 GameFi 的前景非常自信。2021-2023年期间&#xff0c;大量资金涌入 GameFi 赛道&#xff0c;GameFi 一旦爆发将会是现象级的出圈事件。 MiracleGame 是一款基于 BNB Chain 构建的英雄和元神主题的战争教…

HNCTF-re部分复现

目录 [HNCTF 2022 WEEK3]Help_Me! [HNCTF 2022 WEEK3]Whats 1n DLL? [HNCTF 2022 WEEK4]ez_maze 这几天在做HNCTF的week3&#xff0c;week4部分&#xff0c;学到了一些不知道的没接触过的东西&#xff0c;所以记录一下 [HNCTF 2022 WEEK3]Help_Me! 题目下载&#xff1a;下…

[自注意力神经网络]Mask Transfiner网络-论文解读

本文为CVPR2022的论文。国际惯例&#xff0c;先贴出原文和源码&#xff1a; 原论文地址https://arxiv.org/pdf/2111.13673.pdf源码地址https://github.com/SysCV/transfiner 一、概述 传统的Two-Stage网络&#xff0c;如Mask R-CNN虽然在实例分割上取得了较好的效果&#xff…

ARM busybox 的移植实战2

一、busybox 源码分析1 1、源码目录梳理 2、整个程序入口的确认 (1) 分析一个程序&#xff0c;不管多庞大还是小&#xff0c;最好的路线都是 按照程序运行时的逻辑顺序来。所以找到一个程序的入口至关重要。 (2) 学 C 语言的时候都知道&#xff0c;程序的主函数 main 函数就是…

【JUC高并发编程】—— 初见JUC

一、JUC 概述 什么是JUC JUC 是 Java并发编程的缩写&#xff0c;指的是 Java.util.concurrent 即Java工具集下的并发编程库 【说白了就是处理线程的工具包】 JUC提供了一套并发编程工具&#xff0c;这些工具是Java 5以后引入的&#xff0c;使得Java开发者可以更加方便地编写…

【系统集成项目管理工程师】项目干系人管理

&#x1f4a5;十大知识领域&#xff1a;项目干系人管理 项目干系人管理包括以下 4 个过程: 识别干系人规划干系人管理管理干系人参与控制干系人参与 一、识别干系人 输入工具与技术输出项目章程采购文件事业环境因素组织过程资产组织相关会议专家判断干系人分析干系人登记册 …

ansible自动运维——ansible使用临时命令通过模块来执行任务

大家好&#xff0c;这里是天亮之前ict&#xff0c;本人网络工程大三在读小学生&#xff0c;拥有锐捷的ie和红帽的ce认证。每天更新一个linux进阶的小知识&#xff0c;希望能提高自己的技术的同时&#xff0c;也可以帮助到大家 另外其它专栏请关注&#xff1a; 锐捷数通实验&…

为什么使用了索引,查询还是慢?

&#x1f3c6;今日学习目标&#xff1a; &#x1f340;为什么使用了索引&#xff0c;查询还是慢&#xff1f; ✅创作者&#xff1a;林在闪闪发光 ⏰预计时间&#xff1a;30分钟 &#x1f389;个人主页&#xff1a;林在闪闪发光的个人主页 &#x1f341;林在闪闪发光的个人社区&…

linux 安装 oracle 11g

linux 安装 oracle 11g 1、下载oracle 11g (11.2.0.1.0)1.1、Oracle Database 11.2.0.1.01.2、Oracle Database Grid Infrastructure 11.2.0.1.01.3、客户端 2、安装文档3、安装前准备3.1、建立用户和用户组3.2、sysctl3.3、security limits3.4、其他设置3.5、创建安装目录3.6、…

校招又临近了,怎么在面试中应对设计模式相关问题呢?

夏天开始了&#xff0c;那么夏天结束时的毕业季也不远了。毕业是个伤感、期待而又略带残酷的时节&#xff0c;就像蜜桃无论成熟与否都会在这个时间被采摘&#xff0c;如果毫无准备就踏入社会&#xff0c;就会……马上变成低级社畜。所以说还是要早点为了毕业找工作做点准备&…

Jetson nano B01学习笔记 -- 系统环境配置以及ROS安装

文章目录 一、Jetson nano 简介二、 系统环境配置1、系统镜像烧录2、CUDA环境配置 三、 ROS安装和环境配置总结 一、Jetson nano 简介 Jetson Nano是一款体积小巧、功能强大的人工智能嵌入式开发板&#xff0c;于2019年3月由英伟达推出。它预装Ubuntu 18.04LTS系统&#xff0c;…

LeafLet加载自定义Legend的设计与实现

背景 众所周知&#xff0c;在GIS的世界里&#xff0c;图例和地图永远是一对一起出现的对象。在地图上表示地理环境各要素&#xff0c;比如山脉、河流、城市、铁路等所用的符号叫做图例。这些符号所表示的意义&#xff0c;常注明在地图的边角上。图例是表达地图内容的基本形式和…

小六壬学习笔记

小六壬学习笔记 简介前置知识:十二地支和十二时辰适用范围起课&#xff1a;月令日时卦象 疑问&#xff1a;遇到闰月怎么办&#xff1f;禁忌数字起课法手机计算器取余数 简单解卦 简介 马前课&#xff0c;又名&#xff1a;小六壬。 小六壬历史渊源&#xff1a;https://m.sohu.c…

统信UOS 20 安装达梦数据库V8

统信UOS 20 安装达梦数据库V8 1、安装教程2、启动数据库实例服务失败解决方法3、使用dm管理工具连接数据库 1、安装教程 https://blog.csdn.net/OceanWaves1993/article/details/129936878 此教程进行到启动数据库实例步骤时 使用下面命令启动数据库实例服务时&#xff0c;报…

大数据技术之集群数据迁移

在大数据集群数据迁移的项目中涉及到很多技术细节&#xff0c;本博客记录了迁移的大致的操作步骤。 迁移借用Hadoop自带的插件&#xff1a;distcp。 一、Hadoop集群数据迁移 **DistCp&#xff08;分布式拷贝&#xff09;**是用于大规模集群内部和集群之间拷贝的工具。它使用M…

DHCP笔记

目录 DHCP动态主机配置协议——UDP67/68端口 DHCP获取IP地址 客户端首次获取IP地址 客户端再次获取IP地址 租期/续租 DHCP的工作报文 DHCP的配置 案例 DHCP动态主机配置协议——UDP67/68端口 DHCP是应用层协议&#xff0c;采用C/S服务模式&#xff0c;只能在一个广播域…

数据科学与机器学习在软件开发中的应用

数据科学和机器学习是现代软件开发的重要组成部分&#xff0c;可以帮助开发人员更好地理解和分析数据&#xff0c;从而提高软件的质量和性能。在本篇博客中&#xff0c;我将深入探讨数据科学和机器学习在软件开发中的应用&#xff0c;并讨论它们如何帮助我们创建更好的软件。 …

Xshell中的基本命令

whoami 当我们刚登录上Xshell的时候&#xff0c;我们应该做什么呢&#xff1f;&#xff1f; 我们上次说了如何增加使用者&#xff0c;和删除使用者&#xff0c;今天我们说一下其他的基本命令。 我们刚开始登录的时候可以用root登录 那么我们怎么看自己事谁呢&#xff1f; …

Android 一个获取网址时间的Demo

Android 一个获取网址时间的Demo 文章目录 Android 一个获取网址时间的Demo通过一个网址获取时间的代码关于Android NTP 时间Android 同步时间代码 前段时间有个客户想用局域网同步Android 设备的时间&#xff0c;开发后把这个demo分享一下。 效果&#xff1a; 这里也获取了阿…

VUE3子组件-业务代码优化

Vue3子组件 1.简介 Vue 3组件的主要优势之一就是它们可以帮助你将你的应用程序分解成可维护和可重用的部分。当你在应用程序中多次使用相同的代码时&#xff0c;你可以将它们抽象成一个组件&#xff0c;然后在应用程序中的多个地方使用该组件&#xff0c;而不必每次都编写相同…