设计模式:七个最常用的套路
设计模式不是玄学,是前人踩坑后总结的"代码怎么组织才好改"。别去背那 23 种,先把下面七个真正天天用的吃透:单例、工厂、策略、观察者、装饰器、代理、模板方法。Spring 框架本身就是用这些套路堆出来的,学懂了读源码不懵。
图七个模式一句话地图
创建型:单例(一个类全局只有一个对象)、工厂(你别 new,我帮你造)。行为型:策略(一组可互换的算法)、观察者(一对多,状态变了通知一片)、模板方法(定好骨架,子类填细节)。结构型:装饰器(一层层包着加功能)、代理(找个替身替你办事)。
单例 Singleton:全局只有一个对象
配置、连接池、日志器这种"一个就够、多了浪费"的东西,用单例。大白话:把构造器藏起来,自己持有唯一一个实例,谁要都给同一个。
// 饿汉式:类加载时就造好,天生线程安全,最简单
public final class Config {
private static final Config INSTANCE = new Config();
private Config() {} // 私有构造器,外面 new 不了
public static Config getInstance() { return INSTANCE; }
}
// 枚举单例:最推荐,天生防序列化破坏、防反射
public enum Logger {
INSTANCE; // 枚举天然单例,JVM 保证
public void log(String msg) { System.out.println(msg); }
}
// 用:Logger.INSTANCE.log("hi");
工厂 Factory:别自己 new,交给工厂
new 哪个实现类,本来应该是个"决策"。工厂模式把"造哪个对象"的代码收进一个地方,换实现时只改工厂,调用方一行不动。Spring 的 IoC 就是个超级大工厂。
interface Notification { void send(String msg); }
class SmsNoti implements Notification {
public void send(String m) { System.out.println("发短信:" + m); }
}
class EmailNoti implements Notification {
public void send(String m) { System.out.println("发邮件:" + m); }
}
// 工厂:调用方说要哪种,工厂负责 new 对应的实现
class NotiFactory {
public static Notification get(String type) {
return switch (type) {
case "sms" -> new SmsNoti();
case "email" -> new EmailNoti();
default -> throw new IllegalArgumentException("未知渠道");
};
}
}
// 业务代码只认接口,换渠道不用改这里
NotiFactory.get("sms").send("订单已下单");
策略 Strategy:一组可互换的算法
"满减""打折""第二杯半价"都是算价格的算法,以后还要加新玩法。用 if-else 越堆越长,策略模式把每种算法做成一个类,运行时挑一个用。Spring 里 PaymentService 有多种支付方式,就是策略。
interface DiscountStrategy { double apply(double price); }
class FullReduction implements DiscountStrategy {
public double apply(double p) { return p >= 100 ? p - 20 : p; }
}
class HalfOff implements DiscountStrategy {
public double apply(double p) { return p * 0.5; }
}
// 运行时随便换,新增活动就加个类,不改老代码
DiscountStrategy s = new HalfOff();
System.out.println(s.apply(100)); // 50.0
观察者 Observer:状态变了,通知一片
公众号发文章,所有关注者同时收到。观察者模式就是"一对多":被观察的对象一变,自动通知所有订阅者。Spring 的 ApplicationEvent(下一页讲)就是这个套路。
// Java 内置:Observable 已废弃,现在用事件/监听器自己写更清楚
// 这里用函数式接口演示精髓:主题持有一堆监听器
class OrderEvent {
private final List<Runnable> listeners = new CopyOnWriteArrayList<>();
public void onCreated(Runnable r) { listeners.add(r); }
public void create() {
System.out.println("订单创建");
listeners.forEach(Runnable::run); "> 逐个通知所有订阅者
}
}
// 用法:下单成功后,发短信、加积分、推消息,各干各的,解耦
OrderEvent e = new OrderEvent();
e.onCreated(() -> System.out.println("发短信通知"));
e.onCreated(() -> System.out.println("给用户加积分"));
e.create();
装饰器 Decorator:一层层包着加功能
咖啡可以加奶、加糖、加奶油,组合方式太多,用继承写不完。装饰器是"拿一个对象,包一层,加一点功能,再传下去"。Java IO 就是教科书:new BufferedInputStream(new FileInputStream(f)),一层包一层。
interface Coffee { double cost(); }
class BlackCoffee implements Coffee {
public double cost() { return 10; }
}
// 装饰器:包住一个 Coffee,在此基础上加价
class WithMilk implements Coffee {
private final Coffee inner;
public WithMilk(Coffee c) { this.inner = c; }
public double cost() { return inner.cost() + 3; }
}
Coffee c = new WithMilk(new BlackCoffee());
System.out.println(c.cost()); // 13.0
代理 Proxy:找个替身替你办事
你想在调一个方法前后加日志、加权限、加事务,但又不想改这个类的代码——代理模式做个替身,原方法前后插一脚。Spring AOP 的底层就是它,下一章详解。
interface UserService { void save(); }
class UserServiceImpl implements UserService {
public void save() { System.out.println("存用户到数据库"); }
}
// 静态代理:包一层,前后加日志
class LogProxy implements UserService {
private final UserService target;
public LogProxy(UserService t) { this.target = t; }
public void save() {
System.out.println("[日志] 调用前");
target.save(); "> 调真正的对象
System.out.println("[日志] 调用后");
}
}
模板方法 Template Method:定骨架,子类填细节
"泡饮料"的流程固定:烧水→放料→冲泡→加料。茶和咖啡只是"放什么料、加什么料"不同。模板方法在父类写死流程顺序,把差异步骤做成抽象方法让子类填。Spring 的 JdbcTemplate 就是这个思路。
abstract class Drink {
// 模板方法:流程固定,用 final 防止子类改顺序
public final void make() {
boilWater();
brew(); "> 抽象步骤,子类填
pourInCup();
addCondiments(); "> 抽象步骤,子类填
}
private void boilWater() { System.out.println("烧水"); }
private void pourInCup() { System.out.println("倒杯子"); }
protected abstract void brew();
protected abstract void addCondiments();
}
class CoffeeDrink extends Drink {
protected void brew() { System.out.println("冲咖啡"); }
protected void addCondiments() { System.out.println("加牛奶"); }
}
设计模式是"当你发现要改代码很难改时"才掏出来的重构工具,不是写代码前先套的框架。业务还很简单时,三行 if-else 能搞定,就别上来工厂+策略+单例全家桶——过度设计比没设计更坑。读 Spring 源码认出它们就行,自己写先从最简单的来。
1.(概念题)单例有几种写法?为什么推荐枚举单例?
查看答案
答案:饿汉式、懒汉式(双重检查锁)、枚举式。枚举单例由 JVM 保证实例唯一,天然防反射破坏和反序列化重新 new,最省心。
2.(概念题)策略模式解决什么问题?和 if-else 比好在哪?
查看答案
答案:一堆可互换的算法(多种折扣、多种支付),避免长长的 if-else。新增算法只需加一个类,符合开闭原则,不改老代码。
3.(概念题)代理模式和装饰器模式长得像,区别是?
查看答案
答案:都是"包一层",但意图不同:装饰器是动态加功能(包装的人自己决定包几层);代理是控制访问(代理类知道真实对象是谁、负责拦着/管控,比如 Spring AOP 加事务)。
4.(思考题)Spring 的 IoC 容器、AOP、JdbcTemplate 分别对应哪个模式?
查看答案
答案:IoC/DI 是工厂模式(大工厂帮你 new 和注入 Bean);AOP 是代理模式(动态代理前后插切面);JdbcTemplate 是模板方法(固定流程,只留 SQL/行映射给你填)。