在 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 图、纠结 “这个场景该用哪个模式”,却忽略了核心:设计模式是 “前人解决问题的经验总结”,落地时要围绕三个原则:
- 单一职责:每个类只做一件事(如订单主题只管状态和通知,不管库存怎么扣);
- 开闭原则:新增功能靠扩展,不修改老代码(如新增支付方式只加类,不改工厂);
- 组合优于继承:优先用 “组合”(如策略上下文持有策略对象),避免继承的耦合问题。
下次写代码时,若遇到 “改一处牵全身”“if-else 超过 3 层”“新功能加不进去”,不妨停下来想:这场景能用哪个模式解决?坚持用实战落地,你会发现代码质量和开发效率会有质的提升 —— 这才是从 “CRUD 工程师” 到 “架构思维开发者” 的关键一步。
编辑分享