
java与模式实战方法:提示词、素材与细节优化
在 Java 项目里使用设计模式,难点通常不是记住“工厂”“策略”“装饰器”这些名字,而是判断问题是否真的需要模式,如何把模式放进现有代码,以及怎样验证改动没有引入新的复杂度。使用 AI 辅助开发后,难点又多了一层:怎样提供足够准确的上下文,让生成的代码符合项目约定,而不是只在孤立示例里看起来合理。
本文把这几件事放在同一个工作流程里:先识别变化点,再选择模式;用结构化提示词约束 AI;准备可复用的项目素材;通过一个可运行的 Java 示例落地策略模式;最后检查边界、参数和安全问题。示例面向 Java 8 及以上版本,不依赖第三方库。
一、先明确“模式”解决什么问题
设计模式是常见问题的组织方式,不是必须完成的清单。一个类里出现多个 if,不意味着一定要套模式;反过来,如果业务规则会持续增加,所有分支都挤在一个方法里,简单的条件判断也可能很快变成维护负担。
开始改造前,可以先用下面几个问题描述现状:
- 什么在变化?例如折扣计算规则、支付渠道、通知方式或文件导出格式。
- 谁需要知道变化?如果调用方必须了解每种规则的内部细节,说明边界可能划分得不合适。
- 变化的频率和规模如何?只有一个长期稳定的分支,未必值得抽象;规则不断增加、经常调整,抽象的价值就更高。
- 新方案会减少什么成本?它是否降低了修改影响范围,还是仅仅增加接口、工厂和类的数量?
模式的实用目标是把容易变化的部分隔离开,并让依赖关系更清晰。若一个模式不能让修改更集中、测试更容易或调用方更简单,就应考虑更朴素的实现。
二、用提示词把需求变成可检查的任务
向 AI 提问时,单说“帮我用设计模式重构 Java 代码”通常不够。模型不知道项目使用的 Java 版本、现有接口、异常约定和兼容要求,容易自行补设定。更可靠的提示词要交代背景、目标、约束、输出形式和验收条件。
一个可复用的提示词模板
你是一名熟悉 Java 项目维护的工程师。
请基于下面的代码和需求给出重构建议。
项目环境:
- Java 版本:[例如 Java 8]
- 是否允许新增依赖:[是/否]
- 现有异常和日志约定:[填写实际约定]
- 必须保持兼容的公开接口:[填写接口]
当前问题:
[描述现状,以及正在发生的修改或维护问题]
目标:
[描述期望行为,不要只写模式名称]
约束:
- 不改变:[列出不能改变的行为]
- 参数边界:[列出 null、负数、空字符串等处理规则]
- 线程安全要求:[填写已知要求;未知时请说明未知]
- 不要引入:[列出不允许的依赖、静态状态或其他实现]
请按以下结构回答:
1. 判断是否需要设计模式,并说明理由。
2. 给出适用的模式及不适用的情况。
3. 提供完整、可编译的 Java 示例,并写明文件名和运行方式。
4. 说明输入校验、异常、并发和安全边界。
5. 列出需要人工确认的假设,不要把未知业务规则当成事实。
这个模板有两个重要作用。第一,它要求模型解释“为什么选这个模式”,减少为了套模式而套模式的情况。第二,它要求模型把未知信息标出来,而不是擅自决定。例如“优惠金额能否小于零”是业务规则,不能单凭 Java 语法推断。
把大任务拆成几轮
对真实代码库,不建议第一轮就要求 AI 生成完整重构。可以按风险递增的顺序推进:
- 请模型只识别变化点、依赖关系和可能的模式,不改代码。
- 确认业务规则后,让模型设计最小接口和类关系。
- 让模型只改指定文件,并列出新增、删除和行为变化。
- 要求补充边界测试,再由开发者本地编译、运行和审查差异。
分步工作的价值不在于模型一定更准确,而在于更容易发现错误假设,也便于把影响限制在明确范围内。提示词不会自动赋予模型对整个仓库的了解;如果工具没有读取某些文件,就应把必要内容明确提供。
三、准备好可复用的“素材”
这里的素材不是图片,而是能帮助人和 AI 快速理解项目的技术资料。素材过少,回答容易脱离项目;素材过多,则会引入无关内容并增加审查成本。应优先提供与本次改动直接相关的信息。
常用素材清单
- 代码素材:相关接口、调用方、实现类和测试代码。不要只给一个方法,却省略它的调用方式与约束。
- 业务素材:规则说明、输入样例、边界样例,以及冲突时以哪条规则为准。
- 项目约定:Java 版本、包结构、命名方式、异常处理、日志规范和依赖限制。
- 运行素材:编译命令、测试命令、代表性日志和已知失败现象。敏感数据应先脱敏。
- 验收素材:期望输出、兼容要求、性能目标及不在本次范围内的事项。
例如,不要只写“增加 VIP 折扣”,而应说明折扣适用于哪些商品、是否可与优惠券叠加、金额的币种和精度、优惠能否超过订单金额,以及规则冲突时的优先级。对这些细节没有共识时,正确做法是标记为待确认,而不是让代码或模型自行猜测。
为 AI 准备项目上下文时,还要遵循数据最小化原则。不要提交生产密钥、访问令牌、真实个人信息或不必要的客户数据。把配置替换成占位值,把日志中的账号、地址和标识符脱敏。使用某个 AI 工具时,数据会如何处理,应以该工具的实际服务条款和组织政策为准;不能假设所有工具都默认不会保存或处理输入内容。
四、实战:用策略模式组织折扣规则
假设订单折扣会持续增加。最初只有普通订单,后来加入比例折扣和固定金额减免。如果规则少且稳定,一个简单分支足够;如果不同规则需要独立测试、持续新增或由不同模块维护,就可以把每种算法封装为策略。
下面的示例遵守几个明确的业务假设:金额必须非空且非负;金额以两位小数进行折扣计算;百分比折扣范围为 0 到 1,表示减免比例;固定减免不能超过订单金额。若真实业务采用不同币种精度、税前或税后口径,应先替换这些假设,而不是直接照搬示例。
将以下代码保存为 DiscountStrategyDemo.java,使用 JDK 8 或更高版本编译运行:
import java.math.BigDecimal;
import java.math.RoundingMode;
public class DiscountStrategyDemo {
interface DiscountStrategy {
BigDecimal discountFor(BigDecimal amount);
}
static final class NoDiscount implements DiscountStrategy {
@Override
public BigDecimal discountFor(BigDecimal amount) {
return checkedAmount(amount).setScale(2, RoundingMode.HALF_UP);
}
}
static final class PercentageDiscount implements DiscountStrategy {
private final BigDecimal rate;
PercentageDiscount(BigDecimal rate) {
if (rate == null
|| rate.compareTo(BigDecimal.ZERO) < 0
|| rate.compareTo(BigDecimal.ONE) > 0) {
throw new IllegalArgumentException(
"rate must be between 0 and 1");
}
this.rate = rate;
}
@Override
public BigDecimal discountFor(BigDecimal amount) {
BigDecimal checked = checkedAmount(amount);
return checked.multiply(rate)
.setScale(2, RoundingMode.HALF_UP);
}
}
static final class FixedDiscount implements DiscountStrategy {
private final BigDecimal deduction;
FixedDiscount(BigDecimal deduction) {
if (deduction == null
|| deduction.compareTo(BigDecimal.ZERO) < 0) {
throw new IllegalArgumentException(
"deduction must be non-negative");
}
this.deduction = deduction;
}
@Override
public BigDecimal discountFor(BigDecimal amount) {
BigDecimal checked = checkedAmount(amount);
BigDecimal capped = deduction.min(checked);
return capped.setScale(2, RoundingMode.HALF_UP);
}
}
enum DiscountType {
NONE, PERCENTAGE, FIXED
}
static final class DiscountFactory {
static DiscountStrategy create(
DiscountType type, BigDecimal parameter) {
if (type == null) {
throw new IllegalArgumentException(
"discount type must not be null");
}
switch (type) {
case NONE:
return new NoDiscount();
case PERCENTAGE:
return new PercentageDiscount(parameter);
case FIXED:
return new FixedDiscount(parameter);
default:
throw new IllegalArgumentException(
"unsupported discount type: " + type);
}
}
}
static final class CheckoutService {
BigDecimal payable(
BigDecimal amount, DiscountStrategy strategy) {
BigDecimal checked = checkedAmount(amount);
if (strategy == null) {
throw new IllegalArgumentException(
"strategy must not be null");
}
BigDecimal discount = strategy.discountFor(checked);
if (discount == null
|| discount.compareTo(BigDecimal.ZERO) < 0
|| discount.compareTo(checked) > 0) {
throw new IllegalStateException(
"strategy returned an invalid discount");
}
return checked.subtract(discount)
.setScale(2, RoundingMode.HALF_UP);
}
}
private static BigDecimal checkedAmount(BigDecimal amount) {
if (amount == null || amount.compareTo(BigDecimal.ZERO) < 0) {
throw new IllegalArgumentException(
"amount must be non-null and non-negative");
}
return amount;
}
public static void main(String[] args) {
CheckoutService checkout = new CheckoutService();
BigDecimal amount = new BigDecimal("100.00");
DiscountStrategy percentage = DiscountFactory.create(
DiscountType.PERCENTAGE, new BigDecimal("0.15"));
DiscountStrategy fixed = DiscountFactory.create(
DiscountType.FIXED, new BigDecimal("120.00"));
System.out.println(checkout.payable(
amount, new NoDiscount()));
System.out.println(checkout.payable(amount, percentage));
System.out.println(checkout.payable(amount, fixed));
}
}
预期输出为:
100.00
85.00
0.00
代码中的模式分工
DiscountStrategy定义统一策略接口,结算服务不必知道每种折扣的计算过程。NoDiscount、PercentageDiscount和FixedDiscount分别封装不同算法。DiscountFactory根据类型创建策略,将选择逻辑与结算过程分开。CheckoutService通过接口调用策略,并检查策略返回值,避免把所有算法和业务流程写在一个方法里。
这里使用 BigDecimal 而不是 double,是因为金额涉及十进制精度。通过字符串构造 BigDecimal,可以避免先经过二进制浮点数再转换所带来的表示误差。示例把折扣结果按两位小数、四舍五入处理,但“折扣在哪一步舍入”属于业务约定,不应一概认为适用于所有结算系统。
percentage 的参数限制是显式的:它不能为 null,并且必须在 0 和 1 之间。该范围来自本示例对参数的定义,表示减免比例,并不是 Java 或 BigDecimal 对参数施加的通用限制。若业务采用“百分数 15 表示 15%”的输入格式,就应改为 0 到 100,并在命名和接口文档中写清楚。
五、如何验证这段实现
可运行的 main 只能证明几个正常路径可以执行,不等于覆盖了所有边界。实际项目应使用测试框架或现有测试体系验证至少以下情况:
- 普通策略不改变金额。
- 比例为 0、1 时结果符合规则;小于 0 或大于 1 时构造策略失败。
- 固定减免为 0、等于金额或大于金额时,结果符合封顶规则。
- 金额为
null或负数时,按约定抛出参数异常。 - 策略返回负值、超过订单金额或返回
null时,结算服务拒绝该结果。 - 临界金额的舍入结果符合产品和财务确认过的规则。
示例用 IllegalArgumentException 表示调用参数不合法,用 IllegalStateException 表示策略返回违反结算约定的数据。项目如果有统一的领域异常体系,应采用项目约定,并确保调用方知道哪些异常可恢复、哪些需要上报。
在生产代码中,最好不要仅凭字符串配置随意创建策略。若折扣类型来自数据库或外部请求,应先校验允许的类型,再将经过验证的配置转换为策略。对于无法识别的类型,应明确报错或走经过产品确认的默认行为,避免静默地给用户错误价格。
六、策略模式的适用边界与常见错误
适用的情况
- 同一业务流程中存在多种可替换算法,调用方希望通过相同接口使用它们。
- 算法会独立变化,需要单独测试或由不同实现承载。
- 新增一种算法不应迫使调用方重写整段业务流程。
不一定适用的情况
- 规则只有一两条且长期稳定,普通条件判断更清楚。
- 差异只是简单常量,没有独立行为,提取出许多类反而难以导航。
- 真正变化的是整个对象创建过程,或需要在对象外层动态叠加职责。此时工厂、装饰器等其他结构可能更合适,但仍要按实际问题判断。
常见实现错误
- 只把分支搬进工厂。如果所有算法仍写在同一个巨大工厂方法里,维护问题只是换了位置。工厂通常负责创建对象,不应承担各策略的业务计算。
- 过度使用枚举。示例使用枚举是为了让类型明确、便于展示;若规则由插件动态注册,固定枚举可能不够灵活。应根据真实扩展方式选择注册表或依赖注入,而不是把示例结构机械推广。
- 让策略依赖过多隐式状态。如果策略读取可变全局变量、当前登录用户或环境状态,测试会更难,结果也可能随调用时机变化。必要依赖应通过构造器或明确参数传入。
- 忽略金额和舍入规则。折扣先舍入还是最后统一舍入,币种精度是多少,退款如何计算,都可能影响结果。
setScale(2, HALF_UP)只是示例选择,并非所有货币计算的通用答案。 - 把创建模式当成扩展能力。工厂能集中创建逻辑,却不会自动让新规则无需改代码。示例新增一种策略,仍需要增加实现并调整工厂。是否需要可配置注册机制,要看项目的扩展和部署要求。
七、AI 生成的模式代码要怎样审查
AI 生成的代码可能语法正确,却不符合项目行为。建议按以下顺序检查,而不是只看代码是否“像设计模式”:先核对业务规则,再检查公开接口和异常,再检查数据精度与并发,最后运行编译、测试和静态检查。
- 核对需求对应关系:每个新增类都应能对应一项明确变化或职责。找不到理由的抽象可能没有必要。
- 检查空值和非法值:确认校验覆盖了接口边界,而不仅是示例里的常用输入。
- 检查对象状态:策略是否包含可变字段?是否会被多个线程共享?不可变对象通常更容易安全复用,但不能仅凭“用了模式”就断言线程安全。
- 执行真实构建:示例能运行,不代表能兼容项目的 JDK、依赖版本、代码风格和构建流程。
- 审查差异范围:确认没有改动不相关文件、删除必要校验或引入未经允许的依赖。
尤其要分清“输入校验”和“安全防护”。参数范围检查可以防止不合法折扣进入结算逻辑,但它不能替代身份认证、授权、数据库访问控制或交易风控。来自客户端的金额和折扣类型不能因为前端已经校验就被信任;服务器端必须按可信业务数据重新计算或验证。
八、参数限制与安全用途的准确边界
示例中的百分比限制、金额非负和固定减免封顶,都是针对折扣计算的业务完整性检查。它们有助于发现错误输入,不能据此宣称系统具备支付安全、反欺诈或完整的财务合规能力。真实系统还需要根据架构设计身份认证、权限检查、审计、幂等、防重放和数据访问控制等措施。
提示词中也不要放入密钥、密码、签名材料或可直接访问生产系统的凭证。AI 生成的命令、SQL、配置和代码都应视为待审阅内容。特别是涉及删除数据、修改权限、执行脚本或触发付款的操作,应在受控环境中验证,并遵守项目审批和发布流程。
Java 代码中的参数校验同样不能代替数据库约束。若数据需要持久化,应根据数据模型设置恰当的唯一性、非空和范围约束,并在应用层返回可理解的错误。应用校验与数据库约束解决的问题不同:前者改善调用边界和错误反馈,后者防止数据状态被其他写入路径破坏。
九、把方法落到日常开发流程
一套可重复的实践可以浓缩为以下步骤:
- 描述问题,而不是先指定模式。记录当前分支、变化原因和具体维护成本。
- 补齐素材。提供相关代码、项目约定、样例输入和验收条件,移除敏感信息。
- 让 AI 先分析,再生成。要求说明候选方案、取舍和未知假设。
- 选择最小有效结构。只抽取确实变化的部分,避免为未来猜想提前搭建复杂框架。
- 验证边界。检查空值、范围、精度、异常、并发和外部输入的处理方式。
- 本地运行并审查。执行项目构建和测试,检查差异,再决定是否接受建议。
模式提供的是组织代码的语言,提示词提供的是传递上下文的方法,素材则决定建议能否贴近项目。三者都不能替代业务确认和工程验证。先把规则说清楚,再选择合适的结构,最后用测试和代码审查证明实现符合约定,才是 Java 与设计模式实践中更可靠的路线。









