厚积薄发打卡Day75 :【MSUP】Java语言特性与设计模式(上)

12 篇文章 0 订阅
11 篇文章 0 订阅
本文介绍了Java设计模式的重要性和常见应用,重点关注单例模式的实现,包括静态初始化(饿汉式)和双重检查(懒汉式),并探讨了Spring中的单例注册表。此外,还涵盖了工厂模式、代理模式、适配器模式和观察者模式的实际应用和示例。通过对设计模式的理解和实战,提升代码设计能力。
摘要由CSDN通过智能技术生成

前言

在看狂神频道的时候偶然发现下图,感触颇深。特别在当今【程序 = 业务 + 框架】思想盛行的开发者中,夯实基础基础显得格外重要,因此开此专栏总结记录。
在这里插入图片描述

设计模式详解

  • 设计模式的考察点,一般有2个:

    • 一个是常用设计模式的实现,
    • 另外一个是设计模式的使用场景。也就是每个设计模式用来解决什么样的问题,在什么场景下该使用什么样的设计模式。
  • 最常见的设计模式有单例模式、工厂模式、代理模式,构造者模式、责任链模式、适配器模式、观察者模式等。

  • 我们看一下详解设计模式的知识点分为三大类型,共23种:

    • 创建型的有5种:

      (抽 工 单 建 原)

      • 象工厂模式、厂方法模式、例模式、造者模式、型模式。
    • 结构型的有7种:

      (有个长得不错桥梁工程代理喜欢拿着组合转接头享受缘分

      • 适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
    • 行为型的有11种:

      访问者写好策略备忘录,观察模板迭代的状态,命令中介解释责任链。)

      解释:这句话讲的就是看房子的经过。

      看房子的人就是访问者,看房前要写看房策略备忘录,不能马马虎虎地去看房子。

      去看房子的时候,要仔细观察楼板(模板)层叠(迭代)的状态

      看完房子,命令中介解释清楚产权的责任链

      • 策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式。忘录模式状态模式、访问者模式、中介者模式和解释器模式。
  • 面试中对于设计模式,你应该明白不同设计用来解决什么样的场景问题。对于常用的设计模式能够灵活运用。

在这里插入图片描述

常用模式

常用的设计模式有:单例模式、工厂模式、代理模式、构造者模式、责任链模式、适配器模式、观察者模式等,

接下来逐个模式进行讲解

重点:单例模式

  • 学习博客:厚积薄发打卡Day28 :狂神说Java之彻底玩转单例设计模式

    我引用我自己哈哈

  • 首先是单例模式,这个模式在实际业务中经常会用到,也是设计模式中的主要考察点。这里我只介绍线程安全的单例模式的实现方式。

  • 静态初始化(饿汉式)

    • 实现的思路就是在类初始化时。成单例实例的创建,因此不会产生并发问题。这种方式下,不管是否会使用到这个单例,都会创建这个单例。

    • 创建步骤:

      1. 私有构造器
      2. 类的内部创建对象
      3. 向外暴露一个静态的公共方法
    • 上代码:

      public class HungrySingleton {
          //1.私有构造器
          private HungrySingleton() {
          }
      
          //2.类的内部创建对象
          private final static HungrySingleton HUNGRYSINGLE = new HungrySingleton();
      
          //3.向外暴露一个静态的公共方法
          public static HungrySingleton getInstance() {
              return HUNGRYSINGLE;
          }
      
          //测试
          public static void main(String[] args) {
              //单线程测试:
              HungrySingleton instance1 = HungrySingleton.getInstance();
              HungrySingleton instance2 = HungrySingleton.getInstance();
              System.out.println(instance1 == instance2); //true
              //多线程测试:
              new Thread(() -> {
                  HungrySingleton instanceA = HungrySingleton.getInstance();
                  System.out.println(instanceA);//HungrySingleton@626213bf
              }).start();
              new Thread(() -> {
                  HungrySingleton instanceB = HungrySingleton.getInstance();
                  System.out.println(instanceB);//HungrySingleton@626213bf
              }).start();
              //多线程测试非单例:
              new Thread(() -> {
                  NoneSingleton noneSingleton1 = new NoneSingleton();
                  System.out.println(noneSingleton1);//NoneSingleton@531ebd8d
              }).start();
              new Thread(() -> {
                  NoneSingleton noneSingleton2 = new NoneSingleton();
                  System.out.println(noneSingleton2);//NoneSingleton@6bd95d5d
              }).start();
          }
      }
      //非单例测试类
      class NoneSingleton {
      
      }
      
  • 双重检查(懒汉式)

    • 第二种实现方式是双重检查,也叫做懒汉方式。只有在真正用到这个单例实例的时候才会创建。如果没有使用,就不会创建这个方式,必然会面对多个线程,同时使用实例时的并发问题。

    • 为了解决并发访问问题,通过synchronize 的或者lock 进行双重检查,保证只有一个线程能够创建实例,这里需要注意内存可见性引起的并发问题,必须使用volatile关键字,修饰单例变量

    • 实现步骤:

      1. 私有化构造函数
      2. 创建对象容器(因为new并不是一个原子性操作,需要加锁实例化)
      3. 对外提供加锁的静态实例化方法
    • 上代码(DCL:Double Check Lock)

      public class LazySingletonWithDCL {
          //1. 私有化构造函数
          private LazySingletonWithDCL() {
              System.out.println(Thread.currentThread().getName() + " ->OK");
          }
      
          //2. 创建对象容器(因为new并不是一个原子性操作,需要加锁实例化)
          private volatile static LazySingletonWithDCL lazySingleton;
      
          //3. 对外提供加锁的静态实例化方法
          public static LazySingletonWithDCL getInstance() {
              synchronized (LazySingletonWithDCL.class) {
                  if (lazySingleton == null) {
                      lazySingleton = new LazySingletonWithDCL();
                  }
              }
              return lazySingleton;
          }
      
          //开启多条线程实例化LazySingletonWithDCL
          public static void main(String[] args) {
              for (int i = 0; i < 100; i++) {
                  //            new Thread(()->{LazySingletonWithDCL.getInstance();}).start();
                  new Thread(LazySingletonWithDCL::getInstance).start();
              }
          }
      }
      //-------------打印结果-------------------
      //Thread-0 ->OK
      
    • 看个反例(WithoutDcl)

      public class LazySingleton {
          //1.私有化构造函数
          private LazySingleton() {
              System.out.println(Thread.currentThread().getName()+" ->OK");
          }
      
          //2.创建对象(容器)
          private static LazySingleton lazyMan ;
      
          //3.对外提供静态实例化方法,判断在对象为空的时候创建
          public static LazySingleton getInstance(){
              //用的时候再加载
              if (lazyMan == null) {
                  lazyMan = new LazySingleton();
              }
              return lazyMan;
          }
          //开启多条线程实例化LazySingletonWithDCL
          public static void main(String[] args) {
              for (int i = 0; i < 100; i++) {
                  //            new Thread(()->{LazySingletonWithDCL.getInstance();}).start();
                  new Thread(LazySingleton::getInstance).start();
              }
          }
      }
      //--------------打印结果:--------------
      /*
      Thread-0 ->OK
      Thread-5 ->OK
      Thread-4 ->OK
      Thread-3 ->OK
      Thread-2 ->OK
      Thread-1 ->OK
      */
      
  • JDK例子:Runtime

    在这里插入图片描述

    public class Runtime {
        //2.类的内部创建对象
        private static Runtime currentRuntime = new Runtime();
    
        //3.开发对外的实例化方法
        public static Runtime getRuntime() {
            return currentRuntime;
        }
    
        //1.私有化构造方法
        private Runtime() {}
    
        ....
    }
    
  • 单例注册表

    • spring 中的bean 的单例模式就是通过单例注册表方式实现的

      单例模式 - 单例注册表与 Spring 实现单例剖析

      运用Map的key不能重复与的特性,实现单例。

    • 详见Spring源码:在这里插入图片描述

    • AbstractBeanFactory中doGetBean()

      // Create bean instance.
      if (mbd.isSingleton()) {
          sharedInstance = getSingleton(beanName, () -> {
              try {
                  return createBean(beanName, mbd, args);
              }
              catch (BeansException ex) {
                  // Explicitly remove instance from singleton cache: It might have been put there
                  // eagerly by the creation process, to allow for circular reference resolution.
                  // Also remove any beans that received a temporary reference to the bean.
                  destroySingleton(beanName);
                  throw ex;
              }
          });
          bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
      }
      
    • DefaultSingletonBeanRegistry中getSingleton() addSingleton()方法

    public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
        Assert.notNull(beanName, "Bean name must not be null");
        synchronized (this.singletonObjects) {
            Object singletonObject = this.singletonObjects.get(beanName);
            if (singletonObject == null) {
                //省略...
            }
            finally {
                if (recordSuppressedExceptions) {
                    this.suppressedExceptions = null;
                }
                afterSingletonCreation(beanName);
            }
            if (newSingleton) {
                addSingleton(beanName, singletonObject);
            }
        }
        return singletonObject;
    }
    
    
    /** Cache of singleton objects: bean name to bean instance. */
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
    
    protected void addSingleton(String beanName, Object singletonObject) {
        synchronized (this.singletonObjects) {
            this.singletonObjects.put(beanName, singletonObject);
            this.singletonFactories.remove(beanName);
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }
    

记得要将设计模式与实际业务场景进行结合来体现,对设计模式的理解和应用能力。

工厂模式

  • 学习博客:

    1. 厚积薄发打卡Day29 :[kuangStudy] GoF23通俗易懂的设计模式之 <工厂模式>
  • 这个是创建不同类型实例常用的方式,例如spring 中的各种bean是由不同的工厂类进行创建的

    厚积薄发打卡Day47: [itcast] GoF23设计模式之 <自定义Spring IOC> (上)

    BeanFactory解析

    Spring中Bean的创建是典型的工厂模式,通过简单工厂模式+配置文件的方式实现这一系列的Bean工厂,即IoC容器,为开发者管理对象之间的依赖关系提供了很多便利和基础服务,在Spring中有许多IoC容器的实现供用户选择,其相互关系如下图所示。

    在这里插入图片描述

    其中,BeanFactory作为最顶层的一个接口,定义了IoC容器的基本功能规范,观察其源码:

    public interface BeanFactory {
    
        String FACTORY_BEAN_PREFIX = "&";
        //根据bean的名称获取IOC容器中的的bean对象
        Object getBean(String name) throws BeansException;
        //根据bean的名称获取IOC容器中的的bean对象,并指定获取到的bean对象的类型,这样我们使用时就不需要进行类型强转了
        <T> T getBean(String name, Class<T> requiredType) throws BeansException;
        Object getBean(String name, Object... args) throws BeansException;
        <T> T getBean(Class<T> requiredType) throws BeansException;
        <T> T getBean(Class<T> requiredType, Object... args) throws BeansException;
    
        <T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);
        <T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType);
    
        //判断容器中是否包含指定名称的bean对象
        boolean containsBean(String name);
        //根据bean的名称判断是否是单例
        boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
        boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
        boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;
        boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException;
        @Nullable
        Class<?> getType(String name) throws NoSuchBeanDefinitionException;
        String[] getAliases(String name);
    }
    

    在BeanFactory里只对IoC容器的基本行为做了定义,根本不关心Bean是如何定义及怎样加载的,以bean的产生结果为导向,正如我们只关心能从工厂里得到什么产品,不关心工厂是怎么生产这些产品的,所以后续会有注册类来为 BeanFactory加载bean。

  • Calendar类:

    在这里插入图片描述

代理模式

  • 学习博客:厚积薄发打卡Day36 :[itcast] GoF23通俗易懂的设计模式之 <代理模式>

    从火车站卖票的例子对比说明了动态代理和静态代理

  • 主要用在不适合或者不能直接引用另一个对象的场景,可以通过代理模式对被代理的对象进行访问行为的控制,Java的代理模式分为静态代理和动态代理

    • 静态代理:是指在编译时就已经创建好了代理类

      • 例如我们在源代码中编写的类
    • 动态代理:只在JVM 运行过程中动态创建的代理类:使用动态代理的方法

      • 有JDK动态代理,CGLIB

      • 可以看到在newProxyInstance()方法中需要由Class<?>[] interfaces参数,也就是说必须要定义接口,才能对接口进行代理。

        假如没有接口的情况下,这时就应该使用CGLib来实现动态代理:

        CGLIB(Code Generation Library)详解

        CGLIB是一个功能强大,高性能的代码生成包。其被广泛应用于AOP框架(Spring、dynaop)中,用以提供方法拦截操作。Hibernate作为一个比较受欢迎的ORM框架,同样使用CGLIB来代理单端(多对一和一对一)关联(延迟提取集合使用的另一种机制)。CGLIB作为一个开源项目,其代码托管在github,地址为:https://github.com/cglib/cglib

  • 代理和装饰者的区别

    静态代理和装饰者模式的区别:

    • 相同点:
      • 都要实现与目标类相同的业务接口
      • 在两个类中都要声明目标对象
      • 都可以在不修改目标类的前提下增强目标方法
    • 不同点:
      • 目的不同
        • 装饰者是为了增强目标对象
        • 静态代理是为了保护和隐藏目标对象
      • 获取目标对象构建的地方不同
        • 装饰者是由外界传递进来,可以通过构造方法传递
        • 静态代理是在代理类内部创建,以此来隐藏目标对象
  • 面试时遇到这个问题,可以举个动态代理的例子:

    1. 比如dubbo中,是使用jdk的动态代理,通过反射把远程请求进行封装。使服务看上去就像在使用本地的方法。

    2. jdk动态代理在mybatis中的应用,主要通过 MapperProxyFactory实现,代理模式(以及动态代理在mybatis中的应用)

      如果使用过Mybatis,我们就会发现Mybatis的使用非常简单,首先定义一个dao接口,然后编写一个与dao接口的对应的配置文件,java对象与数据库字段的映射关系和dao接口对应的sql语句都是以配置的形式写在配置文件中,非常的简单清晰

      但是笔者在使用的过程中就曾经有过这样的疑问,dao接口是怎么和mapper文件映射起来的呢?只有一个dao接口又是怎么以对象的形式来实现数据库的读写操作呢?相信有疑问的肯定不止我一个人,当然,在看了上面两节之后,应该很容易猜到可以通过代理模式来动态的创建dao接口的代理对象,并通过这个代理对象来实现数据库的操作。

    3. Spring中的AOP实现

      Java动态代理——框架中的应用场景和基本原理

责任链模式

  • 责任链模式有点像工厂的流水线链上每一个节点,完成对对象的某一种处理
  • 例如Netty框架在处理消息时使用的pipeline,就是一种责任链模式

适配器模式

  • 学习博客:厚积薄发打卡Day33 :[kuangStudy] GoF23通俗易懂的设计模式之 <适配器模式>

  • 类似于我们常见的转接头,把两种不匹配的对象来进行适配,也可以起到对两个不同的对象进行解耦的作用

    • 例如我们常用的日志处理框架SLF4J,如果我们使用了SLF4J,就可以跟Log4J或者Logback,这种具体的日志实现框架进行解耦

    • 通过不同适配器将SLF4J与Log4J等实现框架进行适配,完成日志功能的使用

    • 适配器模式在Spring源码中的应用

      适配器模式在 SpringMVC 中的经典使用体现在它的核心方法 doDispatch 方法中,再来看一个 Spring MVC 中的 HandlerAdapter 类,它也有多个子类,类图如下。

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Etw8sTET-1627302709199)(厚积薄发打卡Day74 :【MSUP】Java语言特性与设计模式(上)/5-20092G24T2227.png)]

      设计模式|适配器模式使用案例、适配器模式在源码中的应用

      位于org.springframework.web.servlet包中的DispatcherServlet是servlet接口的实现类,作用是处理请求并返回结果。在servlet容器接收到一个请求时,servlet容器会针对这个请求创建一个servletRequest和servletRespones对象,相应的处理方法会通过servletRequest中携带的参数对应处理请求,再通过servletRespones对象商城请求的响应结果。

      DispatcherServlet类的doDispatch方法是处理请求的核心逻辑,截取部分内容如下:

       try {
           //对请求做类型转换
           processedRequest = checkMultipart(request);
           multipartRequestParsed = (processedRequest != request);
      
           // 根据请求信息找到相应的Handler
           mappedHandler = getHandler(processedRequest);
           if (mappedHandler == null) {
               noHandlerFound(processedRequest, response);
               return;
           }
      
           // 根据handleru想你找相应的HandlerAdapter
           HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
       }
      

观察者模式

  • 也被称作发布订阅模式,适用于一个对象的某个行为,需要触发一系列的事件场景

    • 例如GRPC 中stream 流式的请求的处理,就是通过观察者模式实现的。
    • 或者举个 redis 例子:Redis 发布订阅
  • 博客学习:厚积薄发打卡Day45: [itcast] GoF23通俗易懂的设计模式之 <观察者模式>

    1. JDK中的应用:

      在 Java 中,通过 java.util.Observable 类和 java.util.Observer 接口定义了观察者模式,只要实现它们的子类就可以编写观察者模式实例。

      1,Observable类

      在这里插入图片描述

      • 值得注意的时,void notifyObservers(Object arg) 方法:通常越晚加入集合的观察者越先得到通知:深入查看源码得知时倒序通知。

        在这里插入图片描述

      2,Observer 接口

      Observer 接口是抽象观察者,它监视目标对象的变化,当目标对象发生变化时,观察者得到通知,并调用 update 方法,进行相应的工作。

      public interface Observer {
          void update(Observable o, Object arg);
      }
      

      3,举例应用:

      【例】警察抓小偷

      警察抓小偷也可以使用观察者模式来实现,警察是观察者,小偷是被观察者。

      • 警察:

        public class Police implements Observer {
            private String name;
        
            public Police(String name) {
                this.name = name;
            }
            public void setName(String name) {
                this.name = name;
            }
        
            public String getName() {
                return name;
            }
        
            @Override
            public void update(Observable o, Object arg) {
                System.out.println("警察:" + ((Thief) o).getName() + ",我已经盯你很久了,你可以保持沉默,但你所说的将成为呈堂证供!");
            }
        }
        
      • 小偷:

        public class Thief extends Observable {
            //小偷名字
            private String name;
        
            public Thief(String name) {
                this.name = name;
            }
        
            public void setName(String name) {
                this.name = name;
            }
        
            public String getName() {
                return name;
            }
        
            public void steal() {
                System.out.println("小偷:渣渣们,老子来偷东西了!");
                super.setChanged(); //changed  = true
                super.notifyObservers();
            }
        }
        
      • 警察抓小偷:

        在这里插入图片描述

    2. Spring中的应用:

      1. Spring之事件监听(观察者模型)【波波烤鸭.著】

        1. Spring中事件监听的结构

          在这里插入图片描述

        2. 总结:

          1. Spring中的事件监听使用的是观察者模式
          2. 所有事件需要继承ApplicationEvent父类
        3. 所有的监听器需要实现ApplicationListener接口

          1. 事件发布需要通过ApplicationContext中的publisherEvent方法实现
          2. 监听器的注册是ApplicationEventMulticaster提供的,但我们并不需要实现。
        4. 扩展:

          1. 三种方式实现观察者模式 及 Spring中的事件编程模型
          2. Spring 应用之观察者设计模式
    3. Servlet中的Listener

      实例解析观察者模式及其在Java设计模式开发中的运用

      在说Servlet中的Listener之前,

      先说说观察者模式的另一种形态——事件驱动模型。与上面提到的观察者模式的主题角色一样, 事件驱动模型包括事件源, 具体事件, 监听器, 具体监听器。
      Servlet中的Listener就是典型的事件驱动模型。

      在这里插入图片描述

构造者模式

  • 适用于一个对象,有很多复杂的属性,需要根据不同情况创建不同的具体对象
  • 例如创建一个SpringBuilder对象时,可以使用builder 方式
  • 建造者模式-各框架应用场景穷举
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值