目录

常用设计模式总结

单例模式

单例模式有两种实现方式:

  • 饿汉式单例

    类一加载就创建单例对象,如果对象比较多比较大并且在运行中始终没有用到那么就白白的消耗内存了

    饿汉模式可以通过如下几个方法来实现:

    1. 直接赋予属性值
    2. 静态代码块
    3. Enum枚举(Java推荐方式)
  • 懒汉式单例

    懒汉和饿汉相反,只有当对象用到的时候才创建,但是这种模式需要考虑一个并发问题,如果处理的不恰当的话就会破坏单例而创建出两个对象

    实现饿汉的方式主要有下面几个:

    1. 静态内部类(Java语法特性,只有用到静态内部类的时候静态内部类才会被加载)
    2. 方法级加上同步锁
    3. 双重检查,控制锁的粒度更小

上面的单例都会被 反射 给破坏,所以Java推荐以Enum方式创建单例来防止反射破坏单例,Java在编译层面防止用反射创建Enum对象

  • 容器注册式单例

    还有一种 容器注册式单例 ,在Spring中会有一个IOC容器,如果没有显示指定对象需要多个,那么Spring都只会创建一个单例对象并且注册进IOC容器,需要的时候直接去这个容器中获取,如果容器中没有则进行创建并且保存到容器中,如果有则直接从容器中取出来返回,这个容器可以简单的看成是一个ConcurrentHashMap

    容器注册式单例和对象式单例的区别?

下面列出一些具体的应用:

  • Runtime类使用的就是饿汉式单例,这是一个JVM运行时类,记录了JVM运行时的一些信息比如JVM可用的堆内存,可用的CPU个数等

  • Spring中会解析XML配置文件然后通过BeanFactory创建单例的Bean对象同时注册到IOC容器中供程序进行DI注入

  • 我们在使用JDBC连接数据库的时候创建的JDBC连接对象是单例的,因为我们连接对象仅仅只是维护了一些连接数据库的参数,查询的时候会根据这些参数创建TCP连接,所以这样的连接上下文对象也只需要一个即可

工厂模式

工厂模式有三种,三种模式都是逐步演变过来的?

  • 简单工厂

    所有对象的创建都在一个工厂类里,工厂类职责过于复杂

  • 工厂方法

    定义一个工厂接口,每个类都定义一个创建他本身的工厂类,缺点就是工厂类随着类的增加会逐渐增加

  • 抽象工厂

    在工厂方法上面做的一个改进,不为每个类都创建工厂类了,而是只为同一类对象只创建一个工厂类,抽象工厂所关注的是如何创建一系列的类

    比如美的和海尔各自有自己的工厂类,美的工厂只负责创建美的空调、美的冰箱、美的电磁炉等,而海尔工厂只负责创建海尔冰箱、海尔空调、海尔电磁炉等

依赖注入Dependency Injection其实就是用工厂模式来创建对象的

建造者模式

建造者模式和工厂模式的区别在于,建造者模式关注的是创建一个对象的过程,而工厂模式关注的是创建哪个对象

建造者模式相当于对一个类的创建过程的一个封装,将类的创建过程抽象为一个建造者类,这样类本身的构造和实现就分离开来了,我们可以很清晰的通过这个类的建造者类知道这个类是如何被创建的,并且很清晰的看见这个类本身的各个实现代码,而不是看见一坨混乱的代码

在构造者类里面我们还可以做一些参数校验的工作来保证类的正确安全的创建

  • StringBuilder就是建造者模式,里面有一个字节数组,每次append的时候都是往这个数组里面加,然后toString的时候就将这个字节数组转化为字符串

    另外此类是并发不安全的,StringBuffer是并发安全的,所以在非并发场景下选择StringBuilder速度快没有锁的消耗,在并发场景下则选择StringBuffer保证并发安全

原型模式

就是 拷贝模式,是对原来类的一个拷贝,如果一个类的创建比较耗时间并且复杂,那么可以使用原型模式减少创建类的过程和消耗

代理模式

代理模式强调的是代替一个类去做一件事,强调的是控制访问,这件事可能需要很多前置准备或则后置收尾工作,这样的话类本身只需要关注自己的事情即可,其他多余的事情多余的前期准备工作都由代理类去完成,代理类会为你准备好一切供你使用

在Java代码中的体现就是 代理类持有一个被代理的对象,是一种组合的思想

Java的代理模式里面还需要了解一个JVM的动态代理,动态代理就是在JVM运行的时候通过反射来创建代理类,这样就不需要自己手动创建代理类了,因为代理类往往有很多模板代码

装饰器模式

装饰模式和代理模式的区别在于,装饰模式强调的是 功能增强,装饰模式可以在不改变原来代码的情况下增加一些功能,这样就不会破坏原来的代码还可以增加功能,这样就解耦合了

在Java代码中的体现就是 装饰类持有一个目标对象,同时装饰类也实现了目标对象的接口,在接口方法里面调用目标对象的方法的同时进行增强 ,这就是组合的典型应用,而不是使用继承

  • Java中的IO就有装饰器模式,BufferedInputStream里面包含一个InputStream,进行读取的时候先调用InputStreamread方法预读取一部分数据到自己的缓存区里,用户调用BufferedInputStream读取的时候就是从缓存区里读取数据,这样就加快了读取的速度,并且不用每次都调用InputerStream读取数据了,因为直接的InputStream的read往往会进行系统调用

适配器模式

适配器模式就是对老的接口做一个适配,这样就不需要改原来的代码即可和现有的系统接入,比如手机充电器必须将220v的电适配一下转化为手机电池受得了的电压

门面模式

门面模式强调的是整合多个接口为一个同一简单的接口,就是提供一个公共简洁的接口将多个复杂的接口合并为一个简单易用的接口供外部调用,主要的目的有下面几个:

  1. 封装内部细节,提供统一的接口
  2. 将多个接口调用合并为一个,比如可以减少网络传输的消耗,前端不需要多次请求,只需要请求一个公共合并的接口进行一次网络通讯即可获取所有信息
  3. 保证事务的原子型,将多个必须共同完成的接口放入到一个统一的接口中进行调用这样就保证了原子型
  • 其实我们在SpringBoot后端程序的时候就是门面模式,每个Dao接口只负责查询自己负责的数据,Service可能需要多个数据,于是就会调用多个Dao接口来查询数据然后返回给Controller,同理Controller也可能需要调用多个Service接口,而前端只需要请求一个Controller接口即可获取数据

享元模式

享元模式就是一个 线程池、对象池 模型,有了这些缓存池,我们就不需要频繁的创建线程或则对象了,直接从池子里面获取即可,这就是享元模式

  • Java字符串的常量池,Java会维护一个字符串常量池,第一次创建时会被加入到该池里面,以后再遇到一模一样的字符串就直接引用常量池里面的常量即可

    Java会维护8种类型的常量池,比如int常量池中初始化-128~127的范围

public static void main(String[] args) {
    String s1 = "hello,world";
    String s2 = "hello,world";
    System.out.println(s1 == s2); //true

    //s3 会创建两份 一份放入常量池一份在堆空间 intern返回的是常量池中的引用
    String s3 = new String("hello,world");
    System.out.println(s1==s3); //false
    System.out.println(s3==s3.intern()); //false 
    System.out.println(s1==s3.intern()); //true
    
    
    Integer i1 = 10, i2 = 10, i3 = 8888, i4 = 8888;
    System.out.println(i1==i2); //true
    System.out.println(i3==i4); //false
    System.out.println(i3.intValue()==i4.intValue()); //true
  }

TODO组合模式

TODO