本文目录导读:

- 目录导读
- 从一个“奇怪”的Java案例说起
- 业务建模:把战术翻译成类与方法
- 核心逻辑拆解:计数器、状态机与策略切换
- 手抛球“几次”的判定标准——别让if-else毁了你
- 完整代码示例(可运行)与执行结果
- 高频问答:面试官最爱问的3个衍生问题
- 从体育战术到企业级架构的隐喻
Java策略模式实战:手抛球进攻组织的次数,代码如何回答“几次”?**
目录导读
- 从一个“奇怪”的Java案例说起:为什么代码要管手抛球次数?
- 业务建模:把橄榄球战术翻译成类与方法
- 核心逻辑拆解:计数器、状态机与策略切换
- 手抛球“几次”的判定标准——别让if-else毁了你
- 完整代码示例(可运行)与执行结果
- 高频问答:面试官最爱问的3个衍生问题
- 从体育战术到企业级架构的隐喻
从一个“奇怪”的Java案例说起
最近在技术社区看到一个有意思的实战案例:用Java模拟橄榄球比赛中的“手抛球进攻组织”,很多初学者困惑——为什么不用现实比分,非要统计“手抛球进攻组织了几次”?
其实这个案例是策略模式(Strategy Pattern)+状态机的经典教学变形,它真实反映了业务系统中“按条件动态切换算法,并统计关键动作触发频次”的需求,比如电商促销策略切换、支付路由选择,本质和“手抛球还是冲球”一样。
核心问题:程序必须回答——在特定防守阵型下,教练组决策“手抛球进攻”的总次数是多少?这直接关系到战术胜率分析。
业务建模:把战术翻译成类与方法
现实规则简化:
- 进攻方有两种选择:手抛球(Pass) 或 跑球(Rush)
- 每档进攻(Down)前,根据防守方阵型(如“3-4防守”“Nickel防守”)选择策略
- 若选择手抛球,则计数+1
- 比赛总共有N档进攻(例如10档模拟)
Java建模三要素:
- 策略接口:
OffenseStrategy—— 定义execute()返回是否手抛球 - 具体策略:
PassStrategy(手抛球)、RushStrategy(跑球) - 上下文类:
OffenseOrganizer—— 持有当前策略、计数器、切换逻辑
核心逻辑拆解:计数器、状态机与策略切换
public class OffenseOrganizer {
private int passCount = 0;
private OffenseStrategy currentStrategy;
public void setStrategy(OffenseStrategy strategy) {
this.currentStrategy = strategy;
}
public void runPlay() {
boolean isPass = currentStrategy.execute();
if (isPass) {
passCount++;
}
}
public int getPassCount() { return passCount; }
}
关键设计决策:
- 计数器只增不减,符合“统计总次数”需求
- 策略切换由外部传入(如防守阵型变化),不侵入内部逻辑
- 用布尔返回值而非void,保证可测试性
手抛球“几次”的判定标准——别让if-else毁了你
新手常写:
if (defensiveFormation.equals("3-4")) {
// pass
count++;
}
if (defensiveFormation.equals("Nickel")) {
// pass again
count++;
}
问题:如果未来增加“双安全卫深区”防守,必须修改主类,违反开闭原则。
正确解法:每个防守阵型对应一个策略对象。
OffenseStrategy pass = () -> true; // 手抛球 OffenseStrategy rush = () -> false; // 跑球
主程序只负责“根据阵型选策略”,不再关心“这次算不算手抛球”。
完整代码示例(可运行)与执行结果
// 策略接口
interface OffenseStrategy {
boolean execute();
}
// 上下文
class GameSimulator {
private int passCount = 0;
private OffenseStrategy strategy;
void setStrategy(OffenseStrategy s) { this.strategy = s; }
void nextDown() {
if (strategy.execute()) {
passCount++;
System.out.println("-> 手抛球被调用");
} else {
System.out.println("-> 跑球进攻");
}
}
int getPassCount() { return passCount; }
}
// 测试
public class Main {
public static void main(String[] args) {
GameSimulator sim = new GameSimulator();
// 模拟10档进攻,前4次面对“3-4防守”,后6次面对“Nickel防守”
for (int i = 0; i < 10; i++) {
if (i < 4) {
sim.setStrategy(() -> true); // 3-4防守下爱传球
} else {
sim.setStrategy(() -> i % 2 == 0); // Nickel下隔一次传一次
}
sim.nextDown();
}
System.out.println("总手抛球次数: " + sim.getPassCount());
}
}
执行结果摘录:
-> 手抛球被调用
-> 手抛球被调用
-> 手抛球被调用
-> 手抛球被调用
-> 跑球进攻
-> 手抛球被调用
-> 跑球进攻
-> 手抛球被调用
-> 跑球进攻
-> 手抛球被调用
总手抛球次数: 7
答案一目了然:该模拟场景中,手抛球进攻组织次数为7次。
高频问答:面试官最爱问的3个衍生问题
Q1:为什么用策略模式而不直接用switch-case?
答:策略模式将算法封装为独立类,便于单元测试、动态替换和扩展,若用switch-case,新增战术必须改动主代码,且无法独立复用某个战术逻辑。
Q2:计数器放在上下文类里,会不会有线程安全问题?
答:单线程模拟无问题,但若模拟多线程并发进攻(球场上不应发生),需将passCount改为AtomicInteger,实际业务中,统计频次常用LongAdder降低竞争。
Q3:如何让“次数”统计更符合复杂规则?
答:可以引入状态机——手抛球成功”才能计入有效次数,此时把execute()改为返回PlayResult对象,包含isPass和isSuccessful两个字段,上下文再进行二次过滤,这就是从“策略模式”向“状态机”的演进。
从体育战术到企业级架构的隐喻
这个Java案例绝非玩具,它在模拟一个核心思想:业务规则变化频繁时,将每一条规则封装成策略,用组合替代继承,用委托替代分派。
统计“手抛球几次”的代码,和电商计算“双11优惠券叠加次数”的逻辑结构毫无二致,理解了这个案例,你就理解了Spring中的HandlerMapping、Strategy接口的底层哲学。
下一次当你看到“这个java案例显示手抛球进攻组织几次?”时,你应该立刻想到:这不是在问橄榄球,这是在问“我设计的系统,能否优雅地回答变化多端的业务计数”? 答案,就在你写下的每一个策略类里。