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

news2024/12/31 7:22:37

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.给代码块加锁,双重检索+synchronized
    public 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{

    @Override
    public void run() {
        System.out.println("奥迪汽车行驶");
    }
}

宝马汽车:

public class Bmw implements Car{

    @Override
    public 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 {

    @Override
    public void run() {
        System.out.println("奥迪汽车行驶");
    }
}

宝马:

public class Bmw implements Car {

    @Override
    public void run() {
        System.out.println("宝马汽车行驶");
    }

}

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

奥迪工厂

public class AodiFactory implements  CarFactory{

    @Override
    public Car createCar() {
        return new Aodi();
    }
    
}

宝马工厂

public class BmwFactory implements  CarFactory{

    @Override
    public 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{

    @Override
    public Car getCar() {
        return new AodiCar();
    }

    @Override
    public Phone getPhone() {
        return new AodiPhone();
    }

}

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

public class BmwFactory implements AbstractFactory{

    @Override
    public Car getCar() {
        return new BmwCar();
    }

    @Override
    public Phone getPhone() {
        return new BmwPhone();
    }
}
public class BmwFactory implements AbstractFactory{

    @Override
    public Car getCar() {
        return new BmwCar();
    }

    @Override
    public 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.coloradmin.cn/o/453662.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Centos切换jdk版本

先安装了jdk1.8的版本&#xff0c;需要使用jdk17的版本 1.先安装jdk17&#xff0c;再配置环境变量&#xff1a; vim ~/.bashrc 2.在最后一行添加 ##这个添加的就是路径&#xff0c;一定要和自己jdk安装的路径是一致的 export JAVA_HOME/usr/lib/jvm/java-8-openjdk-amd64 3.然…

Mybatis框架超详解及运用总结

Mybatis 一、什么是Mybatils&#xff1f;二、第一个Mybatils程序2.1、创建springboot工程2.2、准备数据2.3、配置MyBatis2.4、编写SQL语句2.5、单元测试 三、JDBC四、数据库连接池五、lombok六、Mybatis基础操作6.1、删除6.2、新增6.2.1、主键返回 6.3、修改6.4、查询6.4.1、数…

【AI绘画】AI绘画的创意应用

目录 1.引言2.将AI生成的图像转化为数字艺术品2.1AI生成的画作拍卖2.2将AI生成的图像转化为雕塑 3.将AI生成的图像用于虚拟场景的创建3.1使用GAN生成虚拟人物3.2在虚拟场景中使用AI生成的图像 1.引言 当今的AI绘画技术已经发展到了让人惊艳的程度&#xff0c;不仅可以生成高质量…

【每日一题Day183】LC1187使数组严格递增 | dp

使数组严格递增【LC1187】 给你两个整数数组 arr1 和 arr2&#xff0c;返回使 arr1 严格递增所需要的最小「操作」数&#xff08;可能为 0&#xff09;。 每一步「操作」中&#xff0c;你可以分别从 arr1 和 arr2 中各选出一个索引&#xff0c;分别为 i 和 j&#xff0c;0 <…

缓存优化----SpringCache

spring cache spring Cache介绍 spring cache是一个框架&#xff0c;实现了基于注解的缓存功能&#xff0c;只需要简单地加一个注解&#xff0c;就能实现缓存功能。 Spring cache提供了一层抽象&#xff0c;底层可以切换不同的cache实现。具体就是通过CacheManager接口来统一不…

springboot JWT 搭建授权服务器

目录 0 基本介绍 0.1 课程视频 0.2 架构逻辑图 0.2.1 登录JWT与授权服务器交互 0.2.2 登录成功后JWT与gateway-server交互 路由限制 1 JWT私钥公钥 1.1 安装git ->win系统右键 -> git bash here 1.2 生成私钥jks文件 1.3 用私钥jks文件解析出公钥 1.4 保存 BEGI…

造型简约的机箱,安装简单兼容性好,安钛克P20C体验

我们准备组装一台新主机的时候&#xff0c;机箱确实很重要&#xff0c;它决定了主机的整体风格和兼容性。我比较喜欢用中塔机箱&#xff0c;像是上个月我新装的主机&#xff0c;用的就是安钛克P20C&#xff0c;这款机箱的设计很简约&#xff0c;而且还有多种版本可选&#xff0…

C++——模板进阶

文章目录 &#x1f490;专栏导读&#x1f490;文章导读&#x1f337;类型模板参数&#x1f337;非类型模板参数&#x1f337;模板的特化&#x1f338;引例&#x1f338;函数模板的特化&#x1f338;类模板特化&#x1f33c;全特化 &#x1f338;偏特化&#x1f33c;部分特化&am…

【下载器篇】IDM下载器个性化设置

【下载器篇】IDM下载器个性化设置 IDM个性化设置—【蘇小沐】 文章目录 【下载器篇】IDM下载器个性化设置1.实验环境 &#xff08;一&#xff09;下载类型扩展UA默认值 &#xff08;二&#xff09;工具栏样式&#xff08;改风格&#xff09;3D Style样式 &#xff08;三&#…

2023.4.23 自注意力机制

一般都是单向量输入&#xff0c;但是如果多向量输入应该如何处理呢&#xff1f;引出自注意力机制 多向量输入可能会有多种输出&#xff0c;如果输入n个向量&#xff0c;输出n个向量表明这是sequence labeling&#xff0c;比如对于一个英文句子&#xff0c;每一个单词都判断是什…

c++11 标准模板(STL)(std::priority_queue)(二)

适配一个容器以提供优先级队列 std::priority_queue 定义于头文件 <queue> template< class T, class Container std::vector<T>, class Compare std::less<typename Container::value_type> > class priority_queue; priority_queu…

Windows下编译UHD

1.安装Visual Studio 2019,下载地址https://download.csdn.net/download/qq_36314864/87719209 2.安装cmake,下载地址https://download.csdn.net/download/qq_36314864/87719747 安装完成后记得C:\Program Files\cmake-3.22.1-windows-x86_64\bin添加到环境变量里面,或者安…

数据结构修炼:链表习题讲解!!!

题一&#xff1a;移除链表元素 我们可以看出这道题是让我们删除特定数据&#xff0c;我们可以用双指针来解这道题&#xff1a; 如果首元素为val&#xff0c;那么cur和head一起后移&#xff1a; 如果没有碰到val&#xff0c;那么就会cur后移&#xff0c;并且提前将cur传给perv&a…

如何避免美国ASP主机服务器崩溃和故障?

在当今数字化时代&#xff0c;网站是一个公司展示其业务的主要方式之一。因此&#xff0c;公司的在线业务应该始终保持高可用性和可靠性。ASP主机服务器是一种用于托管网站的服务器&#xff0c;其特点是可靠性高。但是&#xff0c;即使是最可靠的服务器也会遭受故障或崩溃。在本…

漏刻有时数据可视化大屏引导页设计(php原生开发、主背景图片更换、标题设置)

文章目录 1.引入外部js库2.HTML排版3.项目配置文件4.菜单图标自动匹配5.php与html混排6.CSS样式表7.添加/编辑信息8.生成配置文件 在制作数据可视化大屏时&#xff0c;尤其是在触摸屏演示时&#xff0c;需要开发和设计对应的数据大屏引导页。基于常见场景&#xff0c;单独开发数…

Unity API详解——Object类

Object类是Unity中所有对象的基类&#xff0c;例如GameObject、Component、Material、Shader、Texture、Mesh、Font等都是Object的子类。本博客介绍Object类的一些实例方法和静态方法。 一、Object类实例方法 在Object类中&#xff0c;涉及的实例方法主要有GetInstanceID方法…

Java基础学习(10)

Java基础学习 一、JDK8时间类1.1 Zoneld时区1.2 Instant时间戳1.3 ZonedDateTime1.4 DateTimeFormatter1.5 日历类时间表示1.6 工具类1.7 包装类JDK5提出的新特性Integer成员方法 二、集合进阶2.1 集合的体系结构2.1.1 Collection 2.2collection的遍历方式2.2.1 迭代器遍历2.2.…

RecycleView与TabLayout联动展示更多功能列表页面的实现

一.前言 对于更多功能页面&#xff0c;使用RecycleView与TabLayout联动方式实现是比较常见的&#xff0c;先上效果图&#xff08;请大佬们忽略gif的水印&#xff09; 单独使用TabLayout和RecycleView都是比较容易的&#xff0c;这里就不做举例了&#xff1b;gif中的列表实际上…

权限控制导入到项目中

在项目中应用 进行认证和授权需要前面课程中提到的权限模型涉及的7张表支撑&#xff0c;因为用户信息、权限信息、菜单信息、角色信息、关联信息等都保存在这7张表中&#xff0c;也就是这些表中的数据是进行认证和授权的依据。所以在真正进行认证和授权之前需要对这些数据进行…

( “树” 之 BST) 501. 二叉搜索树中的众数 ——【Leetcode每日一题】

二叉查找树&#xff08;BST&#xff09;&#xff1a;根节点大于等于左子树所有节点&#xff0c;小于等于右子树所有节点。 二叉查找树中序遍历有序。 ❓501. 二叉搜索树中的众数 难度&#xff1a;简单 给你一个含重复值的二叉搜索树&#xff08;BST&#xff09;的根节点 root…