ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

服装管理系统设计模式落点与课设答辩指南

服装管理系统设计模式落点与课设答辩指南

简介:这是一份面向软件设计模式与体系结构课程学习者的期末大作业资料包,以服装管理系统为实践案例,适合需要完成课程设计、理解设计模式落地方式的学生参考。项目围绕单例、工厂、观察者、策略、装饰器、代理及MVC等模式展开,并结合三层架构、事件驱动等体系结构思想,呈现从需求分析、数据库设计到功能实现的完整脉络。压缩包共39个文件,约2.4MB,包含17个Java源文件、14张运行截图与结构图、4个XML配置、1份软件设计说明书、1份项目演示PPT及工程配置文件,源码、文档和演示材料相对齐全,便于对照阅读与复现。已有5466人学习下载,说明其在课程作业场景中具备一定参考价值。读者可从中获取可运行的服装管理系统源码、模式应用示例、设计文档与展示课件,用于梳理类间职责、理解架构分层,并借鉴项目说明与演示结构完成自己的课程设计或答辩准备。

1. 答辩现场最反直觉的一条:模式数量不加分,落错地方才扣分

课程答辩上最容易被问倒的一句话,不是“你用了哪些设计模式”,而是“这段计价代码,如果明年换季再加一种折扣,你要改几个类”。软件设计模式与体系结构这门课的期末大作业,考的不是你能背出 23 种模式,而是你能不能在服装管理系统这个具体场景里,把商品、库存、订单、会员四条业务线切干净,再让设计模式落在真正会变的地方。功能能跑只是入场券,架构分层和模式落点才是拉分项。这篇笔记按“考什么 → 高频模式怎么用 → 真题怎么答 → 哪里最容易被扣分”的顺序,把服装管理系统类课设的考点拆成可背、可练的内容,适合正在做课设、准备答辩,或者面试时被问到设计模式落点的人。

2. 考点分布:服装管理系统的四个业务模块与一张分值地图

服装管理系统看起来业务量不大,考点密度却不低:一个“颜色 × 尺码”就把数据库设计和对象创建同时难住,一次“换季打折叠加会员折扣”就能把计价逻辑写成一堆 if-else。这一章先把模块切法和分值分布讲清楚,后面再落到具体模式。

2.1 从需求到模块:商品、库存、订单、会员四条线怎么切

商品线要区分 SPU 和 SKU。一款“春季圆领卫衣”是 SPU,它下面的“黑色 L”“米白 M”才是 SKU,真正被下单、被扣库存、被退货的粒度是 SKU 而不是 SPU。很多人的表设计到这就翻车:只在商品表放一个 stock 字段,结果颜色尺码没法分库存,答辩老师一问“黑色 L 卖完了,黑色 M 还能不能卖”就答不上来。正确切法是 SPU 表存款式信息,SKU 表存“颜色 + 尺码 + 条形码 + 售价”,库存表以 SKU 为主键记可用量与锁定量。

库存线要区分“可用库存”和“锁定库存”。下单未支付时锁库存,支付成功才真正扣减,超时取消要回滚。这条线是所有并发问题的出处,也是设计模式的高频落点——库存低于阈值要通知补货,这就是观察者。

订单线是一台状态机:待支付 → 待发货 → 已发货 → 已完成,中途还有已取消、退款中、已退款。状态之间的合法迁移是硬约束,用 if-else 堆出来的写法会在“已发货能不能取消”这种问题上出错。

会员与促销线是变化最频繁的地方。会员等级折扣、满减、优惠券、限时活动价,几乎每学期答辩都会被要求“现场加一种促销”。这条线天然对应策略模式和装饰器模式。

四条线切完,模式的落点基本自动浮现:SKU 创建过程复杂 → 工厂 / 建造者;价格可组合 → 策略 + 装饰器;订单流转 → 状态;库存预警 → 观察者;下单固定骨架 → 模板方法;接第三方支付物流 → 适配器。答辩时能把这层对应关系讲出来,比报菜名式地说“我用了单例、工厂、代理、观察者”有用得多。

提示:模块划分图不要画成组织架构图,按“谁会变”来切。变化频繁的放一起并用接口隔开,稳定的沉到下层。

2.2 分值地图:分数到底压在哪儿

这类课程大作业的评分通常集中在四块,不同课程权重会浮动,但结构大同小异:

考核项常见考点大致权重最容易丢分的地方
功能完整性商品与 SKU 管理、入库出库、下单支付、退换货约 30%只做增删改查,库存没有扣减流水,退货不回滚
架构与分层分层职责、依赖方向、接口抽象约 25%Controller 里写业务、Service 里直接 new 实现类
模式应用6~8 个模式的落点与选择理由约 25%模式和场景不匹配,类图与代码对不上
文档与答辩用例图、类图、时序图、现场问答约 20%类图关系画反,说不清“为什么不用某个模式”

这张表最值得注意的不是百分比,而是失分点分布:功能丢分往往是漏了异常分支,架构丢分集中在依赖方向,模式丢分几乎全在“用了但说不清为什么”。很多人把时间全花在多写两个功能上,结果答辩十分钟被问穿三处,得不偿失。

从投入产出比看,先把分层做干净、把类图画准、把 6 个模式的落点说圆,拿到的分数远高于再堆两个 CRUD 页面。答辩老师翻代码通常只看几个位置:计价那段、库存扣减那段、订单状态判断那段。这三处写漂亮了,整体印象就立住了。

2.3 架构风格:三层不是目的,依赖方向才是

分层架构是这类系统的默认答案,但很多人只画出 Controller / Service / Dao 三层,代码里却让 Controller 直接 new Service 实现、Service 直接 new Dao 实现,一改数据库就得动业务代码。分层真正要拿分的是依赖方向:上层依赖抽象,下层实现抽象。

// 面向接口:Service 只认 Dao 接口,具体实现由外部注入 public interface OrderDao { Order findByNo(String orderNo); void save(Order order); } public class OrderServiceImpl implements OrderService { private final OrderDao orderDao; // 依赖抽象,不依赖 MysqlOrderDao private final StockService stockService; // 构造器注入:依赖从外面传进来,单元测试可以塞假实现 public OrderServiceImpl(OrderDao orderDao, StockService stockService) { this.orderDao = orderDao; this.stockService = stockService; } @Override public void place(Order order) { stockService.lock(order); // 锁库存 orderDao.save(order); // 落单 } }

这段代码的关键在三个参数和一处写法:orderDao声明的是接口类型,构造器负责注入,方法体里没有任何new。stockService同理。这样做的好处很实际——换存储、写单测、做 mock 都不用改业务类,答辩时被问“怎么体现依赖倒置”,指着这两行字段声明就够了。

架构风格还常被追问“为什么不用微服务”“为什么不用前后端分离”。稳妥的答法是承认边界:当前规模是单机单体,服务拆分的成本高于收益,但代码里已经按订单、库存、会员划了包边界,未来要拆有切口。不要答“微服务太复杂所以没做”,那等于承认没想过。

注意:包结构也要能讲。按层分包(controller / service / dao)适合小型课设,按业务分包(order / stock / member)更适合说清模块边界,选一种并说清理由,比混着来强。

3. 高频模式:七个模式在服装系统里的真实落点

这一章是整篇最该背的部分。答辩问模式,从来不是让你背定义,而是给你一个场景问“这里放什么”。下面七个模式按创建型、结构型、行为型排开,每个都给出落点、代码或结构,以及“为什么不是另一个”。

3.1 创建型:工厂方法建 SKU、抽象工厂建一族、单例管配置

工厂方法的落点是 SKU 创建。上衣要校验尺码范围,裤装要校验裤长,鞋要转欧洲码,创建前的校验逻辑各不相同,但调用方只想拿到一个合法的 SKU 对象。这时把createSku抽成接口,上衣工厂、裤装工厂各自实现校验,调用方只依赖接口。

抽象工厂的落点是一族对象。比如“标准订单族”包含标准订单、即时支付、普通物流,“预售订单族”包含预售订单、定金支付、延迟发货物流。这三个产品必须配套使用,不能混搭,抽象工厂正好表达“一族一起换”。答辩常问工厂方法和抽象工厂的区别,一句话答:工厂方法管一个产品等级,抽象工厂管多个产品等级的组合。

单例的落点是配置读取和流水号生成器。写法上不要只写懒汉式,容易被追问线程安全。

// 枚举单例:天然线程安全,还能防反射与反序列化破坏 public enum IdGenerator { INSTANCE; private final AtomicLong seq = new AtomicLong(1000L); public long next() { return seq.incrementAndGet(); // 订单号后缀,进程内唯一 } }

INSTANCE是唯一实例,AtomicLong保证自增的原子性,next()返回的号段从 1000 起。为什么用枚举而不是双重检查锁?因为枚举的实例创建由 JVM 保证,反序列化也不会产生新对象,答辩时这两点说出来就是加分项。但要注意别滥用单例:把 Service 全做成单例式的静态工具类,会直接毁掉可测试性,这是另一个失分点。

3.2 结构型:适配器接第三方、装饰器叠价格、代理管访问

适配器的落点是对接外部接口。第三方支付返回的字段叫trade_no、total_fee,你系统里叫payNo、amount,方法签名也不一样。这时不要让业务代码去认对方的字段,写一个适配器实现你自己的PayGateway接口,内部做字段映射和签名转换。物流接口同理,不同快递公司回传的轨迹格式完全不同,适配器把它们统一成LogisticsTrace。

装饰器的落点是价格叠加。基础价之上叠会员折扣、满减、优惠券,每一层都可以独立开关、独立排序。装饰器和继承的区别就在这:继承是编译期定死的组合,装饰器是运行期按需拼。答辩追问“为什么不用继承”时,答“三层促销就是 2³ 种子类,加一层翻一倍”最有力。

代理的落点是访问控制与横切逻辑。给订单查询加权限校验、给商品详情加缓存,这类逻辑不该混进业务方法,用静态代理或动态代理包一层,业务类保持干净。代理和装饰器的区别在意图:装饰器是增强功能,代理是控制访问。

3.3 行为型(上):策略管算法,状态管流转

策略模式的落点是计价。系统里最不该出现的就是一长串 if-else 判断会员等级和促销类型。

// 计价策略:新增一种促销 = 新增一个实现类,调用方一行不改 public interface DiscountStrategy { BigDecimal apply(BigDecimal origin); } // 会员等级折扣:等级 1~5,每级多减 2%,封顶 10% public class MemberDiscountStrategy implements DiscountStrategy { private final int level; public MemberDiscountStrategy(int level) { this.level = level; } @Override public BigDecimal apply(BigDecimal origin) { BigDecimal rate = BigDecimal.valueOf(Math.min(level * 2, 10) / 100.0); return origin.subtract(origin.multiply(rate)) .setScale(2, RoundingMode.HALF_UP); // 统一保留两位 } } // 满减:满 threshold 减 cut,不满原价返回 public class FullReductionStrategy implements DiscountStrategy { private final BigDecimal threshold; private final BigDecimal cut; public FullReductionStrategy(BigDecimal threshold, BigDecimal cut) { this.threshold = threshold; this.cut = cut; } @Override public BigDecimal apply(BigDecimal origin) { if (origin.compareTo(threshold) < 0) return origin; return origin.subtract(cut).max(BigDecimal.ZERO); // 兜底,别减成负数 } } // 上下文:把策略按顺序跑一遍,顺序就是业务规则 public class PriceCalculator { private final List<DiscountStrategy> strategies; public PriceCalculator(List<DiscountStrategy> strategies) { this.strategies = strategies; } public BigDecimal calc(BigDecimal origin) { BigDecimal price = origin; for (DiscountStrategy s : strategies) { price = s.apply(price); } return price; } }

参数说明放在这:level是会员等级,决定折扣率;threshold和cut是满减门槛与减免额;strategies的顺序决定“会员折扣先于满减”还是反过来,这是业务规则而不是技术细节,答辩时主动说明顺序依据会显得想清楚了。setScale(2, RoundingMode.HALF_UP)解决金额精度,max(BigDecimal.ZERO)防止连续减扣出现负数——这两处是很多人被追问“极端情况怎么办”时的救命细节。

状态模式的落点是订单流转。

// 订单状态:能不能付、能不能发由状态自己回答,不再写 switch public interface OrderState { void pay(OrderContext ctx); void ship(OrderContext ctx); void cancel(OrderContext ctx); String name(); } public class WaitPayState implements OrderState { @Override public void pay(OrderContext ctx) { ctx.setPayTime(LocalDateTime.now()); ctx.setState(new WaitShipState()); // 状态转移写在状态类内部 } @Override public void ship(OrderContext ctx) { throw new IllegalStateException("未支付订单不能发货"); } @Override public void cancel(OrderContext ctx) { ctx.releaseStock(); // 取消必须还库存 ctx.setState(new ClosedState()); } @Override public String name() { return "待支付"; } }

OrderContext持有当前状态并暴露setState,状态类内部调用它完成迁移;releaseStock()是上下文提供的能力,保证取消动作和库存回滚绑在一起。这段代码最值钱的地方是ship里那行抛异常——非法迁移在编译期看不出来,运行期必须拦住,答辩老师很吃这一套。

策略和状态长得几乎一样,区别在职责:策略之间互不知道,由调用方挑选;状态自己负责迁移,且迁移受业务约束。订单状态用策略写,最后会变成调用方到处判断“现在能不能发货”,等于把 switch 挪了个位置。

3.4 行为型(下):观察者做预警、模板方法定骨架、责任链走售后

观察者的落点是库存预警。库存对象作为被观察者,补货提醒、运营推送、报表刷新作为观察者,扣减后低于阈值就广播。课设里不要上消息队列,就老老实实用进程内观察者,但要在答辩时补一句“如果以后多实例部署,把观察者列表换成消息中间件,被观察者的接口不变”——这句话说明你知道单机方案的边界。

模板方法的落点是下单流程。校验参数 → 锁库存 → 生成订单 → 发起支付 → 通知发货,这五步的骨架固定,但不同订单类型(普通、预售、团购)在“锁库存”和“发起支付”上实现不同。父类定骨架,子类实现钩子,重复流程只写一遍。答辩追问“和策略的区别”,答“模板方法改的是步骤实现,策略换的是整个算法”。

责任链的落点是售后审批。退货金额 200 以内客服直接批,200 到 1000 走主管,1000 以上走经理。每级节点判断自己能不能处理,处理不了传给下一级。这样加一级审批只加一个节点,不用改原有判断。

4. 真题演练:八道最常被问的题,逐题给答题骨架

4.1 概念辨析四连问:先给一句话结论,再给系统里的例子

问:策略模式和状态模式有什么区别?答题骨架:先给结论——策略是“选算法”,状态是“换行为并驱动迁移”;再给本系统例子——计价用策略,因为折扣之间互不关心、可任意组合排序;订单用状态,因为待支付只能转待发货或已取消,非法迁移必须拦住;最后补一句代价——状态类数量会随状态增长,状态少的时候硬编码 switch 反而更省。

问:工厂方法和抽象工厂怎么选?先给结论——一个产品等级用工厂方法,一族必须配套的产品用抽象工厂。例子:SKU 创建用工厂方法;标准订单 / 预售订单这种“订单 + 支付 + 物流”三件套一起换的场景用抽象工厂。追问“抽象工厂能不能退化成工厂方法”,答能,但退化后就失去了“族约束”,容易混搭出不存在的组合。

问:装饰器和代理看起来一样,怎么区分?先给结论——装饰器是增强,代理是控制。例子:价格叠加用装饰器,每层都实现同一个计价接口并持有同类型对象;权限校验和缓存用代理,业务类本身不知道自己被代理了。再补一句实现差别:装饰器通常由调用方显式组装,代理往往对调用方透明。

问:类图里聚合和组合怎么区分?先给结论——生命周期是否同步。例子:订单和订单项是组合,订单删了订单项没有独立意义;购物车和商品是聚合,购物车清了商品还在。答题时顺手画一下菱形的位置和空心实心,比干说定义有效。

4.2 场景设计四连问:先说约束,再说方案,最后说取舍

问:库存超卖怎么处理?答题骨架:先说单机课设的稳妥做法——数据库行锁或乐观锁版本号,扣减时update stock set qty = qty - 1, version = version + 1 where sku_id = ? and qty >= 1 and version = ?,影响行数为 0 就代表失败重试或提示售罄;再说高并发场景的进阶方案——缓存预扣减加异步落库,但要说明这会引入一致性问题;最后给取舍——课设规模用数据库锁足够,重点是扣减和流水在同一个事务里。

问:订单超时未支付怎么自动取消?三个层次答:最简单是定时任务扫表,按创建时间捞超时单,缺点是精度受扫描间隔影响;好一点用延迟队列,到点触发;再补一句状态约束——取消动作必须走状态机的cancel,让它自己还库存,不要绕过状态直接改字段。

问:换季促销要能叠加,怎么设计?答策略加装饰器或策略链:每种促销一个策略实现,装配顺序写在配置里;新增一种促销只加一个类,符合开闭原则。追问“顺序冲突怎么办”,答按业务优先级固定顺序,并在计价结果里记录每一层的扣减明细,方便对账和排查。

问:退换货审批流程怎么设计?答责任链:每个审批节点持有金额上限和下一节点引用,处理不了就传递;节点可配置,加一级审批不动原有代码。再补一句:审批结果落库并留痕,避免只存在内存链条里。

4.3 代码走查:一段 if-else 计价怎么重构

答辩常见环节是给你一段代码,让你当场说问题。

// 重构前:每加一种促销就多一层 if,只能整段测 public BigDecimal calc(Order order, Member member, Coupon coupon) { BigDecimal price = order.getOriginPrice(); if (member.getLevel() == 1) price = price.multiply(new BigDecimal("0.98")); else if (member.getLevel() == 2) price = price.multiply(new BigDecimal("0.96")); else if (member.getLevel() >= 3) price = price.multiply(new BigDecimal("0.90")); if (order.getOriginPrice().compareTo(new BigDecimal("299")) >= 0) { price = price.subtract(new BigDecimal("30")); } if (coupon != null && coupon.getType() == 1) price = price.subtract(coupon.getAmount()); else if (coupon != null && coupon.getType() == 2) price = price.multiply(coupon.getRate()); return price; }

问题指出来三条就够:一是等级和促销硬编码,加一种就改这个方法,违反开闭原则;二是顺序写死在代码里,会员折扣和满减谁先谁后改不了;三是整段不可分割测试,边界值(刚好 299、券后为负)很容易漏。

重构按四步说:找变化点(折扣种类与顺序)、抽接口(DiscountStrategy)、拆实现(会员、满减、券各自成类)、在装配处决定顺序(工厂或配置)。重构后调用方只剩两行:new PriceCalculator(strategies).calc(origin),其中strategies从配置装配。答辩到这里补一句“重构没有改变对外方法签名,老调用方不用动”,最能说明你懂渐进式改造。

5. 易错点:五个当场掉分的答法

5.1 模式滥用类的两个错法

错法一:把模式数量当战果。错在哪:开口就是“我用了单例、工厂、抽象工厂、建造者、代理、适配器、观察者、策略、状态”,一共九种。为什么错:评委听到第八个时已经在找漏洞了,模式越多,能对上的场景越少,随便挑一个问“这里为什么需要建造者”就卡住。怎么答对:主动收敛到六到七个,每个都能说出“变化点是什么、不加模式会怎样”,并且明确说“XX 场景我评估过用某模式,但当前只有两种实现,硬编码更划算”。敢于做减法,反而是加分项。

错法二:把工厂写成 new 的包装。错在哪:工厂类里只有一个方法return new Sku(),没有任何创建逻辑差异。为什么错:这说明没找到真正的创建复杂度,评委一句“那它和直接 new 有什么区别”就答不上。怎么答对:让工厂承担真实职责——按品类做字段校验、补默认值、生成条码、决定子类型,把“创建前的判断”收进工厂,并说明调用方因此不需要知道子类存在。

5.2 架构与文档类的三个错法

错法三:类图和代码对不上。错在哪:类图画了策略模式,代码里却是一个 switch。为什么错:多数人是先找模板画图再写代码,两边各写各的。怎么答对:类图从代码反推,画完对照三处——接口是否存在、实现类是否被调用、依赖箭头是否指向接口。答辩前把类图里的每个类在 IDE 里搜一遍,搜不到就删掉。

错法四:关系箭头画反。错在哪:聚合的菱形画到了“部分”那一端,依赖箭头指向被依赖者。为什么错:UML 的方向是“谁持有谁”,凭印象画必错。怎么答对:记住组合与聚合的菱形永远在整体一端,依赖箭头从使用者指向被使用者,继承是实线空心三角指向父类,实现是虚线空心三角指向接口。这几条背下来,图就不会出硬伤。

错法五:说“就是个单体课设,没什么架构”。错在哪:把规模小等同于没设计。为什么错:这门课考的就是“在小系统里也能看出结构”,自贬等于放弃 25% 的分。怎么答对:坦率说明当前是单体分层,再讲清边界——按业务分包、对外接口收敛在 Service 层、第三方依赖都走适配器,所以未来拆服务有切口。把“为什么现在不拆”讲成成本判断,而不是能力不足。

6. 从背定义到答场景:一套能套用的回答节奏

6.1 四步答题模板

被问“这里为什么用某个模式”时,别从定义讲起,按下面四步走,三十秒能说清。

步骤说什么服装系统里的例子
一、点变化这个位置什么东西会变、多久变一次促销活动每季都变,会员等级规则每年调
二、给抽象抽出什么接口,谁依赖它DiscountStrategy,计价器只依赖这个接口
三、落实现有哪几个实现类,装配在哪里会员、满减、券三类实现,在计价器工厂里装配
四、讲取舍代价是什么,什么情况下不用它类变多了,只有一种折扣时反而多余

这四步的价值在于顺序:先讲变化,评委就知道你不是背的;先讲取舍,评委就知道你想过边界。第四步千万别省,主动说“什么时候不该用”,比被追问出来强得多。

6.2 押题:三个最可能被问到的方向

第一个方向是策略与状态的区分,加上订单超时取消。这两题连着问的概率极高,因为它们同时考模式辨析和业务约束。准备时把WaitPayState的取消逻辑写熟,能说出“取消要还库存、要停掉支付超时任务”这两句就够。

第二个方向是库存一致性。问题形式可能是“两个人同时买最后一件怎么办”“支付成功但扣库存失败怎么办”。答题顺序固定:先说约束(不能超卖、不能少卖),再说手段(事务 + 行锁或乐观锁 + 扣减流水),最后说边界(课设不上分布式事务,靠本地事务保证)。

第三个方向是架构取舍。常见问法是“为什么不做前后端分离”“为什么不用微服务”“你的分层和 MVC 有什么区别”。统一用“成本—收益—切口”三段式回答,不要评价技术优劣,只谈当前规模下的判断。

面试场景里也一样。被问设计模式时,先反问自己一句:这个场景里到底什么会变。答得上来的,就答得漂亮;答不上来的,宁可说“这里我没用模式,因为只有一种实现”,也不要去凑。

我第一次做这类课设时,把能想到的模式全塞进去了,答辩被连着问了三个“这个为什么需要”,一个都没答圆,最后分数比功能更少的同学还低。从那以后我每次动手前都强制走一遍“先列变化点、再决定用不用模式、最后才画类图”的顺序,宁可少两个模式,也要每个都讲得清。希望帮到你。

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

返回列表