楼层: 首页/ 软件技术/ Java 基础/ 设计模式:七个最常用的套路
12

设计模式:七个最常用的套路

Design Patterns · Singleton / Factory / Strategy / Observer / Decorator / Proxy / Template

设计模式不是玄学,是前人踩坑后总结的"代码怎么组织才好改"。别去背那 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 源码认出它们就行,自己写先从最简单的来。

章末面试 · 设计模式(4 题)

1.(概念题)单例有几种写法?为什么推荐枚举单例?

查看答案

答案:饿汉式、懒汉式(双重检查锁)、枚举式。枚举单例由 JVM 保证实例唯一,天然防反射破坏和反序列化重新 new,最省心。

2.(概念题)策略模式解决什么问题?和 if-else 比好在哪?

查看答案

答案:一堆可互换的算法(多种折扣、多种支付),避免长长的 if-else。新增算法只需加一个类,符合开闭原则,不改老代码。

3.(概念题)代理模式和装饰器模式长得像,区别是?

查看答案

答案:都是"包一层",但意图不同:装饰器是动态加功能(包装的人自己决定包几层);代理是控制访问(代理类知道真实对象是谁、负责拦着/管控,比如 Spring AOP 加事务)。

4.(思考题)Spring 的 IoC 容器、AOP、JdbcTemplate 分别对应哪个模式?

查看答案

答案:IoC/DI 是工厂模式(大工厂帮你 new 和注入 Bean);AOP 是代理模式(动态代理前后插切面);JdbcTemplate 是模板方法(固定流程,只留 SQL/行映射给你填)。