news 2026/10/13 18:12:20

服装管理系统实战:用状态/策略/观察者模式解决库存与定价难题

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
服装管理系统实战:用状态/策略/观察者模式解决库存与定价难题

简介:本资源是面向软件工程专业本科生及设计模式初学者的课程大作业实践项目,聚焦服装管理系统的架构设计与模式落地,帮助学习者深入理解设计模式在真实业务场景中的选型依据与实现细节。压缩包共39个文件,含17个Java源码(覆盖MVC分层、单例/工厂/策略等核心模式实现)、14张PNG界面与类图(直观展示系统交互逻辑与结构设计)、4个XML配置文件(支撑Spring或GUI布局)、1份PPTX项目演示文稿(含需求分析、架构对比与设计决策说明)及1份DOC软件设计说明书(含数据库ER图、模块功能表与测试用例),整体2.4MB,结构完整、开箱即学。已有5459人学习下载,提供从UML建模、代码实现、文档撰写到汇报展示的全流程参考,特别适合课程设计复现、毕业设计选题拓展及设计模式综合应用能力提升。

1. 为什么一个“服装管理系统”大作业,能逼出学生对设计模式和架构的全部认知盲区?

这不是一个简单的 CRUD 管理系统——它是一面镜子,照出你是否真能把《软件设计模式与体系结构》课本里的抽象概念,焊进真实业务逻辑里。某高校期末大作业要求:用 Java(或 C#)实现一个支持多角色(管理员、店员、顾客)、多状态(上架/下架/售罄/调拨)、多维度分类(品牌+季节+尺码+风格)的服装库存与销售协同系统。表面看是增删改查,但一旦开始处理“同一款连衣裙在A店有S/M/L,在B店只有M/XL,且B店刚申请调拨2件到A店”这类场景,单靠 if-else 或硬编码枚举就立刻崩盘。学生普遍卡在三个致命节点:状态流转混乱(比如“售罄”后还能被下单?谁来校验?)、扩展成本爆炸(新增“会员专属折扣”要改7个类)、测试覆盖无力(改一个价格策略,整个结算模块全红)。这恰恰是设计模式与体系结构课程最核心的交付目标:让你在代码还没写完时,就预判出哪里会“长肿瘤”。本文不讲 UML 图怎么画,只带你用真实可运行的代码结构,把工厂模式管创建、策略模式管折扣、状态模式管库存生命周期、观察者模式管库存变更通知——一条线串起来,跑通从登录到生成销售单的最小闭环。适合正在赶工、想少走弯路,或已提交初版但被导师批“架构感缺失”的同学。

2. 用状态模式重构库存生命周期:让“上架→售罄→下架”不再靠 if-else 硬扛

服装库存的状态不是静态标签,而是一套有严格流转规则的生命周期。硬编码判断if (status == "sold_out") { ... } else if (status == "discontinued") { ... }会导致两个问题:一是新增状态(如“临时冻结”)要全局搜索修改所有条件分支;二是状态合法性校验散落在各处,容易出现“售罄商品还能被加购”的逻辑漏洞。状态模式的核心思想是:把每个状态封装成独立类,由上下文(Context)委托给当前状态对象处理行为,并决定下一个状态。

2.1 定义状态接口与具体状态类

// 库存状态接口:所有状态必须实现的行为契约 public interface InventoryState { // 处理“上架”操作(仅部分状态允许) void handleList(InventoryContext context); // 处理“售出”操作(仅部分状态允许) void handleSell(InventoryContext context); // 处理“下架”操作(仅部分状态允许) void handleUnlist(InventoryContext context); // 获取状态名称(用于日志和前端展示) String getStateName(); } // 上架状态:可售,可售出,可下架 public class ListedState implements InventoryState { @Override public void handleList(InventoryContext context) { // 已上架,无需重复操作 System.out.println("警告:商品已处于上架状态"); } @Override public void handleSell(InventoryContext context) { context.decreaseStock(1); // 扣减1件库存 if (context.getStock() <= 0) { context.setState(new SoldOutState()); // 自动转入售罄 } } @Override public void handleUnlist(InventoryContext context) { context.setState(new UnlistedState()); // 主动下架 } @Override public String getStateName() { return "上架"; } } // 售罄状态:不可售出,可重新上架,不可下架(需先上架再下架) public class SoldOutState implements InventoryState { @Override public void handleList(InventoryContext context) { context.setState(new ListedState()); // 补货后重新上架 } @Override public void handleSell(InventoryContext context) { throw new IllegalStateException("错误:售罄商品无法售出"); } @Override public void handleUnlist(InventoryContext context) { throw new IllegalStateException("错误:售罄商品不能直接下架,请先上架"); } @Override public String getStateName() { return "售罄"; } } // 下架状态:不可售出,不可上架(需管理员审核),可重新上架 public class UnlistedState implements InventoryState { @Override public void handleList(InventoryContext context) { // 下架商品重新上架需触发审核流程(模拟) System.out.println("提示:下架商品重新上架需管理员审核,已提交申请"); context.setState(new PendingReviewState()); } @Override public void handleSell(InventoryContext context) { throw new IllegalStateException("错误:下架商品无法售出"); } @Override public void handleUnlist(InventoryContext context) { // 已下架,无需操作 System.out.println("警告:商品已处于下架状态"); } @Override public String getStateName() { return "下架"; } }

逻辑说明:InventoryState接口定义了所有状态必须响应的公共行为,但每个实现类只负责自己状态下的合法操作。例如SoldOutState.handleSell()直接抛异常,而非返回错误码——这强制调用方必须处理状态不合法的场景,避免静默失败。handleSell()中库存扣减后自动切换状态,体现了状态流转的内聚性。

2.2 构建上下文类并集成状态机

// 库存上下文:持有当前状态引用和业务数据 public class InventoryContext { private InventoryState state; private String itemId; private int stock; // 当前库存数量 private String location; // 所属门店ID public InventoryContext(String itemId, int initialStock, String location) { this.itemId = itemId; this.stock = initialStock; this.location = location; // 初始状态为上架(新入库商品默认可售) this.state = new ListedState(); } // 委托给当前状态处理行为 public void list() { state.handleList(this); } public void sell() { state.handleSell(this); } public void unlist() { state.handleUnlist(this); } // 状态变更入口(仅状态类内部可调用) public void setState(InventoryState newState) { this.state = newState; System.out.println("【状态变更】" + itemId + "@" + location + " → " + newState.getStateName()); } // 辅助方法:扣减库存(供状态类内部调用) public void decreaseStock(int quantity) { this.stock -= quantity; System.out.println("【库存扣减】" + itemId + "@" + location + " -" + quantity + ",剩余:" + this.stock); } // Getter 方法(供外部查询) public String getStateName() { return state.getStateName(); } public int getStock() { return stock; } public String getItemId() { return itemId; } }

参数说明:InventoryContext是状态模式的“舞台”,它不关心状态如何变化,只负责持有数据和转发请求。setState()方法设为public是为了状态类能主动切换,但实际项目中可进一步封装为包级私有(default访问修饰符),防止外部误操作。decreaseStock()是典型的“上下文提供数据,状态控制逻辑”的分工体现。

2.3 在业务服务中调用状态机

// 模拟一个库存服务类 public class InventoryService { // 持有所有库存项的上下文映射(实际项目中应从数据库加载) private Map<String, InventoryContext> inventoryMap = new HashMap<>(); public InventoryService() { // 初始化示例数据:A店有3件连衣裙,B店有5件 inventoryMap.put("DRESS-001@STORE-A", new InventoryContext("DRESS-001", 3, "STORE-A")); inventoryMap.put("DRESS-001@STORE-B", new InventoryContext("DRESS-001", 5, "STORE-B")); } // 处理用户下单请求(简化版) public boolean processOrder(String itemId, String storeId) { String key = itemId + "@" + storeId; InventoryContext context = inventoryMap.get(key); if (context == null) { System.out.println("错误:未找到商品 " + itemId + " 在门店 " + storeId); return false; } try { context.sell(); // 交由状态机处理 return true; } catch (IllegalStateException e) { System.out.println("订单失败:" + e.getMessage()); return false; } } // 查询商品当前状态 public String getInventoryStatus(String itemId, String storeId) { String key = itemId + "@" + storeId; InventoryContext context = inventoryMap.get(key); return context != null ? context.getStateName() : "未知"; } } // 测试驱动:验证状态流转 public class StatePatternDemo { public static void main(String[] args) { InventoryService service = new InventoryService(); System.out.println("=== 初始状态 ==="); System.out.println("A店连衣裙状态:" + service.getInventoryStatus("DRESS-001", "STORE-A")); // 上架 System.out.println("\n=== A店售出3件 ==="); service.processOrder("DRESS-001", "STORE-A"); // 剩2 service.processOrder("DRESS-001", "STORE-A"); // 剩1 service.processOrder("DRESS-001", "STORE-A"); // 剩0 → 自动变售罄 System.out.println("\n=== 尝试在售罄状态下再售出 ==="); service.processOrder("DRESS-001", "STORE-A"); // 抛异常 System.out.println("\n=== 售罄后补货上架 ==="); InventoryContext ctx = service.getInventoryContext("DRESS-001@STORE-A"); if (ctx != null) { ctx.list(); // 重新上架 } } }

关键点:processOrder()方法完全不关心状态细节,只调用context.sell()。状态流转逻辑被彻底隔离在状态类内部。当需要新增“临时冻结”状态时,只需新增FrozenState类并实现接口,无需修改InventoryContext或任何服务类——这就是开闭原则的落地。测试用例清晰展示了状态自动切换(售罄)和非法操作拦截(售罄后下单)两个核心价值。

3. 用策略模式解耦价格计算:让“会员价/满减/节日折扣”自由组合不改一行结算代码

服装销售的定价规则极其灵活:新会员首单8折、VIP客户全场9折、满300减50、双十二跨店满减……如果把这些逻辑全塞进OrderService.calculateTotal()里,每次营销活动上线都要改核心结算方法,风险高、测试难、上线慢。策略模式的解法是:把每种定价规则封装成独立策略类,结算服务通过配置动态选择策略,实现“算法可插拔”。

3.1 定义价格计算策略接口与标准实现

// 价格计算策略接口:输入订单信息,输出最终价格 public interface PricingStrategy { BigDecimal calculatePrice(Order order, List<OrderItem> items); String getStrategyName(); // 用于日志和审计 } // 基础价格策略:按商品标价累加(无任何折扣) public class BasePriceStrategy implements PricingStrategy { @Override public BigDecimal calculatePrice(Order order, List<OrderItem> items) { BigDecimal total = BigDecimal.ZERO; for (OrderItem item : items) { total = total.add(item.getPrice().multiply(BigDecimal.valueOf(item.getQuantity()))); } return total; } @Override public String getStrategyName() { return "基础价格"; } } // 会员折扣策略:根据会员等级打不同折扣 public class MemberDiscountStrategy implements PricingStrategy { private final Map<String, BigDecimal> discountMap; // 会员等级 -> 折扣率(如0.9表示9折) public MemberDiscountStrategy() { this.discountMap = new HashMap<>(); discountMap.put("GOLD", new BigDecimal("0.85")); // 黄金会员85折 discountMap.put("SILVER", new BigDecimal("0.90")); // 白银会员9折 discountMap.put("BRONZE", new BigDecimal("0.95")); // 青铜会员95折 } @Override public BigDecimal calculatePrice(Order order, List<OrderItem> items) { String memberLevel = order.getMemberLevel(); BigDecimal discountRate = discountMap.getOrDefault(memberLevel, BigDecimal.ONE); BigDecimal basePrice = new BasePriceStrategy().calculatePrice(order, items); return basePrice.multiply(discountRate).setScale(2, RoundingMode.HALF_UP); } @Override public String getStrategyName() { return "会员折扣"; } } // 满减策略:满N减M(支持多档,如满300减50,满500减120) public class ThresholdDiscountStrategy implements PricingStrategy { // 多档满减规则:[满额, 减额] 数组 private final List<int[]> rules; public ThresholdDiscountStrategy(List<int[]> rules) { this.rules = rules; } @Override public BigDecimal calculatePrice(Order order, List<OrderItem> items) { BigDecimal basePrice = new BasePriceStrategy().calculatePrice(order, items); BigDecimal discount = BigDecimal.ZERO; // 按满额从高到低排序,取最高可满足档位 rules.sort((a, b) -> Integer.compare(b[0], a[0])); // 降序 for (int[] rule : rules) { if (basePrice.compareTo(BigDecimal.valueOf(rule[0])) >= 0) { discount = BigDecimal.valueOf(rule[1]); break; } } return basePrice.subtract(discount).max(BigDecimal.ZERO); // 不低于0 } @Override public String getStrategyName() { return "满减优惠"; } }

逻辑说明:PricingStrategy接口将“计算什么”与“怎么计算”分离。BasePriceStrategy是基石,其他策略都依赖它获取原始价格再叠加计算。ThresholdDiscountStrategy支持多档规则并自动选取最优档,避免了if (price>=500) {...} else if (price>=300) {...}的硬编码嵌套。setScale(2, RoundingMode.HALF_UP)强制保留两位小数,规避浮点精度问题——这是电商结算的硬性要求。

3.2 构建策略上下文与组合策略支持

// 策略上下文:管理当前激活的策略 public class PricingContext { private PricingStrategy currentStrategy; public PricingContext(PricingStrategy strategy) { this.currentStrategy = strategy; } // 运行时切换策略(如营销活动开启时) public void setStrategy(PricingStrategy strategy) { this.currentStrategy = strategy; } // 执行价格计算 public BigDecimal calculatePrice(Order order, List<OrderItem> items) { if (currentStrategy == null) { throw new IllegalStateException("未设置价格策略"); } return currentStrategy.calculatePrice(order, items); } public String getCurrentStrategyName() { return currentStrategy != null ? currentStrategy.getStrategyName() : "未设置"; } } // 组合策略:支持多个策略叠加(如先会员折扣,再满减) public class CompositePricingStrategy implements PricingStrategy { private final List<PricingStrategy> strategies; public CompositePricingStrategy(List<PricingStrategy> strategies) { this.strategies = strategies; } @Override public BigDecimal calculatePrice(Order order, List<OrderItem> items) { BigDecimal price = new BasePriceStrategy().calculatePrice(order, items); for (PricingStrategy strategy : strategies) { // 注意:组合策略中,每个策略都基于上一步结果计算 // 实际业务中需明确顺序(如折扣在前,满减在后) price = strategy.calculatePrice(order, items); } return price.max(BigDecimal.ZERO); } @Override public String getStrategyName() { return "组合策略(" + strategies.stream() .map(PricingStrategy::getStrategyName) .collect(Collectors.joining("+")) + ")"; } }

参数说明:PricingContext是策略模式的“开关”,它不参与计算,只负责路由。CompositePricingStrategy解决了策略叠加问题——但要注意,叠加顺序至关重要(如满减后再打折 vs 折扣后再满减结果不同)。实际项目中,应在策略配置中显式声明执行顺序,而非依赖List插入顺序。

3.3 在订单服务中集成策略上下文

// 订单服务:核心结算逻辑与策略解耦 public class OrderService { // 策略上下文,可被外部注入或配置 private PricingContext pricingContext; public OrderService() { // 默认使用基础价格策略 this.pricingContext = new PricingContext(new BasePriceStrategy()); } // 允许运行时切换策略(如根据订单会员等级自动匹配) public void setPricingStrategyForOrder(Order order) { if ("GOLD".equals(order.getMemberLevel())) { pricingContext.setStrategy(new MemberDiscountStrategy()); } else if (order.getTotalAmount().compareTo(BigDecimal.valueOf(300)) >= 0) { // 满300减50 List<int[]> rules = Arrays.asList(new int[]{300, 50}); pricingContext.setStrategy(new ThresholdDiscountStrategy(rules)); } else { pricingContext.setStrategy(new BasePriceStrategy()); } } // 核心结算方法:完全不关心具体策略实现 public BigDecimal calculateOrderTotal(Order order, List<OrderItem> items) { System.out.println("【结算启动】使用策略:" + pricingContext.getCurrentStrategyName()); BigDecimal total = pricingContext.calculatePrice(order, items); System.out.println("【结算完成】原始价:" + new BasePriceStrategy().calculatePrice(order, items) + " → 最终价:" + total); return total; } } // 订单实体类(简化) public class Order { private String orderId; private String memberLevel; // 会员等级 private BigDecimal totalAmount; // 订单总金额(计算前为null) // 构造函数、getter/setter 略 } // 商品项实体类(简化) public class OrderItem { private String itemId; private BigDecimal price; // 单价 private int quantity; // 数量 // 构造函数、getter/setter 略 } // 测试驱动:验证策略切换效果 public class StrategyPatternDemo { public static void main(String[] args) { OrderService orderService = new OrderService(); Order order = new Order(); order.setOrderId("ORD-2024-001"); order.setMemberLevel("GOLD"); List<OrderItem> items = new ArrayList<>(); items.add(new OrderItem("DRESS-001", new BigDecimal("299.00"), 1)); // 连衣裙299元 items.add(new OrderItem("SHIRT-002", new BigDecimal("199.00"), 2)); // 衬衫199*2=398元 System.out.println("=== 会员黄金等级订单 ==="); orderService.setPricingStrategyForOrder(order); orderService.calculateOrderTotal(order, items); System.out.println("\n=== 普通用户满300订单 ==="); order.setMemberLevel("NONE"); orderService.setPricingStrategyForOrder(order); orderService.calculateOrderTotal(order, items); } }

关键点:OrderService.calculateOrderTotal()方法体只有3行,却能支撑任意复杂的价格规则。新增“节日翻倍积分”策略?只需实现PricingStrategy接口,然后在setPricingStrategyForOrder()中添加判断逻辑即可。策略的“可配置化”是工程落地的关键——实际项目中,策略选择逻辑应从硬编码迁移到配置中心(如 Nacos、Apollo),实现运维人员无需发版即可调整营销规则。

4. 用观察者模式实现库存变更实时通知:让“调拨申请”“售罄预警”自动触发不写轮询

当A店向B店发起调拨申请,或某款商品库存低于安全阈值时,系统需要自动通知相关人员(店长、采购员、系统管理员)。传统做法是在调拨/销售方法末尾硬编码调用sendEmail()、sendSMS(),导致业务逻辑与通知逻辑强耦合。观察者模式将“谁来通知”和“通知什么”解耦:库存上下文作为被观察者(Subject),各类通知服务作为观察者(Observer),当库存变更时,上下文遍历通知所有注册的观察者。

4.1 定义观察者接口与具体通知实现

// 观察者接口:所有监听库存变更的服务必须实现 public interface InventoryObserver { // 当库存发生变更时被调用 void onInventoryChanged(InventoryEvent event); // 获取观察者名称(用于日志) String getObserverName(); } // 邮件通知观察者 public class EmailNotificationObserver implements InventoryObserver { private final String adminEmail; public EmailNotificationObserver(String adminEmail) { this.adminEmail = adminEmail; } @Override public void onInventoryChanged(InventoryEvent event) { String subject = "【库存告警】" + event.getItemId() + " 在 " + event.getLocation() + " 状态变更"; String body = String.format( "时间:%s\n" + "事件:%s\n" + "原状态:%s → 新状态:%s\n" + "当前库存:%d\n" + "操作人:%s", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()), event.getEventType(), event.getOldState(), event.getNewState(), event.getCurrentStock(), event.getOperator() ); // 实际项目中调用邮件发送SDK(如JavaMail、SendGrid) System.out.println("📧 已发送邮件至 " + adminEmail + ":\n" + body); } @Override public String getObserverName() { return "邮件通知"; } } // 短信通知观察者(模拟) public class SMSNotificationObserver implements InventoryObserver { private final String adminPhone; public SMSNotificationObserver(String adminPhone) { this.adminPhone = adminPhone; } @Override public void onInventoryChanged(InventoryEvent event) { String message = String.format( "【服装系统】%s@%s 库存变更:%s→%s,余%d件", event.getItemId(), event.getLocation(), event.getOldState(), event.getNewState(), event.getCurrentStock() ); // 实际项目中调用短信网关API System.out.println("📱 已发送短信至 " + adminPhone + ":" + message); } @Override public String getObserverName() { return "短信通知"; } } // 日志审计观察者(必选,记录所有变更) public class AuditLogObserver implements InventoryObserver { @Override public void onInventoryChanged(InventoryEvent event) { System.out.println("📝 审计日志:" + event.getEventType() + " | " + event.getItemId() + "@" + event.getLocation() + " | " + event.getOldState() + "→" + event.getNewState() + " | " + "操作人:" + event.getOperator()); } @Override public String getObserverName() { return "审计日志"; } }

逻辑说明:每个观察者只关注自己职责——邮件发详细内容,短信发简讯,日志记全字段。onInventoryChanged()方法接收统一的InventoryEvent对象,确保观察者不依赖具体业务类,降低耦合。AuditLogObserver是基础设施层观察者,应始终启用,为后续排查问题提供依据。

4.2 构建被观察者(库存上下文)与事件对象

// 库存变更事件:承载通知所需的所有数据 public class InventoryEvent { private final String itemId; private final String location; private final String eventType; // "SALE", "STOCK_ADJUST", "STATE_CHANGE" private final String oldState; private final String newState; private final int currentStock; private final String operator; // 操作人ID private final long timestamp; public InventoryEvent(String itemId, String location, String eventType, String oldState, String newState, int currentStock, String operator) { this.itemId = itemId; this.location = location; this.eventType = eventType; this.oldState = oldState; this.newState = newState; this.currentStock = currentStock; this.operator = operator; this.timestamp = System.currentTimeMillis(); } // Getter 方法(略) public String getItemId() { return itemId; } public String getLocation() { return location; } public String getEventType() { return eventType; } public String getOldState() { return oldState; } public String getNewState() { return newState; } public int getCurrentStock() { return currentStock; } public String getOperator() { return operator; } public long getTimestamp() { return timestamp; } } // 增强版库存上下文:支持观察者注册与通知 public class ObservableInventoryContext extends InventoryContext { private final List<InventoryObserver> observers = new ArrayList<>(); public ObservableInventoryContext(String itemId, int initialStock, String location) { super(itemId, initialStock, location); } // 注册观察者 public void addObserver(InventoryObserver observer) { if (!observers.contains(observer)) { observers.add(observer); System.out.println("✅ 已注册观察者:" + observer.getObserverName()); } } // 移除观察者 public void removeObserver(InventoryObserver observer) { observers.remove(observer); } // 通知所有观察者(重写父类状态变更方法) @Override public void setState(InventoryState newState) { String oldState = this.getStateName(); super.setState(newState); // 先执行状态变更 // 再发布事件 InventoryEvent event = new InventoryEvent( getItemId(), getLocation(), "STATE_CHANGE", oldState, newState.getStateName(), getStock(), "SYSTEM" // 系统自动触发 ); notifyObservers(event); } // 通知所有观察者(新增库存扣减事件) public void onStockDecreased(int quantity, String operator) { int newStock = getStock(); InventoryEvent event = new InventoryEvent( getItemId(), getLocation(), "SALE", getStateName(), getStateName(), newStock, operator ); notifyObservers(event); } // 通用通知方法 private void notifyObservers(InventoryEvent event) { System.out.println("🔔 开始通知 " + observers.size() + " 个观察者..."); for (InventoryObserver observer : observers) { try { observer.onInventoryChanged(event); } catch (Exception e) { // 观察者异常不应阻断主流程 System.err.println("⚠️ 观察者 " + observer.getObserverName() + " 通知失败:" + e.getMessage()); } } } }

参数说明:ObservableInventoryContext继承自InventoryContext,复用其状态机能力,同时增加观察者管理。notifyObservers()中的异常捕获是关键——某个观察者(如邮件服务)宕机,不能导致库存扣减失败。onStockDecreased()方法暴露给外部调用,用于销售、调拨等业务操作后手动触发通知,比在sell()方法里硬编码更灵活。

4.3 在库存服务中配置观察者并触发通知

// 增强版库存服务:初始化观察者并绑定 public class ObservableInventoryService extends InventoryService { public ObservableInventoryService() { super(); // 初始化时为所有库存项注册观察者 initObservers(); } private void initObservers() { // 为所有库存项添加审计日志观察者(必须) for (InventoryContext context : inventoryMap.values()) { if (context instanceof ObservableInventoryContext) { ((ObservableInventoryContext) context).addObserver(new AuditLogObserver()); } } // 为特定高价值商品添加邮件+短信告警(示例:连衣裙) ObservableInventoryContext dressContextA = (ObservableInventoryContext) inventoryMap.get("DRESS-001@STORE-A"); if (dressContextA != null) { dressContextA.addObserver(new EmailNotificationObserver("manager@store-a.com")); dressContextA.addObserver(new SMSNotificationObserver("13800138000")); } } // 重写下单方法,触发库存变更通知 @Override public boolean processOrder(String itemId, String storeId) { String key = itemId + "@" + storeId; ObservableInventoryContext context = (ObservableInventoryContext) inventoryMap.get(key); if (context == null) { System.out.println("错误:未找到商品 " + itemId + " 在门店 " + storeId); return false; } try { context.sell(); // 扣减成功后,手动触发销售事件通知(含操作人) context.onStockDecreased(1, "USER-12345"); // 模拟用户ID return true; } catch (IllegalStateException e) { System.out.println("订单失败:" + e.getMessage()); return false; } } } // 测试驱动:验证观察者通知链 public class ObserverPatternDemo { public static void main(String[] args) { ObservableInventoryService service = new ObservableInventoryService(); System.out.println("\n=== A店售出1件连衣裙(触发邮件+短信+日志) ==="); service.processOrder("DRESS-001", "STORE-A"); } }

关键点:观察者模式的价值在于“松耦合”和“可扩展”。当需要新增“企业微信机器人告警”时,只需编写WeComNotificationObserver并在initObservers()中注册,无需修改ObservableInventoryContext或任何业务方法。processOrder()方法依然只做一件事:调用context.sell()和context.onStockDecreased(),通知逻辑完全由观察者自治。这种设计让系统具备了应对未来通知渠道变化的弹性。

5. 避坑指南:设计模式落地时最常踩的5个血泪坑

设计模式不是银弹,生搬硬套反而会让代码更难维护。以下是我在指导多个模拟项目X过程中,学生反复踩中的典型陷阱,每一条都附带真实复现步骤和修复方案。

5.1 现象:状态模式中,新增状态后所有已有状态类都要修改handleXXX()方法

原因:在InventoryState接口中定义了所有可能的操作(handleList,handleSell,handleUnlist),但并非每个状态都支持全部操作。当新增一个操作(如handleFreeze())时,编译器强制要求所有6个状态类都实现该方法,哪怕其中5个状态根本不允许冻结。
解决:采用“空对象模式”或“默认实现”。将接口改为抽象类,为不支持的操作提供默认空实现:

public abstract class AbstractInventoryState implements InventoryState { @Override public void handleList(InventoryContext context) { throw new UnsupportedOperationException("当前状态不支持上架操作"); } // 其他方法同理... }

这样,新增操作只需在抽象类中添加默认方法,已有状态类无需改动,只在需要支持的子类中重写即可。

5.2 现象:策略模式中,组合策略(Composite)计算结果错误,满减没生效

原因:CompositePricingStrategy的calculatePrice()方法中,每个策略都基于BasePriceStrategy的原始价格计算,而非上一个策略的结果。例如:先算会员价(299×0.85=254.15),再算满减(254.15满300?不满足,满减失效),导致满减未触发。
解决:修正组合策略逻辑,确保链式计算:

public BigDecimal calculatePrice(Order order, List<OrderItem> items) { BigDecimal price = new BasePriceStrategy().calculatePrice(order, items); for (PricingStrategy strategy : strategies) { // 关键:传入当前price,而非原始items price = strategy.calculatePrice(order, items); // ❌ 错误:仍用原始items // 正确做法:需策略支持“基于当前价格计算”,或重构为责任链模式 } return price; }

更优解:放弃CompositePricingStrategy,改用责任链模式(Chain of Responsibility),每个处理器决定是否处理及是否继续传递。

5.3 现象:观察者模式中,短信通知偶尔丢失,日志显示“观察者通知失败”

原因:notifyObservers()中的异常捕获过于宽泛,SMSNotificationObserver内部调用第三方短信API时抛出NetworkException,但被catch (Exception e)吞掉,仅打印错误日志,未做重试或告警。
解决:分层异常处理。观察者内部应捕获网络异常并记录详细堆栈,同时向上抛出业务异常;上下文层捕获后,对关键观察者(如短信)启动异步重试:

// 在 notifyObservers() 中 if (observer instanceof SMSNotificationObserver) { // 启动异步重试任务(如用 ScheduledExecutorService) retrySMSLater(event, (SMSNotificationObserver) observer); } else { observer.onInventoryChanged(event); }

5.4 现象:工厂模式创建服装对象时,传入参数过多(品牌、季节、尺码、颜色、材质...),构造函数爆炸

原因:过度追求“一个工厂创建所有服装”,将所有属性塞进工厂方法参数,违背单一职责。
解决:采用“建造者模式(Builder)”配合工厂。工厂只负责返回对应类型的 Builder,由 Builder 逐步设置属性:

public class ClothingFactory { public static DressBuilder createDress() { return new DressBuilder(); } public static ShirtBuilder createShirt() { return new ShirtBuilder(); } } // 使用:ClothingFactory.createDress().brand("ZARA").season("SUMMER").size("M").build();

工厂退化为“类型路由”,复杂对象

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/13 18:11:59

自适应滑模控制原理与Matlab仿真:参数不确定系统的鲁棒设计

参数不确定这事&#xff0c;做控制的都懂&#xff0c;模型拿到手&#xff0c;标称参数看着挺准&#xff0c;一上实际系统全是偏差&#xff0c;摩擦、老化、负载变化&#xff0c;每一项都在考验控制器的鲁棒性。传统PID还能凑合&#xff0c;但要是系统本身非线性强、参数摄动范围…

作者头像 李华
网站建设 2026/10/13 18:11:18

从环境准备到版本管理:开发环境配置的底层逻辑与实战指南

聊到“环境准备”&#xff0c;很多人的第一反应是“不就是装个软件嘛&#xff0c;照着官网点下一步就行”。但真正在项目里吃过亏的人都知道&#xff0c;一个靠谱的开发环境&#xff0c;往往决定了你整个项目的体验底色。我见过太多人卡在教程的“前置准备”环节——不是下载太…

作者头像 李华
网站建设 2026/10/13 18:08:51

Solr部署模式选择:独立服务器与SolrCloud集群的实战对比

1. 从单机到集群&#xff1a;为什么Solr的部署模式选择比调优更重要很多刚接触Solr的朋友&#xff0c;拿到安装包的第一反应就是解压、启动、建索引&#xff0c;跑通了就觉得万事大吉。我见过太多项目&#xff0c;前期用单机模式跑得飞快&#xff0c;数据量一上来就开始频繁GC、…

作者头像 李华
网站建设 2026/10/13 18:05:28

蓝屏分析工具原理:从dump文件解析到驱动定位与自动化排查

简介&#xff1a;Bluescreenview蓝屏分析工具资源包面向Windows系统维护人员、IT运维及普通用户&#xff0c;用于解析系统蓝屏时生成的DMP文件&#xff0c;快速定位错误代码、停止消息与驱动程序等崩溃线索。资源包共3个文件&#xff0c;以inscode工程配置、html页面与gitignor…

作者头像 李华
网站建设 2026/10/13 18:04:14

全转动副三自由度并联机器人设计:从自由度计算到避坑指南

简介&#xff1a;这份文档是面向机械设计、机器人工程方向学生与研发人员的毕业设计资料&#xff0c;围绕全转动副三自由度并联机器人展开&#xff0c;重点解决传统并联机构解耦困难与奇点问题。作者通过全新支链设计实现运动解耦并规避奇点&#xff0c;同时给出重复定位精度小…

作者头像 李华
网站建设 2026/10/13 18:00:26

AI辅助专利撰写:技术交底书结构化指南与实操避坑

1. 技术交底书不是"给AI的提示词"&#xff0c;而是发明人的思维导图很多人第一次用AI辅助写专利&#xff0c;脑子里想的都是"我把想法丢给AI&#xff0c;它就能给我吐出一份能递交的申请文件"。我一开始也这么天真过。结果呢&#xff1f;AI写出来的权利要求…

作者头像 李华