李锋镝的博客

  • 首页
  • 时间轴
  • 左邻右舍
  • 关于我
    • 关于我
    • 另一个网站
    • 我的导航站
    • 网站地图
    • 赞助
    • 博友圈
  • 说说
  • 走心评论
  • 互动榜
  • 留言
  • 🚇开往
Destiny
自是人生长恨水长东
  1. 首页
  2. 后端
  3. 正文

6个高频设计模式深度解析(附完整案例与避坑指南)

2025年10月16日 281点热度 0人点赞 0条评论

很多Java开发者都有过这样的困惑:明明背过23种设计模式的定义,写代码时却还是习惯性new 对象()、堆多层if-else。其实设计模式的核心不是“套用模板”,而是“解决具体问题”——尤其是项目中反复出现的“资源控制”“接口兼容”“逻辑扩展”等场景。本文就聚焦Java项目中最常用的6个设计模式,从核心原理到实战优化,再到踩坑指南,帮你真正把设计模式用在刀刃上。

一、单例模式:控制资源的“独家权限”

1.1 核心概念

单例模式的本质是保证一个类在整个应用中只有一个实例,并提供全局唯一的访问入口。它的设计初衷是避免重复创建重量级对象(如数据库连接池、配置类),减少资源浪费,同时防止多实例导致的逻辑混乱(如重复发送短信)。

1.2 适用场景(细分场景)

  • 资源密集型对象:数据库连接池、Redis客户端、线程池等,重复创建会消耗大量内存和CPU。
  • 全局配置类:系统的配置信息(如接口地址、密钥),需要全局统一读取,避免多实例不一致。
  • 工具/服务类:日志服务、短信通知服务、缓存服务等,多实例可能导致重复操作(如重复打印日志、重复发消息)。

1.3 5种实战实现(附优缺点对比)

单例模式的关键是“防止外部new对象”(私有构造方法)和“保证线程安全”,不同实现方式在性能、安全性上有明显差异:

(1)饿汉式:类加载时初始化

public class SMS饿汉单例 {
    // 1. 私有静态实例:类加载时直接创建,天然线程安全
    private static final SMS饿汉单例 INSTANCE = new SMS饿汉单例();

    // 2. 私有构造方法:禁止外部new
    private SMS饿汉单例() {
        // 初始化短信配置(如加载API密钥)
        loadSmsConfig();
    }

    // 3. 全局访问入口
    public static SMS饿汉单例 getInstance() {
        return INSTANCE;
    }

    // 业务方法
    public void sendSms(String phone, String content) {
        System.out.println("向" + phone + "发送短信:" + content);
    }

    private void loadSmsConfig() {
        // 实际项目中可能从配置文件/数据库读取
        System.out.println("初始化短信配置...");
    }
}
  • 优点:实现简单,类加载时完成初始化,无线程安全问题。
  • 缺点:如果类加载后一直未使用(如启动时加载但后续没发过短信),会浪费内存(“饿”的含义就是“提前创建”)。
  • 适用场景:单例对象初始化耗时短、且一定会被使用(如系统核心配置类)。

(2)懒汉式(基础版):用的时候再创建

public class SMS懒汉基础版 {
    private static SMS懒汉基础版 instance;

    private SMS懒汉基础版() {
        loadSmsConfig();
    }

    // 注意:未加锁,多线程下会创建多个实例!
    public static SMS懒汉基础版 getInstance() {
        if (instance == null) { // 线程A和线程B可能同时进入这里
            instance = new SMS懒汉基础版(); // 导致创建两个实例
        }
        return instance;
    }
}
  • 问题:多线程场景下完全不安全,比如两个线程同时进入if (instance == null),会创建多个实例,违背单例核心原则。
  • 结论:项目中绝对不能用!仅作为理解“懒加载”的入门案例。

(3)懒汉式(同步方法版):加锁保证线程安全

public class SMS懒汉同步版 {
    private static SMS懒汉同步版 instance;

    private SMS懒汉同步版() {
        loadSmsConfig();
    }

    // 关键:给整个方法加synchronized锁
    public static synchronized SMS懒汉同步版 getInstance() {
        if (instance == null) {
            instance = new SMS懒汉同步版();
        }
        return instance;
    }
}
  • 优点:解决了线程安全问题,只有第一次创建实例时会加锁。
  • 缺点:每次调用getInstance()都会加锁(即使实例已存在),加锁会消耗性能,高并发场景下会导致接口响应变慢。
  • 适用场景:并发量极低的场景(如后台管理系统的工具类)。

(4)双重检查锁定(DCL):性能与安全兼顾

这是项目中最常用的实现方式,通过“两次检查+volatile”解决线程安全和性能问题:

public class SMS双重检查单例 {
    // 关键1:volatile修饰,防止指令重排(核心!)
    private static volatile SMS双重检查单例 instance;

    private SMS双重检查单例() {
        loadSmsConfig();
    }

    public static SMS双重检查单例 getInstance() {
        // 第一次检查:如果实例已存在,直接返回(无锁,提升性能)
        if (instance == null) {
            // 加锁:只对“创建实例”的逻辑加锁
            synchronized (SMS双重检查单例.class) {
                // 第二次检查:防止多线程同时进入第一个if后,重复创建
                if (instance == null) {
                    instance = new SMS双重检查单例(); 
                    // 不加volatile可能出现“指令重排”:instance指向未初始化完成的对象
                }
            }
        }
        return instance;
    }
}
  • 关键细节:volatile的作用是禁止“指令重排”——new SMS双重检查单例()会拆分为“分配内存→初始化对象→赋值给instance”三步,不加volatile时,JVM可能先赋值再初始化,导致其他线程拿到“未初始化完成的实例”(调用方法时抛空指针)。
  • 优点:高并发场景下性能好,只有第一次创建实例时加锁,后续访问无锁。
  • 适用场景:项目中绝大多数单例场景(如Redis客户端、短信服务)。

(5)枚举单例:防反射+防序列化的“终极方案”

《Effective Java》推荐的实现方式,天然解决“反射破坏单例”和“序列化破坏单例”问题:

public enum SMS枚举单例 {
    // 唯一实例(枚举常量)
    INSTANCE;

    // 初始化方法(枚举的构造方法默认私有,无需手动写)
    SMS枚举单例() {
        loadSmsConfig();
    }

    // 业务方法
    public void sendSms(String phone, String content) {
        System.out.println("向" + phone + "发送短信:" + content);
    }

    private void loadSmsConfig() {
        System.out.println("初始化短信配置...");
    }
}

// 使用方式
SMS枚举单例.INSTANCE.sendSms("13800138000", "订单已确认");
  • 核心优势:
    1. 防反射:枚举的构造方法由JVM控制,外部无法通过Constructor.newInstance()创建实例(会抛异常)。
    2. 防序列化:枚举的readObject()方法会直接返回枚举常量,不会创建新实例(普通单例序列化后反序列化会生成新对象)。
  • 缺点:枚举类加载时会初始化实例,无法实现“懒加载”(如果实例初始化耗时且不一定用,会浪费资源)。
  • 适用场景:对安全性要求极高的场景(如支付签名服务、加密工具类)。

1.4 项目踩坑指南

  1. 避免在单例中持有可变状态:如单例类中定义private int count,多线程修改时会出现线程安全问题(需加锁保护)。
  2. 注意Spring环境的“单例冲突”:Spring默认将Bean管理为单例,如果自己写的单例类被Spring扫描(如加了@Component),会导致Spring创建一个实例,自己getInstance()又创建一个,违背单例原则——解决方案:要么让Spring管理,要么禁用Spring扫描该类。
  3. 序列化问题:普通单例(如DCL版)需要重写readResolve()方法,否则反序列化会生成新实例:
    private Object readResolve() {
       return getInstance(); // 反序列化时返回已有实例
    }

二、工厂模式:对象创建的“流水线”

2.1 核心概念

工厂模式的本质是将“对象创建逻辑”与“业务使用逻辑”分离——调用者不需要知道对象的具体实现(如“微信支付”还是“支付宝”),只需告诉工厂“我要什么类型”,工厂就返回对应的对象。它解决的核心问题是“对象创建复杂”和“接口扩展难”。

2.2 三种工厂模式(实战对比)

工厂模式分为“简单工厂”“工厂方法”“抽象工厂”,对应不同的业务复杂度,项目中最常用的是前两种:

(1)简单工厂:适合产品类型少的场景

以支付系统为例,支持微信、支付宝两种支付方式,简单工厂直接根据类型创建对象:

// 1. 统一支付接口(产品接口)
public interface Payment {
    // 处理支付
    void pay(double amount);
    // 查询支付状态
    String queryStatus(String orderId);
}

// 2. 具体产品:微信支付
public class WeChatPayment implements Payment {
    @Override
    public void pay(double amount) {
        System.out.println("微信支付:" + amount + "元");
        // 实际调用微信支付API(如统一下单接口)
    }

    @Override
    public String queryStatus(String orderId) {
        return "微信支付状态:SUCCESS(订单号:" + orderId + ")";
    }
}

// 3. 具体产品:支付宝
public class AliPayment implements Payment {
    @Override
    public void pay(double amount) {
        System.out.println("支付宝支付:" + amount + "元");
    }

    @Override
    public String queryStatus(String orderId) {
        return "支付宝支付状态:SUCCESS(订单号:" + orderId + ")";
    }
}

// 4. 简单工厂:创建支付对象的“流水线”
public class PaymentSimpleFactory {
    // 根据类型创建对象(核心方法)
    public static Payment createPayment(String type) {
        switch (type) {
            case "WECHAT":
                return new WeChatPayment();
            case "ALIPAY":
                return new AliPayment();
            default:
                throw new IllegalArgumentException("不支持的支付方式:" + type);
        }
    }
}

// 5. 使用方式(调用者无需关心具体实现)
public class OrderService {
    public void processOrder(String orderId, double amount, String payType) {
        // 直接从工厂获取支付对象
        Payment payment = PaymentSimpleFactory.createPayment(payType);
        // 调用支付逻辑(业务使用与创建分离)
        payment.pay(amount);
        System.out.println(payment.queryStatus(orderId));
    }
}
  • 优点:实现简单,调用者代码简洁(无需new WeChatPayment())。
  • 缺点:违反“开闭原则”——如果新增“银行卡支付”,需要修改工厂类的createPayment()方法(加新的case),修改原有代码会增加风险。
  • 适用场景:产品类型固定、很少扩展的场景(如明确只支持2-3种支付方式)。

(2)工厂方法:适合产品频繁扩展的场景

为了解决简单工厂的“开闭原则”问题,工厂方法将“创建逻辑”拆分为多个工厂(每个产品对应一个工厂),新增产品时只需新增工厂,无需修改原有代码:

// 1. 统一支付接口(同简单工厂)
public interface Payment {
    void pay(double amount);
    String queryStatus(String orderId);
}

// 2. 具体产品:微信支付、支付宝(同简单工厂)
public class WeChatPayment implements Payment {...}
public class AliPayment implements Payment {...}

// 3. 工厂接口:定义创建支付对象的规范
public interface PaymentFactory {
    Payment createPayment();
}

// 4. 具体工厂:微信支付工厂(只创建微信支付对象)
public class WeChatPaymentFactory implements PaymentFactory {
    @Override
    public Payment createPayment() {
        // 可以在这里添加微信支付的初始化逻辑(如加载证书)
        return new WeChatPayment();
    }
}

// 5. 具体工厂:支付宝工厂
public class AliPaymentFactory implements PaymentFactory {
    @Override
    public Payment createPayment() {
        return new AliPayment();
    }
}

// 6. 使用方式
public class OrderService {
    public void processOrder(String orderId, double amount, PaymentFactory factory) {
        // 从具体工厂获取支付对象(无需知道类型)
        Payment payment = factory.createPayment();
        payment.pay(amount);
        System.out.println(payment.queryStatus(orderId));
    }
}

// 调用时指定工厂
public static void main(String[] args) {
    OrderService service = new OrderService();
    // 微信支付:传入微信工厂
    service.processOrder("123", 99.9, new WeChatPaymentFactory());
    // 支付宝支付:传入支付宝工厂
    service.processOrder("456", 199.9, new AliPaymentFactory());
}
  • 优点:符合“开闭原则”——新增“银行卡支付”时,只需新增BankCardPayment(产品)和BankCardPaymentFactory(工厂),原有代码无需修改。
  • 缺点:类数量增多(每个产品对应一个工厂),如果产品类型极多(如10种支付方式),会导致工厂类泛滥。
  • 适用场景:产品需要频繁扩展的场景(如支付系统可能不断接入新的支付渠道)。

(3)抽象工厂:适合“产品族”扩展(了解即可)

当需要创建“相关联的一组产品”时(如“支付+退款”为一个产品族,微信有微信支付+微信退款,支付宝有支付宝支付+支付宝退款),抽象工厂会定义创建“产品族”的接口,具体工厂实现整个产品族的创建。项目中使用较少,适合复杂的跨产品场景。

2.3 项目优化技巧

  1. 解决简单工厂的if-else/switch问题:用Map预存“类型→产品”的映射,避免硬编码判断:

    public class PaymentSimpleFactory {
       // 预存类型与产品的映射
       private static final Map PAYMENT_MAP = new HashMap<>();
    
       // 静态代码块初始化映射(只加载一次)
       static {
           PAYMENT_MAP.put("WECHAT", new WeChatPayment());
           PAYMENT_MAP.put("ALIPAY", new AliPayment());
       }
    
       public static Payment createPayment(String type) {
           Payment payment = PAYMENT_MAP.get(type);
           if (payment == null) {
               throw new IllegalArgumentException("不支持的支付方式:" + type);
           }
           return payment;
       }
    }
  2. 结合Spring实现工厂:在Spring项目中,可让工厂从Spring容器中获取Bean,避免手动new对象(更符合Spring的依赖注入思想):

    @Component
    public class SpringPaymentFactory {
       @Autowired
       private Map paymentMap; // Spring会自动注入所有Payment实现类,key为Bean名
    
       public Payment createPayment(String beanName) {
           return paymentMap.get(beanName);
       }
    }

三、策略模式:可替换的“算法家族”

3.1 核心概念

策略模式的本质是定义一系列算法(如满减、折扣、直减),将每个算法封装成独立的“策略类”,并让它们可以互相替换。它解决的核心问题是“大量if-else判断”和“算法动态切换”——比如电商系统的优惠逻辑,不同活动用不同优惠算法,无需改核心代码。

3.2 实战案例:电商优惠系统

以电商订单的优惠逻辑为例,支持“满300减50”“全场8折”“直减20”三种优惠,用策略模式替代if-else:

(1)定义策略接口(算法规范)

// 优惠策略接口(所有优惠算法的统一规范)
public interface DiscountStrategy {
    // 计算优惠金额:参数是订单原价,返回优惠金额
    double calculateDiscount(double orderTotal);
    // 获取策略类型(如"FULL_REDUCTION")
    String getStrategyType();
}

(2)实现具体策略(不同算法)

// 1. 满减策略(满300减50)
public class FullReductionStrategy implements DiscountStrategy {
    private double threshold; // 满减门槛
    private double reduction; // 减免金额

    // 构造方法注入参数(灵活配置满减规则)
    public FullReductionStrategy(double threshold, double reduction) {
        this.threshold = threshold;
        this.reduction = reduction;
    }

    @Override
    public double calculateDiscount(double orderTotal) {
        // 满门槛才减免,否则优惠0元
        return orderTotal >= threshold ? reduction : 0;
    }

    @Override
    public String getStrategyType() {
        return "FULL_REDUCTION";
    }
}

// 2. 折扣策略(全场8折)
public class PercentageDiscountStrategy implements DiscountStrategy {
    private double discountRate; // 折扣率(0.8表示8折)

    public PercentageDiscountStrategy(double discountRate) {
        this.discountRate = discountRate;
    }

    @Override
    public double calculateDiscount(double orderTotal) {
        // 优惠金额 = 原价 - 原价*折扣率
        return orderTotal - (orderTotal * discountRate);
    }

    @Override
    public String getStrategyType() {
        return "PERCENTAGE";
    }
}

// 3. 直减策略(直接减20)
public class DirectReductionStrategy implements DiscountStrategy {
    private double directReduction; // 直减金额

    public DirectReductionStrategy(double directReduction) {
        this.directReduction = directReduction;
    }

    @Override
    public double calculateDiscount(double orderTotal) {
        // 无论原价多少,都减固定金额(但不允许优惠后为负)
        return Math.min(directReduction, orderTotal);
    }

    @Override
    public String getStrategyType() {
        return "DIRECT_REDUCTION";
    }
}

(3)策略上下文(算法调用入口)

上下文类负责“持有策略”和“调用策略”,隔离策略的创建与使用:

public class DiscountContext {
    // 持有当前使用的策略
    private DiscountStrategy currentStrategy;

    // 构造方法注入策略(也可提供set方法动态切换)
    public DiscountContext(DiscountStrategy currentStrategy) {
        this.currentStrategy = currentStrategy;
    }

    // 动态切换策略(如用户中途更换优惠方式)
    public void setCurrentStrategy(DiscountStrategy currentStrategy) {
        this.currentStrategy = currentStrategy;
    }

    // 调用策略计算优惠(给外部的统一入口)
    public double calculateFinalPrice(double orderTotal) {
        double discount = currentStrategy.calculateDiscount(orderTotal);
        return orderTotal - discount; // 返回最终价格
    }
}

(4)策略注册与使用(优化版)

为了避免手动创建策略,可通过“策略注册器”预存所有策略,根据类型动态获取(类似工厂模式的优化):

// 策略注册器(管理所有优惠策略)
public class DiscountStrategyRegistry {
    private static final Map<String, DiscountStrategy> STRATEGY_MAP = new HashMap<>();

    // 注册策略(项目启动时初始化)
    public static void registerStrategy(DiscountStrategy strategy) {
        STRATEGY_MAP.put(strategy.getStrategyType(), strategy);
    }

    // 根据类型获取策略
    public static DiscountStrategy getStrategy(String type) {
        DiscountStrategy strategy = STRATEGY_MAP.get(type);
        if (strategy == null) {
            throw new IllegalArgumentException("不支持的优惠类型:" + type);
        }
        return strategy;
    }
}

// 项目启动时初始化策略(如Spring的@PostConstruct)
public class StrategyInit {
    @PostConstruct
    public void init() {
        // 注册满减策略(满300减50)
        DiscountStrategyRegistry.registerStrategy(new FullReductionStrategy(300, 50));
        // 注册折扣策略(8折)
        DiscountStrategyRegistry.registerStrategy(new PercentageDiscountStrategy(0.8));
        // 注册直减策略(减20)
        DiscountStrategyRegistry.registerStrategy(new DirectReductionStrategy(20));
    }
}

// 最终使用(订单服务)
public class OrderService {
    public double processOrder(double orderTotal, String discountType) {
        // 1. 从注册器获取策略
        DiscountStrategy strategy = DiscountStrategyRegistry.getStrategy(discountType);
        // 2. 调用上下文计算最终价格
        DiscountContext context = new DiscountContext(strategy);
        return context.calculateFinalPrice(orderTotal);
    }
}

// 测试
public static void main(String[] args) {
    OrderService service = new OrderService();
    // 满减优惠:原价400 → 400-50=350
    System.out.println(service.processOrder(400, "FULL_REDUCTION"));
    // 折扣优惠:原价200 → 200*0.8=160
    System.out.println(service.processOrder(200, "PERCENTAGE"));
}

3.3 与工厂模式的区别(易混淆点)

很多人会混淆策略模式和工厂模式,核心区别在于关注点不同:

  • 工厂模式:关注“对象创建”,解决“如何创建复杂对象”的问题,返回的对象是“被使用的工具”。
  • 策略模式:关注“算法替换”,解决“如何动态切换不同逻辑”的问题,返回的对象是“可执行的算法”。
  • 简单说:工厂是“造工具的”,策略是“用工具干活的”。

四、观察者模式:事件通知的“广播系统”

4.1 核心概念

观察者模式的本质是定义“一对多”的依赖关系——当一个对象(被观察者)的状态发生变化时,所有依赖它的对象(观察者)会自动收到通知并更新。它解决的核心问题是“模块解耦”,比如订单支付后,需要通知库存、物流、短信三个系统,无需在订单模块中硬编码调用这三个系统的接口。

4.2 三种实战实现(从手动到框架)

(1)自定义观察者(基础版)

以订单状态变更为例,订单(被观察者)状态变更后,通知库存、物流、短信三个观察者:

// 1. 观察者接口(所有观察者的统一规范)
public interface OrderObserver {
    // 接收通知的方法(参数是被观察者对象,可获取状态)
    void onOrderStatusChanged(Order order);
}

// 2. 具体观察者1:库存系统
public class InventoryObserver implements OrderObserver {
    @Override
    public void onOrderStatusChanged(Order order) {
        if ("PAID".equals(order.getStatus())) {
            System.out.println("库存系统:订单" + order.getId() + "已支付,扣减库存");
            // 实际扣减库存逻辑
        } else if ("CANCELLED".equals(order.getStatus())) {
            System.out.println("库存系统:订单" + order.getId() + "已取消,恢复库存");
        }
    }
}

// 3. 具体观察者2:物流系统
public class LogisticsObserver implements OrderObserver {
    @Override
    public void onOrderStatusChanged(Order order) {
        if ("PAID".equals(order.getStatus())) {
            System.out.println("物流系统:订单" + order.getId() + "已支付,创建物流单");
        }
    }
}

// 4. 具体观察者3:短信系统
public class SmsObserver implements OrderObserver {
    @Override
    public void onOrderStatusChanged(Order order) {
        System.out.println("短信系统:向用户" + order.getPhone() + "发送通知:订单" + order.getId() + "状态变更为" + order.getStatus());
    }
}

// 5. 被观察者:订单类
public class Order {
    private String id;
    private String status; // 订单状态:CREATED/PAID/CANCELLED
    private String phone;
    // 存储所有观察者
    private List<OrderObserver> observers = new ArrayList<>();

    public Order(String id, String phone) {
        this.id = id;
        this.phone = phone;
        this.status = "CREATED"; // 初始状态
    }

    // 注册观察者(添加需要通知的对象)
    public void addObserver(OrderObserver observer) {
        observers.add(observer);
    }

    // 移除观察者(不再通知)
    public void removeObserver(OrderObserver observer) {
        observers.remove(observer);
    }

    // 通知所有观察者(核心方法,状态变更后调用)
    private void notifyObservers() {
        for (OrderObserver observer : observers) {
            observer.onOrderStatusChanged(this);
        }
    }

    // 修改订单状态(对外的方法)
    public void setStatus(String status) {
        this.status = status;
        // 状态变更后,自动通知所有观察者
        notifyObservers();
    }

    // getter方法
    public String getId() { return id; }
    public String getStatus() { return status; }
    public String getPhone() { return phone; }
}

// 6. 使用方式
public static void main(String[] args) {
    // 创建订单(被观察者)
    Order order = new Order("12345", "13800138000");

    // 注册观察者(添加需要通知的系统)
    order.addObserver(new InventoryObserver());
    order.addObserver(new LogisticsObserver());
    order.addObserver(new SmsObserver());

    // 变更订单状态(触发通知)
    System.out.println("=== 订单支付 ===");
    order.setStatus("PAID");

    System.out.println("=== 订单取消 ===");
    order.setStatus("CANCELLED");
}
  • 输出结果:
    === 订单支付 ===
    库存系统:订单12345已支付,扣减库存
    物流系统:订单12345已支付,创建物流单
    短信系统:向用户13800138000发送通知:订单12345状态变更为PAID
    === 订单取消 ===
    库存系统:订单12345已取消,恢复库存
    短信系统:向用户13800138000发送通知:订单12345状态变更为CANCELLED

(2)Java自带Observer(JDK原生)

JDK提供了java.util.Observer和java.util.Observable,可直接使用,无需手动定义观察者接口:

import java.util.Observable;
import java.util.Observer;

// 1. 被观察者:继承Observable
public class JdkOrder extends Observable {
    private String id;
    private String status;
    private String phone;

    public JdkOrder(String id, String phone) {
        this.id = id;
        this.phone = phone;
        this.status = "CREATED";
    }

    public void setStatus(String status) {
        this.status = status;
        // 关键:标记状态已变更
        setChanged();
        // 通知所有观察者(可传额外参数)
        notifyObservers(this);
    }

    // getter方法
    public String getId() { return id; }
    public String getStatus() { return status; }
    public String getPhone() { return phone; }
}

// 2. 观察者:实现Observer接口
public class JdkInventoryObserver implements Observer {
    @Override
    public void update(Observable o, Object arg) {
        JdkOrder order = (JdkOrder) arg;
        if ("PAID".equals(order.getStatus())) {
            System.out.println("JDK库存系统:订单" + order.getId() + "已支付,扣减库存");
        }
    }
}

// 使用方式
public static void main(String[] args) {
    JdkOrder order = new JdkOrder("67890", "13900139000");
    // 添加观察者
    order.addObserver(new JdkInventoryObserver());
    // 变更状态(触发通知)
    order.setStatus("PAID");
}
  • 注意:JDK的Observable是类(不是接口),被观察者必须继承它,这限制了类的继承关系(Java单继承),灵活性较低,项目中用得不多。

(3)Guava EventBus(项目推荐)

Google的Guava框架提供了EventBus,可简化观察者模式的实现,无需手动管理观察者列表和通知逻辑,支持同步/异步通知:

// 1. 引入Guava依赖(Maven)
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>32.1.3-jre</version>
</dependency>

// 2. 定义事件(订单状态变更事件)
public class OrderStatusEvent {
    private String orderId;
    private String status;
    private String phone;

    public OrderStatusEvent(String orderId, String status, String phone) {
        this.orderId = orderId;
        this.status = status;
        this.phone = phone;
    }

    // getter方法
    public String getOrderId() { return orderId; }
    public String getStatus() { return status; }
    public String getPhone() { return phone; }
}

// 3. 定义观察者(用@Subscribe注解标记通知方法)
public class GuavaInventoryObserver {
    // 订阅OrderStatusEvent事件
    @Subscribe
    public void onOrderStatusChanged(OrderStatusEvent event) {
        if ("PAID".equals(event.getStatus())) {
            System.out.println("Guava库存系统:订单" + event.getOrderId() + "已支付,扣减库存");
        }
    }
}

public class GuavaSmsObserver {
    @Subscribe
    public void onOrderStatusChanged(OrderStatusEvent event) {
        System.out.println("Guava短信系统:向用户" + event.getPhone() + "发送通知:订单" + event.getOrderId() + "状态变更为" + event.getStatus());
    }
}

// 4. 使用EventBus
public static void main(String[] args) {
    // 1. 创建EventBus(同步)或AsyncEventBus(异步)
    EventBus eventBus = new EventBus();
    // 异步版:需要传入线程池
    // EventBus eventBus = new AsyncEventBus(Executors.newFixedThreadPool(5));

    // 2. 注册观察者(订阅事件)
    eventBus.register(new GuavaInventoryObserver());
    eventBus.register(new GuavaSmsObserver());

    // 3. 发布事件(触发通知)
    System.out.println("=== 发布支付事件 ===");
    eventBus.post(new OrderStatusEvent("111222", "PAID", "13800138000"));
}
  • 优点:代码简洁(无需定义观察者接口、管理列表),支持异步通知(解决观察者处理慢导致的阻塞问题),项目中强烈推荐使用。

4.3 项目踩坑指南

  1. 避免循环通知:如果观察者A的更新逻辑会触发被观察者再次发布事件,可能导致“观察者A→被观察者→观察者A”的循环,最终栈溢出——解决方案:在通知方法中加“防止重入”判断(如用boolean isProcessing标记)。
  2. 异步通知的线程安全:使用AsyncEventBus时,观察者的处理逻辑要保证线程安全(如避免修改共享变量,或加锁保护)。
  3. 观察者的优先级:默认情况下,观察者的通知顺序不确定,如果需要按顺序通知(如先扣库存再发短信),可自定义EventBus的事件分发器,或拆分事件(如OrderPaidEvent和OrderNotifyEvent)。

五、装饰器模式:动态扩展功能的“积木”

5.1 核心概念

装饰器模式的本质是在不改变原有对象结构的前提下,动态给对象添加新功能——它像“积木”一样,通过嵌套包装的方式组合功能(如“咖啡+牛奶+糖”)。它解决的核心问题是“静态继承的局限性”——如果用继承实现“咖啡加奶”“咖啡加糖”“咖啡加奶加糖”,需要创建3个子类,而装饰器模式只需2个装饰器(牛奶、糖),可灵活组合。

5.2 实战案例:咖啡订单系统

以咖啡配料扩展为例,支持给基础咖啡添加牛奶、糖、巧克力,每种配料对应一个装饰器:

(1)定义核心接口(被装饰的对象规范)

// 咖啡接口(定义咖啡的核心能力)
public interface Coffee {
    // 获取咖啡描述(如“简单咖啡+牛奶”)
    String getDescription();
    // 获取价格(基础咖啡+配料价格)
    double getPrice();
}

(2)基础实现类(被装饰的原始对象)

// 简单咖啡(无任何配料,基础对象)
public class SimpleCoffee implements Coffee {
    @Override
    public String getDescription() {
        return "简单咖啡";
    }

    @Override
    public double getPrice() {
        return 10.0; // 基础价格10元
    }
}

(3)装饰器基类(核心:持有被装饰对象)

装饰器基类必须实现Coffee接口(保证与被装饰对象的接口一致),并持有一个Coffee对象(被装饰的对象,可能是原始对象或已被装饰的对象):

// 咖啡装饰器基类(抽象类)
public abstract class CoffeeDecorator implements Coffee {
    // 持有被装饰的咖啡对象(核心)
    protected Coffee decoratedCoffee;

    // 构造方法注入被装饰对象
    public CoffeeDecorator(Coffee decoratedCoffee) {
        this.decoratedCoffee = decoratedCoffee;
    }

    // 默认实现:委托给被装饰对象(子类可重写)
    @Override
    public String getDescription() {
        return decoratedCoffee.getDescription();
    }

    @Override
    public double getPrice() {
        return decoratedCoffee.getPrice();
    }
}

(4)具体装饰器(添加具体功能)

每个配料对应一个装饰器,重写getDescription()和getPrice(),添加自己的功能:

// 1. 牛奶装饰器
public class MilkDecorator extends CoffeeDecorator {
    public MilkDecorator(Coffee decoratedCoffee) {
        super(decoratedCoffee);
    }

    // 扩展描述:添加“+牛奶”
    @Override
    public String getDescription() {
        return decoratedCoffee.getDescription() + " + 牛奶";
    }

    // 扩展价格:加2元
    @Override
    public double getPrice() {
        return decoratedCoffee.getPrice() + 2.0;
    }
}

// 2. 糖装饰器
public class SugarDecorator extends CoffeeDecorator {
    public SugarDecorator(Coffee decoratedCoffee) {
        super(decoratedCoffee);
    }

    @Override
    public String getDescription() {
        return decoratedCoffee.getDescription() + " + 糖";
    }

    @Override
    public double getPrice() {
        return decoratedCoffee.getPrice() + 1.0;
    }
}

// 3. 巧克力装饰器
public class ChocolateDecorator extends CoffeeDecorator {
    public ChocolateDecorator(Coffee decoratedCoffee) {
        super(decoratedCoffee);
    }

    @Override
    public String getDescription() {
        return decoratedCoffee.getDescription() + " + 巧克力";
    }

    @Override
    public double getPrice() {
        return decoratedCoffee.getPrice() + 3.0;
    }
}

(5)使用方式(灵活组合装饰器)

通过嵌套包装,实现任意配料组合:

public static void main(String[] args) {
    // 1. 基础咖啡(无配料)
    Coffee coffee = new SimpleCoffee();
    System.out.println(coffee.getDescription() + ":" + coffee.getPrice() + "元");

    // 2. 咖啡+牛奶
    coffee = new MilkDecorator(coffee);
    System.out.println(coffee.getDescription() + ":" + coffee.getPrice() + "元");

    // 3. 咖啡+牛奶+糖
    coffee = new SugarDecorator(coffee);
    System.out.println(coffee.getDescription() + ":" + coffee.getPrice() + "元");

    // 4. 咖啡+牛奶+糖+巧克力
    coffee = new ChocolateDecorator(coffee);
    System.out.println(coffee.getDescription() + ":" + coffee.getPrice() + "元");
}
  • 输出结果:
    简单咖啡:10.0元
    简单咖啡 + 牛奶:12.0元
    简单咖啡 + 牛奶 + 糖:13.0元
    简单咖啡 + 牛奶 + 糖 + 巧克力:16.0元

5.3 与继承的对比(核心优势)

特性 装饰器模式 继承
功能扩展方式 动态(运行时组合) 静态(编译时确定)
类数量 少(n个功能只需n个装饰器) 多(n个功能需2^n个子类)
灵活性 高(可任意组合功能) 低(子类只能继承固定功能)
代码耦合度 低(装饰器与被装饰对象独立) 高(子类依赖父类实现)

5.4 Java IO中的装饰器模式(经典案例)

Java的IO流是装饰器模式的典型应用,比如BufferedReader包装FileReader,给“文件读取”添加“缓冲功能”:

// FileReader:基础流(被装饰对象),负责从文件读取字符
Reader reader = new FileReader("test.txt");
// BufferedReader:装饰器,给Reader添加缓冲功能(提升读取性能)
BufferedReader bufferedReader = new BufferedReader(reader);

// 同理:DataInputStream装饰InputStream,添加“读取基本数据类型”的功能
InputStream in = new FileInputStream("data.bin");
DataInputStream dataIn = new DataInputStream(in);
int num = dataIn.readInt(); // 读取int类型数据

六、适配器模式:接口兼容的“转换器”

6.1 核心概念

适配器模式的本质是将一个类的接口转换成客户端期望的另一个接口,让原本因接口不兼容而无法一起工作的类可以协同工作。它解决的核心问题是“接口不匹配”,比如项目中集成第三方SDK(接口与本地系统不一致)、旧系统改造(旧接口与新系统不兼容)。

6.2 两种实战实现(类适配器vs对象适配器)

适配器模式分为“类适配器”(用继承)和“对象适配器”(用组合),项目中推荐对象适配器(符合“合成复用原则”,避免继承的局限性)。

(1)对象适配器(推荐):用组合持有适配者

以“集成第三方支付SDK”为例,本地系统期望的支付接口是PaymentProcessor,而第三方支付宝SDK的接口是AlipaySDK(方法名、参数都不同),用适配器转换:

// 1. 本地系统的统一支付接口(客户端期望的接口)
public interface PaymentProcessor {
    // 本地接口:处理支付,返回支付ID
    String processPayment(String orderId, double amount);
    // 本地接口:查询支付状态,返回枚举
    PaymentStatus queryPayment(String paymentId);
}

// 支付状态枚举(本地系统定义)
public enum PaymentStatus {
    SUCCESS, PROCESSING, FAILED
}

// 2. 第三方SDK(适配者:接口不兼容)
public class AlipaySDK {
    // 第三方接口1:支付,参数是“订单号”“金额(分)”,返回交易号
    public String doPay(String outTradeNo, int totalAmount) {
        System.out.println("支付宝SDK:订单" + outTradeNo + "支付" + totalAmount + "分");
        return "ALI" + System.currentTimeMillis(); // 返回支付宝交易号
    }

    // 第三方接口2:查询支付状态,返回int(1=成功,0=处理中,-1=失败)
    public int getPayStatus(String tradeNo) {
        System.out.println("支付宝SDK:查询交易" + tradeNo + "状态");
        return 1; // 模拟返回成功
    }
}

// 3. 对象适配器:实现本地接口,持有第三方SDK对象
public class AlipayAdapter implements PaymentProcessor {
    // 持有适配者对象(第三方SDK)
    private AlipaySDK alipaySDK;

    // 构造方法注入适配者(灵活替换,比如测试时注入Mock对象)
    public AlipayAdapter(AlipaySDK alipaySDK) {
        this.alipaySDK = alipaySDK;
    }

    // 适配本地processPayment方法到第三方doPay方法
    @Override
    public String processPayment(String orderId, double amount) {
        // 1. 转换参数:本地金额是“元”,第三方需要“分”
        int amountInFen = (int) (amount * 100);
        // 2. 调用第三方SDK
        String tradeNo = alipaySDK.doPay(orderId, amountInFen);
        // 3. 转换返回值:返回第三方交易号(作为本地支付ID)
        return tradeNo;
    }

    // 适配本地queryPayment方法到第三方getPayStatus方法
    @Override
    public PaymentStatus queryPayment(String paymentId) {
        // 1. 调用第三方SDK
        int statusCode = alipaySDK.getPayStatus(paymentId);
        // 2. 转换返回值:第三方int→本地枚举
        switch (statusCode) {
            case 1: return PaymentStatus.SUCCESS;
            case 0: return PaymentStatus.PROCESSING;
            default: return PaymentStatus.FAILED;
        }
    }
}

// 4. 使用方式(客户端只依赖本地接口,不依赖第三方SDK)
public class OrderService {
    private PaymentProcessor paymentProcessor;

    // 注入适配器(客户端不知道第三方SDK的存在)
    public OrderService(PaymentProcessor paymentProcessor) {
        this.paymentProcessor = paymentProcessor;
    }

    public void processOrder(String orderId, double amount) {
        // 调用本地接口(适配者已处理与第三方的兼容)
        String paymentId = paymentProcessor.processPayment(orderId, amount);
        PaymentStatus status = paymentProcessor.queryPayment(paymentId);
        System.out.println("订单" + orderId + "支付状态:" + status);
    }
}

// 测试
public static void main(String[] args) {
    // 1. 创建第三方SDK对象
    AlipaySDK alipaySDK = new AlipaySDK();
    // 2. 创建适配器
    PaymentProcessor adapter = new AlipayAdapter(alipaySDK);
    // 3. 注入适配器到业务服务
    OrderService service = new OrderService(adapter);
    // 4. 处理订单(客户端无感知第三方SDK)
    service.processOrder("ORD789", 99.9);
}

(2)类适配器(了解):用继承适配

类适配器通过“继承第三方SDK”和“实现本地接口”来实现适配,缺点是无法适配多个第三方SDK(Java单继承),且耦合度高:

// 类适配器:继承AlipaySDK(适配者),实现PaymentProcessor(本地接口)
public class AlipayClassAdapter extends AlipaySDK implements PaymentProcessor {
    @Override
    public String processPayment(String orderId, double amount) {
        int amountInFen = (int) (amount * 100);
        return doPay(orderId, amountInFen); // 直接调用父类方法
    }

    @Override
    public PaymentStatus queryPayment(String paymentId) {
        int statusCode = getPayStatus(paymentId);
        // 转换逻辑...
    }
}
  • 结论:项目中尽量不用类适配器,优先选择对象适配器。

6.3 扩展:缺省适配器(解决接口方法过多问题)

当本地接口有很多方法,但实现类只需要用其中少数几个时,可提供一个“缺省适配器”(抽象类),空实现所有方法,子类只需重写需要的方法:

// 本地接口(方法很多)
public interface PaymentProcessor {
    String processPayment(String orderId, double amount);
    PaymentStatus queryPayment(String paymentId);
    void cancelPayment(String paymentId); // 取消支付(部分场景不用)
    void refundPayment(String paymentId); // 退款(部分场景不用)
}

// 缺省适配器(空实现所有方法)
public abstract class PaymentAdapter implements PaymentProcessor {
    @Override
    public String processPayment(String orderId, double amount) {
        return null; // 空实现
    }

    @Override
    public PaymentStatus queryPayment(String paymentId) {
        return null; // 空实现
    }

    @Override
    public void cancelPayment(String paymentId) {
        // 空实现
    }

    @Override
    public void refundPayment(String paymentId) {
        // 空实现
    }
}

// 子类只需重写需要的方法
public class SimplePaymentAdapter extends PaymentAdapter {
    private AlipaySDK alipaySDK;

    public SimplePaymentAdapter(AlipaySDK alipaySDK) {
        this.alipaySDK = alipaySDK;
    }

    // 只重写支付和查询方法,其他方法用默认实现
    @Override
    public String processPayment(String orderId, double amount) {
        int amountInFen = (int) (amount * 100);
        return alipaySDK.doPay(orderId, amountInFen);
    }

    @Override
    public PaymentStatus queryPayment(String paymentId) {
        int statusCode = alipaySDK.getPayStatus(paymentId);
        return statusCode == 1 ? PaymentStatus.SUCCESS : PaymentStatus.FAILED;
    }
}

七、总结:设计模式的“实用主义”原则

  1. 不要为了用模式而用模式:如果一个if-else就能解决问题(如只有2种支付方式),没必要强行用工厂模式;如果一个简单类就能满足需求,没必要写单例。
  2. 优先解决核心问题:单例解决“资源控制”,工厂解决“对象创建”,策略解决“逻辑扩展”,观察者解决“模块解耦”,装饰器解决“功能组合”,适配器解决“接口兼容”——根据问题选模式。
  3. 结合框架简化实现:项目中尽量用成熟框架(如Guava EventBus、Spring)简化模式实现,避免重复造轮子(如用Spring的Bean管理替代手动写单例,用EventBus替代手动写观察者)。

设计模式的最终目标是“让代码更易维护、更易扩展”,而不是“炫技”。当你在项目中遇到“改一处代码要动多个地方”“新增功能要改大量旧代码”时,不妨想想这6个模式,或许能找到更优雅的解决方案。

除非注明,否则均为李锋镝的博客原创文章,转载必须以链接形式标明本文链接

本文链接:https://www.lifengdi.com/hou-duan/4527

相关文章

  • Java设计模式:状态模式
  • Java设计模式:策略模式
  • Java设计模式:模板方法模式
  • JAVA设计模式-抽象工厂模式
  • JAVA设计模式-工厂方法模式
本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可
标签: JAVA 设计模式
最后更新:2025年10月16日

李锋镝

既然选择了远方,便只顾风雨兼程。

打赏 点赞
< 上一篇
下一篇 >
1234567891112131415161718192021222324252627282930313233343536373839404142434446474849505152535455575859606162636465666769727476777879808182858687909293949596979899
取消回复
…

文章评论

秋天是倒放的春天,晚安是爱你的序篇。

那年今日(07月03日)

  • 2005年:西班牙正式通过同性婚姻相关法案
  • 1969年:英国滚石乐队的创始团员布莱恩·琼斯去世
  • 1876年:中国第一条铁路淞沪铁路正式通车运营
  • 1518年:中国明代医学家兼药物学家李时珍出生
  • 1062年:中国北宋官员包拯去世
  • 更多历史事件
最新 热点 随机
最新 热点 随机
Kratos+主题新功能预览及功能演示 SpringBoot DeferredLog 完整详解 LiteLLM 本地代理搭建 Claude-HUD 使用文档 Kratos+ —— Kratos 主题二次开发记录 译文:如何将单体应用拆解为微服务
AI时代,个人技术博客的出路在哪里?这个域名注册整整十年了,十年时间,真快啊WordPress实现用户评论等级排行榜插件WordPress网站换了个字体,差点儿把样式换崩了做了一个WordPress文章热力图插件千万级大表新增字段实战指南:告别锁表与业务中断
Auld Lang Syne 缓存架构实战指南:6大核心缓存技术深度解析与落地方案 阿里巴巴的26款超神Java开源项目 Claude Code全维度实战指南:从入门到精通,解锁AI编程新范式 岁末 海琴烟~~~
最近评论
李锋镝 发布于 4 天前(06月30日) 目前是每天一换,一个星期不重样 :41:
不凡 发布于 4 天前(06月29日) 主题配色挺好看。 :2:
李锋镝 发布于 5 天前(06月29日) 已经更新了~
懋和道人 发布于 5 天前(06月29日) 境外与附件不能访问,其他都是正常的,如果不正常可以通过更换ip访问。
李锋镝 发布于 5 天前(06月29日) 403呀道长
标签聚合
SQL 多线程 日常 IDEA 数据库 ElasticSearch Spring AI编程 JVM JAVA SpringBoot MCP 分布式 MySQL docker AI 架构 K8s Redis WordPress
友情链接
  • Blogs·CN
  • Honesty
  • Mr.Sun的博客
  • 临窗旋墨
  • 哥斯拉
  • 彬红茶日记
  • 志文工作室
  • 懋和道人
  • 拾趣博客导航
  • 搬砖日记
  • 旧时繁华
  • 林羽凡
  • 瓦匠个人小站
  • 皮皮社
  • 知向前端
  • 蜗牛工作室
  • 韩小韩博客
  • 风渡言

COPYRIGHT © 2026 lifengdi.com. ALL RIGHTS RESERVED.

域名年龄

Theme Kratos+ By Dylan Li

津ICP备2024022503号-3

京公网安备11011502039375号