3.1 单例模式
饿汉式、懒汉式单例;DCL懒汉式。
单例是不安全的,会导致属性重复使用。
解决方案
1、不要在controller中定义成员变量。
2、万一必须要定义一个非静态成员变量时候,则通过注解@Scope(“prototype”),将其设置为多例模式。
3、在Controller中使用ThreadLocal变量
ThreadLocal
是 Thread 的局部变量,是线程私有的变量。
1)每个线程持有一个ThreadLocalMap成员变量,Thread.threadLocals。
2)在 ThreadLocalMap 中会有一个 Entry 类型的数组,名字叫 table,其键值对为:
键,当前的 ThreadLocal;值
ThreadLocal 类接口很简单,只有四个方法:
- void set(Object value)设置当前线程的线程局部变量的值。
- public Object get()返回当前线程所对应的线程局部变量。
- public void remove()将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是 JDK5.0 新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。
- protected Object initialValue()返回该线程局部变量的初始值,该方法是一个 protected 的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第 1 次调用 get() 或 set(Object) 时才执行,并且仅执行 1 次。ThreadLocal 中的缺省实现直接返回一个 null。
会导致内存泄漏:对象不能被回收。
如何避免内存泄露
分析完这个问题之后,该如何解决呢?解决方法:调用 ThreadLocal 的 remove 方法。调用这个方法就可以删除对应的 value 对象,可以避免内存泄漏。
工厂模式:
简单工厂,静态工厂
把客户、工单抽象出来:生成对应的类,执行各自的方法逻辑。
简单工厂模式
和名字一样简单,非常简单,直接上代码吧:
public class FoodFactory {
public static Food makeFood(String name) {
if (name.equals("noodle")) {
Food noodle = new LanZhouNoodle();
noodle.addSpicy("more");
return noodle;
} else if (name.equals("chicken")) {
Food chicken = new HuangMenChicken();
chicken.addCondiment("potato");
return chicken;
} else {
return null;
}
}
}
其中,LanZhouNoodle 和 HuangMenChicken 都继承自 Food。
- 简单地说,简单工厂模式通常就是这样,一个工厂类 XxxFactory,里面有一个静态方法,根据我们不同的参数,返回不同的派生自同一个父类(或实现同一接口)的实例对象。
我们强调职责单一原则,一个类只提供一种功能,FoodFactory 的功能就是只要负责生产各种 Food。
工厂模式
简单工厂模式很简单,如果它能满足我们的需要,我觉得就不要折腾了。之所以需要引入工厂模式,是因为我们往往需要使用两个或两个以上的工厂。
public interface FoodFactory {
Food makeFood(String name);
}
public class ChineseFoodFactory implements FoodFactory {
@Override
public Food makeFood(String name) {
if (name.equals("A")) {
return new ChineseFoodA();
} else if (name.equals("B")) {
return new ChineseFoodB();
} else {
return null;
}
}
}
public class AmericanFoodFactory implements FoodFactory {
@Override
public Food makeFood(String name) {
if (name.equals("A")) {
return new AmericanFoodA();
} else if (name.equals("B")) {
return new AmericanFoodB();
} else {
return null;
}
}
}
其中,ChineseFoodA、ChineseFoodB、AmericanFoodA、AmericanFoodB 都派生自 Food。
客户端调用
public class APP {
public static void main(String[] args) {
// 先选择一个具体的工厂
FoodFactory factory = new ChineseFoodFactory();
// 由第一步的工厂产生具体的对象,不同的工厂造出不一样的对象
Food food = factory.makeFood("A");
}
}
虽然都是调用 makeFood(“A”) 制作 A 类食物,但是,不同的工厂生产出来的完全不一样。
- 第一步,我们需要选取合适的工厂,然后第二步基本上和简单工厂一样。
- 核心在于,我们需要在第一步选好我们需要的工厂。比如,我们有 LogFactory 接口,实现类有 FileLogFactory 和 KafkaLogFactory,分别对应将日志写入文件和写入 Kafka 中,显然,我们客户端第一步就需要决定到底要实例化 FileLogFactory 还是 KafkaLogFactory,这将决定之后的所有的操作。
- 虽然简单,不过我也把所有的构件都画到一张图上,这样看着比较清晰:
抽象工厂模式
- 当涉及到产品族的时候,就需要引入抽象工厂模式了。
- 一个经典的例子是造一台电脑。我们先不引入抽象工厂模式,看看怎么实现。
- 因为电脑是由许多的构件组成的,我们将 CPU 和主板进行抽象,然后 CPU 由 CPUFactory 生产,主板由 MainBoardFactory 生产,然后,我们再将 CPU 和主板搭配起来组合在一起,如下图:
- 这个时候的客户端调用是这样的: ```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);
- 单独看 CPU 工厂和主板工厂,它们分别是前面我们说的**工厂模式**。这种方式也容易扩展,因为要给电脑加硬盘的话,只需要加一个 HardDiskFactory 和相应的实现即可,不需要修改现有的工厂。
- 但是,这种方式有一个问题,那就是如果** Intel 家产的 CPU 和 AMD 产的主板不能兼容使用**,那么这代码就容易出错,因为客户端并不知道它们不兼容,也就会错误地出现随意组合。
-下面就是我们要说的**产品族**的概念,它代表了组成某个产品的一系列附件的集合:<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)
- 当涉及到这种产品族的问题的时候,就需要抽象工厂模式来支持了。我们不再定义 CPU 工厂、主板工厂、硬盘工厂、显示屏工厂等等,我们直接定义电脑工厂,每个电脑工厂负责生产所有的设备,这样能保证肯定不存在兼容问题。
![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)
- 这个时候,对于客户端来说,不再需要单独挑选 CPU厂商、主板厂商、硬盘厂商等,直接选择一家品牌工厂,品牌工厂会负责生产所有的东西,而且能保证肯定是兼容可用的。
```java
public static void main(String[] args) {
// 第一步就要选定一个“大厂”
ComputerFactory cf = new AmdFactory();
// 从这个大厂造 CPU
CPU cpu = cf.makeCPU();
// 从这个大厂造主板
MainBoard board = cf.makeMainBoard();
// 从这个大厂造硬盘
HardDisk hardDisk = cf.makeHardDisk();
// 将同一个厂子出来的 CPU、主板、硬盘组装在一起
Computer result = new Computer(cpu, board, hardDisk);
}
- 当然,抽象工厂的问题也是显而易见的,比如我们要加个显示器,就需要修改所有的工厂,给所有的工厂都加上制造显示器的方法。这有点违反了对修改关闭,对扩展开放这个设计原则。
适配器模式:
@Controller:默认单例模式,通过注解@Scope(“prototype”),将其设置为多例模式
主要分为三类:类适配器模式、对象的适配器模式、接口的适配器模式。使用场景:
- 系统需要使用现有的类,而这些类的接口不符合系统的需要。
- 想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。
- 需要一个统一的输出接口,而输入端的类型不可预知。
迭代器模式:
Iterator循环遍历使用
遍历List集合:list l = new ArrayList();
l.add("aa");
l.add("bb");
l.add("cc");
for (Iterator iter = l.iterator(); iter.hasNext();) {
String str = (String)iter.next();
System.out.println(str);
}
/*迭代器用于while循环
Iterator iter = l.iterator();
while(iter.hasNext()){
String str = (String) iter.next();
System.out.println(str);
}
*/
代理模式:
将程序中的交叉业务逻辑(比如安全,日志,事务等),封装成一个切面,然后注入到目标对象(具体业务逻辑)中去,AOP可以对某个对象或某些对象的功能进行增强,比如对象中的方法进行增强,可以再执行某个方法之前额外做一些事情,在某个方法执行之后额外的做一些事情。
AOP(面向切面)是一种编程范式,提供从另一个角度来考虑程序结构以完善面向对象编程(OOP)。
AOP为开发者提供了一种描述横切关注点的机制,并能够自动将横切关注点织入到面向对象的软件系统中,从而实现了横切关注点的模块化。
AOP能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任,例如事务处理、日志管理、权限控制等,封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。
使用AOP的好处
- 降低模块的耦合度
- 使系统容易扩展
- 提高代码复用性
实现AOP的主要设计模式就是动态代理。Spring的动态代理有两种:一是JDK的动态代理;另一个是cglib动态代理。
AOP使用代理模式实现。IOC主要设计模式是工厂模式。
AOP:目的是解耦,代码解耦。
IOC:创建bean服务都在这里,让IOC容器进行管理。
观察者模式:发布订阅
发短信,发邮件,发送到消息队列