醋醋百科网

Good Luck To You!

一文说透:7 种 Java 设计模式实战,少走 3 年弯路

在 Java 开发圈,设计模式常被贴上 “抽象”“无用” 的标签 —— 很多人背完 UML 图,遇到真实业务场景还是手忙脚乱,写出来的代码满是 if-else、模块耦合严重,改一个功能要动五六个类。其实设计模式不是 “炫技工具”,而是解决高频问题的 “实战手册”。今天结合电商、支付、文件处理等真实场景,拆解 7 种最常用的设计模式,带你从 “懂理论” 到 “会落地”,写出让架构师点头的优雅代码。

一、单例模式:全局唯一实例,避免资源浪费

核心场景:配置管理器、线程池、数据库连接池等需要 “全局唯一” 的组件。若重复创建实例,会导致配置冲突、内存溢出(比如多线程创建连接池,可能引发数据库连接耗尽)。

实战案例:电商项目的ConfigManager(配置管理器),需加载数据库地址、Redis 密码等全局配置,确保全服务只有一份配置实例。

推荐实现:枚举单例(《Effective Java》最优方案)

java

public enum ConfigManager {
    INSTANCE; // 全局唯一实例

    private Properties config;

    // 初始化:项目启动时加载配置文件(仅执行一次)
    ConfigManager() {
        config = new Properties();
        try {
            config.load(ConfigManager.class.getClassLoader()
                    .getResourceAsStream("app.properties"));
        } catch (IOException e) {
            throw new RuntimeException("配置加载失败,服务启动异常", e);
        }
    }

    // 对外提供配置获取接口
    public String getConfig(String key) {
        String value = config.getProperty(key);
        if (value == null) {
            throw new IllegalArgumentException("不存在的配置项:" + key);
        }
        return value;
    }
}

// 业务层调用(全服务统一入口,无重复创建)
public class OrderDao {
    public void connectDb() {
        // 直接获取单例实例,无需new
        String dbUrl = ConfigManager.INSTANCE.getConfig("db.mysql.url");
        String dbUser = ConfigManager.INSTANCE.getConfig("db.mysql.user");
        // 连接数据库逻辑...
    }
}

避坑点

  • 别用 “懒汉式(无锁)”:多线程下会创建多个实例,线程不安全;
  • 别用 “静态内部类”:虽能懒加载,但无法抵御反射攻击(可通过反射强制创建实例);
  • 枚举单例天然线程安全、防反射,且代码极简,是生产环境首选。

二、工厂模式:解耦对象创建,应对需求多变

核心场景:当对象创建逻辑复杂(需初始化密钥、校验参数),或需根据条件创建不同子类(如支付方式、日志类型),用工厂统一管理创建过程,避免业务层与具体实现类耦合。

实战案例:支付系统支持支付宝、微信支付、银联支付,每种支付的初始化逻辑不同(支付宝需加载公钥,微信需加载证书),用工厂屏蔽差异。

java

// 1. 定义支付接口(统一规范)
public interface Payment {
    void pay(BigDecimal amount, String orderId);
}

// 2. 实现具体支付方式(封装各自初始化逻辑)
public class Alipay implements Payment {
    // 初始化:加载支付宝公钥(仅创建时执行一次)
    public Alipay() {
        System.out.println("加载支付宝商户公钥...");
    }

    @Override
    public void pay(BigDecimal amount, String orderId) {
        System.out.println("支付宝支付:订单" + orderId + ",金额" + amount + "元");
    }
}

public class WxPay implements Payment {
    public WxPay() {
        System.out.println("加载微信支付API证书...");
    }

    @Override
    public void pay(BigDecimal amount, String orderId) {
        System.out.println("微信支付:订单" + orderId + ",金额" + amount + "元");
    }
}

// 3. 支付工厂(核心:统一创建入口)
public class PaymentFactory {
    // 根据支付类型创建实例,业务层无需关心细节
    public static Payment getPayment(String payType) {
        return switch (payType) {
            case "ALIPAY" -> new Alipay();
            case "WXPAY" -> new WxPay();
            case "UNIONPAY" -> new UnionPay(); // 新增支付方式只需加case
            default -> throw new IllegalArgumentException("不支持的支付类型:" + payType);
        };
    }
}

// 4. 业务层调用(只认接口,不认实现)
public class OrderService {
    public void doPay(String orderId, BigDecimal amount, String payType) {
        // 直接从工厂拿实例,无需new Alipay()/new WxPay()
        Payment payment = PaymentFactory.getPayment(payType);
        payment.pay(amount, orderId);
    }
}

优势:新增 “银联支付” 时,只需加UnionPay类和工厂的 case,业务层代码零修改,完美符合 “开闭原则”。

三、策略模式:干掉复杂 if-else,让逻辑更灵活

核心场景:当有多个 “算法 / 逻辑分支” 且频繁变动(如会员折扣、物流计费),用策略模式替代 if-else,降低代码耦合,方便扩展。

反例痛点:会员折扣计算的 “if-else 爆炸” 代码,新增会员类型需改老逻辑:

java

// 糟糕代码:新增“钻石会员”要加else if,违反开闭原则
public BigDecimal calculateDiscount(String memberLevel, BigDecimal amount) {
    if ("普通会员".equals(memberLevel)) {
        return amount.multiply(new BigDecimal("0.95"));
    } else if ("银卡会员".equals(memberLevel)) {
        return amount.multiply(new BigDecimal("0.9"));
    } else if ("金卡会员".equals(memberLevel)) {
        return amount.multiply(new BigDecimal("0.8"));
    } else {
        throw new IllegalArgumentException("未知会员等级");
    }
}

实战重构:策略模式落地会员折扣

java

// 1. 定义策略接口(统一折扣算法规范)
public interface DiscountStrategy {
    BigDecimal calculate(BigDecimal amount);
}

// 2. 实现具体策略(每种会员对应一个策略类)
public class NormalMemberStrategy implements DiscountStrategy {
    @Override
    public BigDecimal calculate(BigDecimal amount) {
        return amount.multiply(new BigDecimal("0.95")); // 普通会员95折
    }
}

public class SilverMemberStrategy implements DiscountStrategy {
    @Override
    public BigDecimal calculate(BigDecimal amount) {
        return amount.multiply(new BigDecimal("0.9")); // 银卡9折
    }
}

public class GoldMemberStrategy implements DiscountStrategy {
    @Override
    public BigDecimal calculate(BigDecimal amount) {
        return amount.multiply(new BigDecimal("0.8")); // 金卡8折
    }
}

// 3. 策略上下文(管理策略,提供对外接口)
public class DiscountContext {
    private DiscountStrategy strategy;

    // 注入具体策略
    public DiscountContext(DiscountStrategy strategy) {
        this.strategy = strategy;
    }

    // 调用策略计算折扣
    public BigDecimal getFinalAmount(BigDecimal amount) {
        return strategy.calculate(amount);
    }
}

// 4. 业务层调用(无if-else,直接选策略)
public class MemberService {
    public BigDecimal getPayAmount(String memberLevel, BigDecimal amount) {
        // 根据会员等级选择策略(可结合工厂模式优化)
        DiscountStrategy strategy = switch (memberLevel) {
            case "普通会员" -> new NormalMemberStrategy();
            case "银卡会员" -> new SilverMemberStrategy();
            case "金卡会员" -> new GoldMemberStrategy();
            default -> throw new IllegalArgumentException("未知会员等级");
        };
        // 计算最终金额
        DiscountContext context = new DiscountContext(strategy);
        return context.getFinalAmount(amount);
    }
}

优势:新增 “钻石会员” 时,只需加DiamondMemberStrategy类,无需修改MemberService,代码扩展性拉满。

四、观察者模式:解耦模块依赖,实现自动通知

核心场景:当一个对象(被观察者)状态变化时,需自动通知多个依赖它的对象(观察者),且模块间不能强耦合。比如 “订单支付后,需通知库存扣减、物流创建、积分增加”—— 硬编码调用会导致订单模块与其他模块紧耦合。

实战案例:订单状态变更通知系统

java

// 1. 定义核心接口(规范观察者与被观察者行为)
public interface Observer {
    // 接收通知:订单ID+新状态
    void update(String orderId, OrderStatus newStatus);
}

public interface Subject {
    void addObserver(Observer observer); // 注册观察者
    void removeObserver(Observer observer); // 移除观察者
    void notifyObservers(String orderId, OrderStatus newStatus); // 通知所有观察者
}

// 订单状态枚举(标准化状态)
public enum OrderStatus {
    PENDING_PAY("待支付"), PAID("已支付"), SHIPPED("已发货");
    private final String desc;
    OrderStatus(String desc) { this.desc = desc; }
    public String getDesc() { return desc; }
}

// 2. 实现被观察者(订单主题)
public class OrderSubject implements Subject {
    // 线程安全的观察者列表
    private final List<Observer> observers = Collections.synchronizedList(new ArrayList<>());

    // 订单状态变更(业务核心)
    public void changeStatus(String orderId, OrderStatus newStatus) {
        System.out.println("订单[" + orderId + "]状态变更为:" + newStatus.getDesc());
        // 状态变更多后,自动通知所有观察者
        notifyObservers(orderId, newStatus);
    }

    @Override
    public void addObserver(Observer observer) {
        if (!observers.contains(observer)) {
            observers.add(observer);
        }
    }

    @Override
    public void removeObserver(Observer observer) {
        observers.remove(observer);
    }

    @Override
    public void notifyObservers(String orderId, OrderStatus newStatus) {
        // 实战建议:用线程池异步执行,避免阻塞订单流程
        for (Observer observer : observers) {
            observer.update(orderId, newStatus);
        }
    }
}

// 3. 实现观察者(各依赖系统)
public class InventoryObserver implements Observer {
    @Override
    public void update(String orderId, OrderStatus newStatus) {
        // 只关注“已支付”状态
        if (OrderStatus.PAID.equals(newStatus)) {
            System.out.println("库存系统:订单[" + orderId + "]已支付,扣减库存");
        }
    }
}

public class LogisticsObserver implements Observer {
    @Override
    public void update(String orderId, OrderStatus newStatus) {
        if (OrderStatus.PAID.equals(newStatus)) {
            System.out.println("物流系统:订单[" + orderId + "]已支付,创建物流单");
        }
    }
}

// 4. 业务调用(模拟订单支付)
public class OrderTest {
    public static void main(String[] args) {
        // 创建订单主题
        OrderSubject orderSubject = new OrderSubject();
        // 注册观察者(需要通知的系统)
        orderSubject.addObserver(new InventoryObserver());
        orderSubject.addObserver(new LogisticsObserver());
        // 模拟支付成功
        orderSubject.changeStatus("O20240901001", OrderStatus.PAID);
    }
}

输出结果(自动通知)

订单[O20240901001]状态变更为:已支付
库存系统:订单[O20240901001]已支付,扣减库存
物流系统:订单[O20240901001]已支付,创建物流单

避坑点

  • 异步执行:观察者逻辑若耗时(如调用第三方接口),同步通知会阻塞订单流程,需用线程池异步处理;
  • 避免内存泄漏:观察者下线时需调用removeObserver,防止被观察者持有无用引用。

五、装饰器模式:动态扩展功能,替代多层继承

核心场景:需给对象 “动态加功能”,且避免继承导致的 “类爆炸”(如 IO 流的缓冲、加密功能,咖啡的牛奶、糖浆调味)。

实战案例:咖啡订单系统(基础咖啡 + 动态加配料)

java

// 1. 定义核心接口(咖啡的统一规范)
public interface Beverage {
    String getDesc(); // 描述(如“基础咖啡+牛奶”)
    double getCost(); // 价格
}

// 2. 实现基础咖啡(被装饰的核心对象)
public class BasicCoffee implements Beverage {
    @Override
    public String getDesc() {
        return "基础咖啡";
    }

    @Override
    public double getCost() {
        return 15.0; // 基础价15元
    }
}

// 3. 定义装饰器抽象类(持有被装饰对象,实现统一接口)
public abstract class BeverageDecorator implements Beverage {
    protected final Beverage beverage; // 被装饰的咖啡

    // 注入被装饰对象
    public BeverageDecorator(Beverage beverage) {
        this.beverage = beverage;
    }

    // 抽象方法:子类实现具体装饰逻辑
    @Override
    public abstract String getDesc();

    @Override
    public abstract double getCost();
}

// 4. 实现具体装饰器(牛奶、糖浆)
public class MilkDecorator extends BeverageDecorator {
    public MilkDecorator(Beverage beverage) {
        super(beverage);
    }

    @Override
    public String getDesc() {
        return beverage.getDesc() + " + 牛奶"; // 追加描述
    }

    @Override
    public double getCost() {
        return beverage.getCost() + 3.0; // 追加价格(牛奶3元)
    }
}

public class SyrupDecorator extends BeverageDecorator {
    public SyrupDecorator(Beverage beverage) {
        super(beverage);
    }

    @Override
    public String getDesc() {
        return beverage.getDesc() + " + 糖浆";
    }

    @Override
    public double getCost() {
        return beverage.getCost() + 2.0; // 糖浆2元
    }
}

// 5. 业务调用(动态加装饰)
public class CoffeeShop {
    public static void main(String[] args) {
        // 基础咖啡
        Beverage coffee = new BasicCoffee();
        System.out.println(coffee.getDesc() + ":" + coffee.getCost() + "元");

        // 加牛奶
        coffee = new MilkDecorator(coffee);
        // 再加糖浆
        coffee = new SyrupDecorator(coffee);
        System.out.println(coffee.getDesc() + ":" + coffee.getCost() + "元");
    }
}

输出结果

基础咖啡:15.0元
基础咖啡 + 牛奶 + 糖浆:20.0元

优势:新增 “奶油” 配料时,只需加CreamDecorator类,无需修改原有代码,比继承更灵活(若用继承,需创建 “咖啡 + 牛奶”“咖啡 + 糖浆”“咖啡 + 牛奶 + 糖浆” 等 N 个类)。

六、适配器模式:解决接口不兼容,做 “中间桥梁”

核心场景:当需要使用现有类,但它的接口与需求不匹配(如老系统接口、第三方库接口),用适配器做格式转换,避免修改原有代码。

实战案例:新系统调用老物流接口(老接口返回 Map,新系统需 DTO)

java

// 1. 老系统接口(无法修改,返回Map)
public class OldLogisticsService {
    // 老接口:返回非标准化Map
    public Map<String, Object> queryLogistics(String orderId) {
        Map<String, Object> map = new HashMap<>();
        map.put("order_no", orderId); // 订单号(key是order_no)
        map.put("status", "已发货");   // 物流状态
        map.put("express_name", "顺丰"); // 快递公司
        return map;
    }
}

// 2. 新系统接口(标准化DTO)
public interface NewLogisticsService {
    LogisticsDTO getLogistics(String orderId); // 返回DTO
}

// 新系统的标准化DTO
public class LogisticsDTO {
    private String orderId;    // 订单号(新字段名)
    private String status;     // 物流状态
    private String expressName;// 快递公司
    // getter/setter省略
}

// 3. 适配器(实现新接口,内部调用老接口)
public class LogisticsAdapter extends OldLogisticsService implements NewLogisticsService {
    @Override
    public LogisticsDTO getLogistics(String orderId) {
        // 1. 调用老接口
        Map<String, Object> oldData = super.queryLogistics(orderId);

        // 2. 格式转换:Map → DTO
        LogisticsDTO dto = new LogisticsDTO();
        dto.setOrderId((String) oldData.get("order_no")); // 映射老key
        dto.setStatus((String) oldData.get("status"));
        dto.setExpressName((String) oldData.get("express_name"));

        return dto;
    }
}

// 4. 新系统调用(只认新接口)
public class OrderQueryService {
    public void showLogistics(String orderId) {
        NewLogisticsService service = new LogisticsAdapter();
        LogisticsDTO dto = service.getLogistics(orderId);
        // 用DTO做后续处理(如返回给前端)
        System.out.println("订单" + dto.getOrderId() + ":" + dto.getExpressName() + "," + dto.getStatus());
    }
}

常见应用:Java 的InputStreamReader(将字节流InputStream适配为字符流Reader)、SpringMVC 的HandlerAdapter(适配不同处理器),都是适配器模式的经典落地。

七、模板方法模式:固定流程骨架,灵活替换步骤

核心场景:当多个业务有 “统一流程” 但 “部分步骤不同”(如文件导入、审批流程),用模板方法固定流程,将可变步骤延迟到子类实现。

实战案例:多格式文件导入(CSV/Excel,流程统一:读取→解析→保存)

java

// 1. 抽象模板类(固定流程骨架)
public abstract class FileImportTemplate {
    // 模板方法:用final修饰,防止子类修改流程
    public final void importFile(String filePath) {
        try {
            // 步骤1:读取文件(可变步骤,子类实现)
            String content = readFile(filePath);
            
            // 步骤2:解析内容(可变步骤,子类实现)
            List<User> userList = parseContent(content);
            
            // 步骤3:保存数据(固定步骤,所有子类共用)
            saveToDb(userList);
            
            // 步骤4:通知结果(固定步骤)
            notifySuccess(filePath);
        } catch (Exception e) {
            notifyFail(filePath, e.getMessage());
            throw new RuntimeException("文件导入失败", e);
        }
    }

    // 抽象方法:可变步骤1——读取文件
    protected abstract String readFile(String filePath);

    // 抽象方法:可变步骤2——解析内容
    protected abstract List<User> parseContent(String content);

    // 固定步骤1:保存数据库(共用逻辑)
    private void saveToDb(List<User> userList) {
        System.out.println("批量保存" + userList.size() + "条用户数据");
        // 实际项目中调用DAO批量插入
    }

    // 固定步骤2:通知成功
    private void notifySuccess(String filePath) {
        System.out.println("文件[" + filePath + "]导入成功");
    }

    // 固定步骤3:通知失败
    private void notifyFail(String filePath, String errorMsg) {
        System.out.println("文件[" + filePath + "]导入失败:" + errorMsg);
    }
}

// 用户实体类(解析后的数据格式)
public class User {
    private String id;
    private String name;
    // getter/setter省略
}

// 2. 实现具体子类(CSV导入)
public class CsvImport extends FileImportTemplate {
    @Override
    protected String readFile(String filePath) {
        System.out.println("读取CSV文件:" + filePath);
        return "1,张三\n2,李四"; // 模拟CSV内容
    }

    @Override
    protected List<User> parseContent(String content) {
        System.out.println("解析CSV内容");
        List<User> userList = new ArrayList<>();
        String[] lines = content.split("\n");
        for (String line : lines) {
            String[] cols = line.split(",");
            User user = new User();
            user.setId(cols[0]);
            user.setName(cols[1]);
            userList.add(user);
        }
        return userList;
    }
}

// 3. 实现具体子类(Excel导入)
public class ExcelImport extends FileImportTemplate {
    @Override
    protected String readFile(String filePath) {
        System.out.println("读取Excel文件:" + filePath);
        return "模拟Excel二进制内容"; // 实际用EasyExcel/POI读取
    }

    @Override
    protected List<User> parseContent(String content) {
        System.out.println("解析Excel内容");
        List<User> userList = new ArrayList<>();
        // 模拟解析出2条数据
        User user1 = new User();
        user1.setId("3");
        user1.setName("王五");
        userList.add(user1);
        return userList;
    }
}

// 4. 业务调用(统一入口)
public class ImportService {
    public static void main(String[] args) {
        FileImportTemplate csvImport = new CsvImport();
        csvImport.importFile("user.csv");

        FileImportTemplate excelImport = new ExcelImport();
        excelImport.importFile("user.xlsx");
    }
}

优势:新增 “JSON 导入” 时,只需加JsonImport子类,无需修改模板类,流程统一且灵活。

总结:设计模式的本质是 “解决问题”,不是 “背公式”

很多人学设计模式走了弯路 —— 死记 UML 图、纠结 “这个场景该用哪个模式”,却忽略了核心:设计模式是 “前人解决问题的经验总结”,落地时要围绕三个原则:

  1. 单一职责:每个类只做一件事(如订单主题只管状态和通知,不管库存怎么扣);
  2. 开闭原则:新增功能靠扩展,不修改老代码(如新增支付方式只加类,不改工厂);
  3. 组合优于继承:优先用 “组合”(如策略上下文持有策略对象),避免继承的耦合问题。

下次写代码时,若遇到 “改一处牵全身”“if-else 超过 3 层”“新功能加不进去”,不妨停下来想:这场景能用哪个模式解决?坚持用实战落地,你会发现代码质量和开发效率会有质的提升 —— 这才是从 “CRUD 工程师” 到 “架构思维开发者” 的关键一步。

编辑分享

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言