3.1 单例模式

饿汉式、懒汉式单例;DCL懒汉式。
单例是不安全的,会导致属性重复使用。

解决方案

1、不要在controller中定义成员变量。
2、万一必须要定义一个非静态成员变量时候,则通过注解@Scope(“prototype”),将其设置为多例模式。
3、在Controller中使用ThreadLocal变量

ThreadLocal

是 Thread 的局部变量,是线程私有的变量。
1)每个线程持有一个ThreadLocalMap成员变量,Thread.threadLocals。
2)在 ThreadLocalMap 中会有一个 Entry 类型的数组,名字叫 table,其键值对为:
键,当前的 ThreadLocal;值,实际需要存储的变量,比如用户对象或者 simpleDateFormat 对象等。
ThreadLocal 类接口很简单,只有四个方法:

  1. void set(Object value)设置当前线程的线程局部变量的值。
  2. public Object get()返回当前线程所对应的线程局部变量。
  3. public void remove()将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是 JDK5.0 新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。
  4. protected Object initialValue()返回该线程局部变量的初始值,该方法是一个 protected 的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第 1 次调用 get() 或 set(Object) 时才执行,并且仅执行 1 次。ThreadLocal 中的缺省实现直接返回一个 null。

会导致内存泄漏:对象不能被回收。
如何避免内存泄露
分析完这个问题之后,该如何解决呢?解决方法:调用 ThreadLocal 的 remove 方法。调用这个方法就可以删除对应的 value 对象,可以避免内存泄漏。

工厂模式:

简单工厂,静态工厂
把客户、工单抽象出来:生成对应的类,执行各自的方法逻辑。

简单工厂模式

  • 和名字一样简单,非常简单,直接上代码吧:

    1. public class FoodFactory {
    2. public static Food makeFood(String name) {
    3. if (name.equals("noodle")) {
    4. Food noodle = new LanZhouNoodle();
    5. noodle.addSpicy("more");
    6. return noodle;
    7. } else if (name.equals("chicken")) {
    8. Food chicken = new HuangMenChicken();
    9. chicken.addCondiment("potato");
    10. return chicken;
    11. } else {
    12. return null;
    13. }
    14. }
    15. }
  • 其中,LanZhouNoodle 和 HuangMenChicken 都继承自 Food。

  • 简单地说,简单工厂模式通常就是这样,一个工厂类 XxxFactory,里面有一个静态方法,根据我们不同的参数,返回不同的派生自同一个父类(或实现同一接口)的实例对象。

我们强调职责单一原则,一个类只提供一种功能,FoodFactory 的功能就是只要负责生产各种 Food。

工厂模式

  • 简单工厂模式很简单,如果它能满足我们的需要,我觉得就不要折腾了。之所以需要引入工厂模式,是因为我们往往需要使用两个或两个以上的工厂。

    1. public interface FoodFactory {
    2. Food makeFood(String name);
    3. }
    4. public class ChineseFoodFactory implements FoodFactory {
    5. @Override
    6. public Food makeFood(String name) {
    7. if (name.equals("A")) {
    8. return new ChineseFoodA();
    9. } else if (name.equals("B")) {
    10. return new ChineseFoodB();
    11. } else {
    12. return null;
    13. }
    14. }
    15. }
    16. public class AmericanFoodFactory implements FoodFactory {
    17. @Override
    18. public Food makeFood(String name) {
    19. if (name.equals("A")) {
    20. return new AmericanFoodA();
    21. } else if (name.equals("B")) {
    22. return new AmericanFoodB();
    23. } else {
    24. return null;
    25. }
    26. }
    27. }
  • 其中,ChineseFoodA、ChineseFoodB、AmericanFoodA、AmericanFoodB 都派生自 Food。

  • 客户端调用

    1. public class APP {
    2. public static void main(String[] args) {
    3. // 先选择一个具体的工厂
    4. FoodFactory factory = new ChineseFoodFactory();
    5. // 由第一步的工厂产生具体的对象,不同的工厂造出不一样的对象
    6. Food food = factory.makeFood("A");
    7. }
    8. }
  • 虽然都是调用 makeFood(“A”) 制作 A 类食物,但是,不同的工厂生产出来的完全不一样。

  • 第一步,我们需要选取合适的工厂,然后第二步基本上和简单工厂一样。
  • 核心在于,我们需要在第一步选好我们需要的工厂。比如,我们有 LogFactory 接口,实现类有 FileLogFactory 和 KafkaLogFactory,分别对应将日志写入文件和写入 Kafka 中,显然,我们客户端第一步就需要决定到底要实例化 FileLogFactory 还是 KafkaLogFactory,这将决定之后的所有的操作。
  • 虽然简单,不过我也把所有的构件都画到一张图上,这样看着比较清晰:

image.png

抽象工厂模式

  • 当涉及到产品族的时候,就需要引入抽象工厂模式了。
  • 一个经典的例子是造一台电脑。我们先不引入抽象工厂模式,看看怎么实现。
  • 因为电脑是由许多的构件组成的,我们将 CPU 和主板进行抽象,然后 CPU 由 CPUFactory 生产,主板由 MainBoardFactory 生产,然后,我们再将 CPU 和主板搭配起来组合在一起,如下图:

23种设计模式 - 图2

  • 这个时候的客户端调用是这样的: ```java //得到Intel的CPU CPUFactory cpuFactory = new IntelCPUFactory(); CPU cpu = intelCPUFactory.makeCPU();

//得到AMD的主板 MainBoardFactory mainBoardFactory = new AmdMainBoardFactory(); MainBoard mainBoard = mainBoardFactory.make();

//组装CPU和主板 Computer computer = new Computer(cpu, mainBoard);

  1. - 单独看 CPU 工厂和主板工厂,它们分别是前面我们说的**工厂模式**。这种方式也容易扩展,因为要给电脑加硬盘的话,只需要加一个 HardDiskFactory 和相应的实现即可,不需要修改现有的工厂。
  2. - 但是,这种方式有一个问题,那就是如果** Intel 家产的 CPU AMD 产的主板不能兼容使用**,那么这代码就容易出错,因为客户端并不知道它们不兼容,也就会错误地出现随意组合。
  3. -下面就是我们要说的**产品族**的概念,它代表了组成某个产品的一系列附件的集合:<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/22981711/1635398745685-d50593ef-e27a-4685-8cfe-d9203f1f5ced.png#clientId=u5396cd80-6a78-4&from=paste&height=478&id=ub267adbc&margin=%5Bobject%20Object%5D&name=image.png&originHeight=478&originWidth=1226&originalType=binary&ratio=1&size=179113&status=done&style=none&taskId=u733e4151-033c-41ac-8839-11a702b33f3&width=1226)
  4. - 当涉及到这种产品族的问题的时候,就需要抽象工厂模式来支持了。我们不再定义 CPU 工厂、主板工厂、硬盘工厂、显示屏工厂等等,我们直接定义电脑工厂,每个电脑工厂负责生产所有的设备,这样能保证肯定不存在兼容问题。
  5. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/22981711/1635398773911-facebb02-73a0-4d1e-ae7f-230c7fc2ea4c.png#clientId=u5396cd80-6a78-4&from=paste&height=693&id=u1ef6c54e&margin=%5Bobject%20Object%5D&name=image.png&originHeight=693&originWidth=1099&originalType=binary&ratio=1&size=334516&status=done&style=none&taskId=u62813fa4-af7a-4d24-9b73-b102c4a744d&width=1099)
  6. - 这个时候,对于客户端来说,不再需要单独挑选 CPU厂商、主板厂商、硬盘厂商等,直接选择一家品牌工厂,品牌工厂会负责生产所有的东西,而且能保证肯定是兼容可用的。
  7. ```java
  8. public static void main(String[] args) {
  9. // 第一步就要选定一个“大厂”
  10. ComputerFactory cf = new AmdFactory();
  11. // 从这个大厂造 CPU
  12. CPU cpu = cf.makeCPU();
  13. // 从这个大厂造主板
  14. MainBoard board = cf.makeMainBoard();
  15. // 从这个大厂造硬盘
  16. HardDisk hardDisk = cf.makeHardDisk();
  17. // 将同一个厂子出来的 CPU、主板、硬盘组装在一起
  18. Computer result = new Computer(cpu, board, hardDisk);
  19. }
  • 当然,抽象工厂的问题也是显而易见的,比如我们要加个显示器,就需要修改所有的工厂,给所有的工厂都加上制造显示器的方法。这有点违反了对修改关闭,对扩展开放这个设计原则。

    适配器模式:

    @Controller:默认单例模式,通过注解@Scope(“prototype”),将其设置为多例模式
    主要分为三类:类适配器模式、对象的适配器模式、接口的适配器模式。

    使用场景:

  1. 系统需要使用现有的类,而这些类的接口不符合系统的需要。
  2. 想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。
  3. 需要一个统一的输出接口,而输入端的类型不可预知。

    迭代器模式:

    Iterator循环遍历使用
    遍历List集合:
    1. list l = new ArrayList();
    2. l.add("aa");
    3. l.add("bb");
    4. l.add("cc");
    5. for (Iterator iter = l.iterator(); iter.hasNext();) {
    6. String str = (String)iter.next();
    7. System.out.println(str);
    8. }
    9. /*迭代器用于while循环
    10. Iterator iter = l.iterator();
    11. while(iter.hasNext()){
    12. String str = (String) iter.next();
    13. System.out.println(str);
    14. }
    15. */

    代理模式:

    将程序中的交叉业务逻辑(比如安全,日志,事务等),封装成一个切面,然后注入到目标对象(具体业务逻辑)中去,AOP可以对某个对象或某些对象的功能进行增强,比如对象中的方法进行增强,可以再执行某个方法之前额外做一些事情,在某个方法执行之后额外的做一些事情。

AOP(面向切面)是一种编程范式,提供从另一个角度来考虑程序结构以完善面向对象编程(OOP)。
AOP为开发者提供了一种描述横切关注点的机制,并能够自动将横切关注点织入到面向对象的软件系统中,从而实现了横切关注点的模块化。
AOP能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任,例如事务处理、日志管理、权限控制等,封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。
使用AOP的好处

  • 降低模块的耦合度
  • 使系统容易扩展
  • 提高代码复用性

实现AOP的主要设计模式就是动态代理。Spring的动态代理有两种:一是JDK的动态代理;另一个是cglib动态代理。
AOP使用代理模式实现。IOC主要设计模式是工厂模式。
AOP:目的是解耦,代码解耦。
IOC:创建bean服务都在这里,让IOC容器进行管理。

观察者模式:发布订阅

发短信,发邮件,发送到消息队列