1.工厂模式:
工厂模式是一类创建型设计模式,目的是把对象的创建过程从使用者代码中分离出来,降低耦合、集中管理创建逻辑、方便扩展与测试;
有简单工厂,抽象工厂,工厂方法等;
工厂模式的核心思想是:
1 2
| 依赖倒置原则(DIP) 高层模块不依赖低层模块,二者都依赖抽象
|
如果在Service中new 对象 , 那么Service类 就会依赖于这个对象类;
但是如果使用工厂模式,那么Service类和对象类就都依赖于工厂类,符合了依赖倒置原则;
实现了解耦:
在配置环境中配置:
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
| 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"); } }
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(); } }
|
现代/实用写法:注册式工厂(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)*是一种*结构型设计模式。
它的核心思想是:
为某个对象提供一个代理对象,由代理对象控制对原对象的访问。
简单理解:
客户端不直接访问真实对象,而是通过代理对象访问。
代理对象可以在调用真实对象方法 前后增加额外功能。
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("记录结束..."); } }
|
代理类做了什么:
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(); }
}
|
输出:
四、JDK动态代理
JDK 提供了:
可以 在运行时动态生成代理类。
要求:
示例
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; } }
|
优点:
缺点:
实现方式三:双重检查锁(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
创建对象实际上有三步:
JVM 可能发生:
变成:
其他线程可能拿到:
volatile 可以:
6.迭代器模式