1.工厂模式:

工厂模式是一类创建型设计模式,目的是把对象的创建过程从使用者代码中分离出来,降低耦合、集中管理创建逻辑、方便扩展与测试;

简单工厂,抽象工厂,工厂方法等;

工厂模式的核心思想是:

1
2
依赖倒置原则(DIP)
高层模块不依赖低层模块,二者都依赖抽象

如果在Service中new 对象 , 那么Service类 就会依赖于这个对象类;
但是如果使用工厂模式,那么Service类和对象类就都依赖于工厂类,符合了依赖倒置原则;

实现了解耦:

在配置环境中配置:

1
logger.type=file
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class LoggerFactory {

public static Logger createLogger(){

String type = Config.get("logger.type");

switch(type){
case "file":
return new FileLogger();
case "console":
return new ConsoleLogger();
}

throw new RuntimeException();
}

}

就可以通过修改配置文件来实现不同逻辑,而不用去修改代码

例如简单工厂:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// Product 接口与实现
public interface Shape {
void draw();
}

public class Circle implements Shape {
public void draw() { System.out.println("draw circle"); }
}
public class Rectangle implements Shape {
public void draw() { System.out.println("draw rectangle"); }
}

// SimpleFactory
public class ShapeFactory {
public static Shape create(String type) {
return switch (type) {
case "circle" -> new Circle();
case "rect" -> new Rectangle();
default -> throw new IllegalArgumentException("unknown type: " + type);
};
}
}

// 使用
public class Main {
public static void main(String[] args) {
Shape s1 = ShapeFactory.create("circle");
s1.draw(); // draw circle
}
}

现代/实用写法:注册式工厂(Java 8+)

如果你不想为每个产品写一堆工厂类,可以用 Map<String, Supplier<T>> 注册工厂函数,便于扩展与配置(更贴近现代代码风格)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.util.*;
import java.util.function.Supplier;

public class RegistryFactory {
private static final Map<String, Supplier<Shape>> map = new HashMap<>();

static {
map.put("circle", Circle::new); // 通过方法引用注册
map.put("rect", Rectangle::new);
}

public static Shape create(String key) {
Supplier<Shape> s = map.get(key);
if (s == null) throw new IllegalArgumentException("unknown: " + key);
return s.get();
}

// 运行时注册
public static void register(String key, Supplier<Shape> supplier) {
map.put(key, supplier);
}
}

这种方式便于通过配置文件或反射在运行时注册新类型,适合插件式架构。

Java 8 中引入了一组函数式接口,用于支持 Lambda 表达式和函数式编程,其中 Supplier 就是非常重要的一个接口。Supplier 位于 java.util.function 包中,它表示一个数据提供者(生产者),特点是不接收任何参数,但是会返回一个结果。从接口定义可以看出它的设计非常简单,本质上就是一个“提供数据”的函数。源码如下:

1
2
3
4
@FunctionalInterface
public interface Supplier<T> {
T get();
}

这里的泛型 T 表示返回的数据类型,而 get() 方法就是唯一的抽象方法。当调用 get() 时,就会返回一个对象。因此可以理解为:Supplier 的作用就是在需要的时候提供一个对象或数据。由于它只有一个抽象方法,因此它是一个典型的函数式接口,可以直接使用 Lambda 表达式方法引用 来实现。例如:

1
2
3
4
5
6
7
8
9
10
11
12
import java.util.function.Supplier;

public class Test {
public static void main(String[] args) {

Supplier<String> supplier = () -> "Hello World";

String result = supplier.get();

System.out.println(result);
}
}

在这段代码中,Supplier<String> 表示这个 Supplier 会返回一个 String 类型的数据,Lambda 表达式 () -> "Hello World" 实际上就是实现了 get() 方法。当调用 supplier.get() 时,就会返回 "Hello World" 并输出到控制台。这种写法相比传统的匿名内部类更加简洁。例如,如果不用 Lambda 表达式,传统写法如下:

1
2
3
4
5
6
Supplier<String> supplier = new Supplier<String>() {
@Override
public String get() {
return "Hello World";
}
};

可以看到,Lambda 表达式极大简化了代码。

2.策略模式:

是一种行为型设计模式,它的核心思想是:定义一系列算法或行为,并将每一种算法封装起来,使它们可以相互替换,从而让算法的变化独立于使用算法的客户端

例如早期使用jdbc时需要用Class.forName(“com.mysql.cj.jdbc.Driver”);来配置mysql;也可以通过修改参数来切换到Oracle等;

如果使用switch语句或者if-else语句,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class PayService {

public void pay(String type, double amount) {

if ("alipay".equals(type)) {
System.out.println("使用支付宝支付:" + amount);
} else if ("wechat".equals(type)) {
System.out.println("使用微信支付:" + amount);
} else if ("bank".equals(type)) {
System.out.println("使用银行卡支付:" + amount);
} else {
throw new RuntimeException("不支持的支付方式");
}
}

}

每增加一种新的支付方式,都需要修改原有代码。如果支付方式越来越多,这个方法会变得非常臃肿,同时违反了设计原则中的开闭原则(对扩展开放:当需求发生变化时,我们可以通过添加新的代码来增强系统的功能 ,对修改关闭:在不修改原有源代码的前提下,实现功能的变更); 策略模式正是为了解决这种问题。

策略模式的基本实现步骤通常包括三个部分。第一部分是定义一个策略接口,这个接口规定所有具体策略必须实现的行为。例如在支付系统中,我们可以定义一个支付策略接口:

1
2
3
interface PayStrategy {
void pay(double amount);
}

这个接口代表“支付策略”,任何具体支付方式都必须实现 pay 方法。接下来我们创建多个具体策略类,每个类实现一种具体的支付方式。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class AliPayStrategy implements PayStrategy {

@Override
public void pay(double amount) {
System.out.println("使用支付宝支付:" + amount);
}
}

class WechatPayStrategy implements PayStrategy {

@Override
public void pay(double amount) {
System.out.println("使用微信支付:" + amount);
}
}

class BankPayStrategy implements PayStrategy {

@Override
public void pay(double amount) {
System.out.println("使用银行卡支付:" + amount);
}
}

这里每一个类都代表一种具体策略(Concrete Strategy)。每个策略类都实现了同一个接口,因此它们之间可以互相替换。接下来需要一个**上下文类(Context)**来使用这些策略。上下文类并不关心具体是哪一种支付方式,它只依赖策略接口:

1
2
3
4
5
6
7
8
9
10
11
12
class PayContext {

private PayStrategy strategy;

public PayContext(PayStrategy strategy) {
this.strategy = strategy;
}

public void executePay(double amount) {
strategy.pay(amount);
}
}

在这个 PayContext 类中,真正执行支付的逻辑是调用 strategy.pay()。由于 strategy 是接口类型,因此可以在运行时传入任何具体实现类。最后在客户端代码中,我们可以根据需要选择不同的策略:

1
2
3
4
5
6
7
8
9
10
11
public class Test {

public static void main(String[] args) {

PayStrategy strategy = new AliPayStrategy();

PayContext context = new PayContext(strategy);

context.executePay(100);
}
}

如果现在需要改为微信支付,只需要更换策略对象即可:

1
2
3
PayStrategy strategy = new WechatPayStrategy();
PayContext context = new PayContext(strategy);
context.executePay(100);

可以看到,客户端代码不需要修改业务逻辑,只是替换策略对象。如果未来新增一种支付方式,例如 ApplePayStrategy,只需要新增一个实现类即可,而不需要修改已有代码,这就实现了“对扩展开放,对修改关闭”

在实际项目中,策略模式通常会和工厂模式结合使用,以避免客户端直接 new 不同的策略类。例如可以通过工厂根据类型返回对应的策略:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class PayStrategyFactory {

public static PayStrategy create(String type) {

switch (type) {
case "alipay":
return new AliPayStrategy();
case "wechat":
return new WechatPayStrategy();
case "bank":
return new BankPayStrategy();
default:
throw new RuntimeException("不支持的支付方式");
}
}
}

客户端代码可以写成:

1
2
3
4
5
6
7
8
9
10
11
public class Test {

public static void main(String[] args) {

PayStrategy strategy = PayStrategyFactory.create("alipay");

PayContext context = new PayContext(strategy);

context.executePay(200);
}
}

这样就把策略选择逻辑策略执行逻辑进一步解耦。

3.备忘录模式

**备忘录模式(Memento Pattern)*是一种*行为型设计模式,它的核心思想是:

在不破坏对象封装性的前提下,捕获对象的内部状态,并在需要的时候恢复到之前的状态。

简单理解就是:
给对象做“快照”,需要的时候可以恢复到过去的某个状态。

常见场景:

  • 撤销(Undo)功能:文本编辑器撤销操作
  • 回滚操作:数据库事务回滚
  • 游戏存档:保存和读取游戏进度

一、备忘录模式的角色

备忘录模式通常有 三个角色

角色 作用
Originator(发起人) 需要保存状态的对象
Memento(备忘录) 用来存储对象状态
Caretaker(管理者) 负责保存和恢复备忘录

关系结构:

1
2
3
4
5
Originator  ----创建---->  Memento
↑ |
| |
--------恢复状态-----------
Caretaker

4.代理模式

**代理模式(Proxy Pattern)*是一种*结构型设计模式
它的核心思想是:

为某个对象提供一个代理对象,由代理对象控制对原对象的访问。

简单理解:

1
客户端 -> 代理对象 -> 真实对象

客户端不直接访问真实对象,而是通过代理对象访问。

代理对象可以在调用真实对象方法 前后增加额外功能

Spring Aop底层用的就是动态代理;

代理模式是为了保证原对象的安全,不去暴露太多原对象的信息; 同时代理类内有前置,后置方法,可以在真实对象中添加其他的逻辑;

一、代理模式的结构

代理分为静态代理动态代理

静态代理是由程序写死的,动态代理是运行时动态生成的;

对于静态代理类,对每个类都要写一个代理类,会导致一个代码爆炸的问题;

代理模式一般有三个角色:

角色 作用
Subject(抽象主题) 定义公共接口
RealSubject(真实对象) 真正执行业务逻辑
Proxy(代理对象) 控制对真实对象的访问

结构图:

1
2
3
4
5
6
7
        Subject

----------------
| |
RealSubject Proxy
|
RealSubject

调用流程:

1
2
3
4
5
Client

Proxy

RealSubject

二、静态代理(Static Proxy)

静态代理指的是:

代理类在编译时就已经确定。


示例:用户服务代理

1 定义接口(Subject)

1
2
3
4
5
public interface UserService {

void login();

}

2 实现真实对象(RealSubject)

1
2
3
4
5
6
7
8
public class UserServiceImpl implements UserService {

@Override
public void login() {
System.out.println("用户登录...");
}

}

3 创建代理类(Proxy)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class UserServiceProxy implements UserService {

private UserService userService;

public UserServiceProxy(UserService userService) {
this.userService = userService;
}

@Override
public void login() {

System.out.println("记录日志...");

userService.login();

System.out.println("记录结束...");
}
}

代理类做了什么:

1
2
3
前置增强
调用真实对象
后置增强

4 测试代码

1
2
3
4
5
6
7
8
9
10
11
12
public class Test {

public static void main(String[] args) {

UserService userService = new UserServiceImpl();

UserService proxy = new UserServiceProxy(userService);

proxy.login();
}

}

输出:

1
2
3
记录日志...
用户登录...
记录结束...

四、JDK动态代理

JDK 提供了:

1
java.lang.reflect.Proxy

可以 在运行时动态生成代理类

要求:

1
必须基于接口

示例

1 接口

1
2
3
4
5
public interface UserService {

void login();

}

2 实现类

1
2
3
4
5
6
7
8
public class UserServiceImpl implements UserService {

@Override
public void login() {
System.out.println("用户登录...");
}

}

3 InvocationHandler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class LogHandler implements InvocationHandler {

private Object target;

public LogHandler(Object target) {
this.target = target;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {

System.out.println("方法执行前记录日志");

Object result = method.invoke(target, args);

System.out.println("方法执行后记录日志");

return result;
}
}

4 创建代理对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.lang.reflect.Proxy;

public class Test {

public static void main(String[] args) {

UserService userService = new UserServiceImpl();

UserService proxy = (UserService) Proxy.newProxyInstance(
userService.getClass().getClassLoader(),
userService.getClass().getInterfaces(),
new LogHandler(userService)
);

proxy.login();
}

}

输出:

1
2
3
方法执行前记录日志
用户登录...
方法执行后记录日志

5.单例模式:

**单例模式(Singleton Pattern)*是一种*创建型设计模式
它的核心思想是:

保证一个类在整个系统中只有一个实例,并提供一个全局访问点。

也就是说:

1
2
3
4
5
一个类

只能创建一个对象

所有地方都使用这个对象

常见使用场景:

1
2
3
4
5
线程池
数据库连接池
配置管理类
Spring Bean(默认单例)
日志对象

Sping中的Bean默认就是单例的,IOC容器管理它的生命周期,容器关闭,Bean也随之销毁; 对于多例的Bean可以使用@Scope注解来设置; 但是IOC容器只负责创建,不负责完整生命周期管理,需要自己来主动销毁;

实现方式一:饿汉式

特点:类加载时就创建对象

代码示例

1
2
3
4
5
6
7
8
9
10
11
public class Singleton {

private static final Singleton INSTANCE = new Singleton();

private Singleton() {
}

public static Singleton getInstance() {
return INSTANCE;
}
}

实现方法一:懒汉式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Singleton {

private static Singleton instance;

private Singleton() {
}

public static Singleton getInstance() {

if (instance == null) {
instance = new Singleton();
}

return instance;
}

}

实现方式二:同步锁懒汉式

解决线程安全问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Singleton {

private static Singleton instance;

private Singleton() {
}

public static synchronized Singleton getInstance() {

if (instance == null) {
instance = new Singleton();
}

return instance;
}
}

优点:

1
线程安全

缺点:

1
2
性能差
每次都加锁

实现方式三:双重检查锁(DCL)

这是面试最常问的单例写法。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Singleton {

private static volatile Singleton instance;

private Singleton() {
}

public static Singleton getInstance() {

if (instance == null) {

synchronized (Singleton.class) {

if (instance == null) {

instance = new Singleton();

}

}
}

return instance;
}
}

为什么要 volatile

创建对象实际上有三步:

1
2
3
1 分配内存
2 初始化对象
3 指向内存

JVM 可能发生:

1
指令重排序

变成:

1
2
3
1 分配内存
3 指向内存
2 初始化对象

其他线程可能拿到:

1
未初始化对象

volatile 可以:

1
禁止指令重排序

6.迭代器模式