[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"site-settings":3,"\u002Farticles\u002Fdesign-pattern-15":26,"\u002Farticles\u002Fdesign-pattern-15\u002Frelated":43,"\u002Farticles\u002Fdesign-pattern-15\u002Fadjacent":72,"\u002Fcategories":84,"render-design-pattern-15":110},{"code":4,"message":5,"data":6},0,"ok",{"author_city":7,"author_years":8,"site_tagline":9,"site_description":10,"skill_one_desc":11,"author_status":12,"skill_two_title":13,"about_hero_image":9,"skill_one_title":14,"icp":15,"author_role":16,"og_default_image":9,"closing_quote_date":17,"toolkit":18,"skill_two_desc":19,"author_bio":9,"site_footer":9,"site_url":20,"author_tagline":21,"weekly_thought":9,"tagline_line_one":22,"author_bio_two":9,"closing_quote":23,"site_name":24,"tagline_line_two":25,"home_hero_image":9},"西安","10","","花开空白 - 个人博客","熟悉Java、Python、Go等后端语言。","努力工作中","前端开发","后端开发","陕ICP备2021009682号-1","开发者","2022年·秋","Java,JavaScript,TypeScript,Python,Go","熟悉Js、Ts语言以及Vue、uniapp等框架。","https:\u002F\u002Firule.cn","很高兴遇见你。","把复杂的事，","把每次一发布，都当成一次和陌生人的握手。","花开空白","写成简单的故事。",{"code":4,"message":5,"data":27},{"id":28,"slug":29,"title":30,"summary":31,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":34,"likeCount":4,"wordCount":35,"readingMinutes":36,"publishedAt":37,"contentMd":38,"contentHtml":39,"tags":40,"metaTitle":9,"metaDescription":9,"keywords":9,"canonicalUrl":9,"robots":41,"ogImage":9,"faq":42,"aiSummary":42},"2113931965261418497","design-pattern-15","设计模式修炼之路 ⑮ 策略模式——if-else 的解药","一个分支一个类、工厂加 Map 连选择处都不写 if-else、Spring 自动收集、线程池拒绝策略","2113897628365230081",1,1681,7953,20,"2024-06-16T18:10:44","> 行为型第二站。\n\n上一篇结尾立了个 flag：你代码里那个嵌套五层、每来一个新需求就得多写一个 else if 的方法，读完这篇就有解了。\n\n解药就是它——策略模式，GoF 23 里出场率最高的模式之一，江湖人称\"if-else 杀手\"。\n\n## 一、策略模式是什么\n\n先看 GoF 的原始定义：\n\n> Define a family of algorithms, encapsulate each one, and make them interchangeable.\n> （定义一族算法，把每一个封装起来，让它们可以互相替换。）\n\n翻译成人话：**if-else 里的每个分支，抽出来变成一个独立的类，全都实现同一个接口。要用哪个，传哪个。**\n\n就这么多，没有更玄的东西了。\n\n三个角色：\n\n\u003Ctable>\u003Cthead>\u003Ctr>\u003Cth>角色\u003C\u002Fth>\u003Cth>干什么\u003C\u002Fth>\u003Cth>本文对应\u003C\u002Fth>\u003C\u002Ftr>\u003C\u002Fthead>\u003Ctbody>\u003Ctr>\u003Ctd>Strategy（策略接口）\u003C\u002Ftd>\u003Ctd>统一契约，所有算法长一个样\u003C\u002Ftd>\u003Ctd>PromotionStrategy\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>ConcreteStrategy（具体策略）\u003C\u002Ftd>\u003Ctd>一个分支一个类\u003C\u002Ftd>\u003Ctd>满减、打折、立减、N元购\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>Context（环境类）\u003C\u002Ftd>\u003Ctd>持有策略，把活儿委托出去\u003C\u002Ftd>\u003Ctd>PromotionContext\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\n\n类比：导航 App 里的路线偏好——\"时间最短\"\"距离最短\"\"避开收费\"\"不走高速\"。导航的骨架（定位、算路、播报）一毫米不动，换的只是\"怎么算路\"这一个策略。你点哪个按钮，App 就给 Context 塞哪个策略。\n\n和上一篇模板模式对比着记：**模板换的是流程里的某几步（填空题），策略换的是整个算法（换试卷）**。模板靠继承，策略靠组合——这正是 ⑭ 结尾说的\"这个时代的答案\"。\n\n## 二、优点与缺点\n\n**优点：**\n\n1. **if-else 消失，开闭原则达成**。新活动上线 = 加一个类 + 注册一行，老方法从此不动。\n2. **每个算法独立演进、独立测试**。满减要升级成阶梯满减，只改 FullReductionStrategy 一个文件，测试也只测这一个类，不用担心牵连打折。\n3. **运行时可换**。策略是个对象，对象可以换——白天满减、晚上八点切 N元购，改个字段的事。\n4. **用组合替代继承**。不烧单继承名额，想叠几个叠几个。\n\n**缺点：**\n\n1. **类膨胀**。五种活动五个类，活动多了目录里一片 Strategy，有人管这叫\"类爆炸\"。\n2. **策略暴露给调用方**。客户端得知道有哪几种策略、区别是什么，才能选。选择困难症从服务端转移到了调用端——所以本文第四节还要请工厂来救场。\n3. **简单场景是杀鸡用牛刀**。就两个固定分支、三年没变过？老老实实 if-else，可读性反而更好。**模式是给\"变化\"买的保险，不变化的代码不需要保险。**\n\n## 三、什么时候用\n\n判断标准就一条：**这些分支会不会增长？**\n\n- **业务规则会增长**：促销活动、支付渠道（微信\u002F支付宝\u002F银联）、会员等级价、物流报价\n- **算法可替换**：压缩（GZIP\u002FZIP\u002Fdeflate）、加密（AES\u002FRSA\u002FSM4）、限流（计数器\u002F滑动窗口\u002F令牌桶\u002F漏桶）\n- **同一动作多种实现**：导出 Excel\u002FPDF\u002FCSV、发送短信\u002F邮件\u002F钉钉\n\n反过来三个字：**别硬套**。两个稳定分支硬上策略模式，只会让读代码的人骂娘。\n\n## 四、实现方式：把 if-else 掰碎重装\n\n### 4.1 反面教材：促销计算巨无霸\n\n电商场景，一个方法算促销价。运营配了什么活动，就传什么类型进来：\n\n```java\npublic class BadPromotionCalculator {\n\n    public double calculate(String promotionType, double amount) {\n        if (\"满减\".equals(promotionType)) {\n            if (amount >= 300) {\n                System.out.println(\"[满减] 满 300 减 50\");\n                return amount - 50;\n            }\n            System.out.println(\"[满减] 未满 300，不参与\");\n            return amount;\n        } else if (\"打折\".equals(promotionType)) {\n            System.out.println(\"[打折] 全场 8 折\");\n            return amount * 0.8;\n        } else if (\"立减\".equals(promotionType)) {\n            System.out.println(\"[立减] 无门槛立减 10 元\");\n            return amount - 10;\n        } else if (\"N元购\".equals(promotionType)) {\n            System.out.println(\"[N元购] 一口价 9.9 元\");\n            return 9.9;\n        } else {\n            System.out.println(\"[无活动] 原价购买\");\n            return amount;\n        }\n        \u002F\u002F 运营说下个月要上\"买一送一\"和\"满额包邮\"……\n        \u002F\u002F 这个方法已经 50 行了，圈复杂度快爆表，改一处手抖全崩\n    }\n}\n```\n\n现在四个活动还勉强看得过去。但是——\n\n- 下个月运营要上\"买一送一\"，得改这里；\n- 大后天要上\"满额包邮\"，还得改这里；\n- 满减要升级成阶梯满减（满 300 减 50、满 600 减 150），继续改这里。\n\n三个月后这个方法两百行，圈复杂度爆表，每次发版回归测试要覆盖所有历史分支。**它违反开闭原则的方式和 ⑭ 的反面教材一模一样：所有变化都往一个方法里堆。**\n\n### 4.2 正解：一个分支一个类\n\n第一步，把分支的共性提炼成接口。所有活动干的是同一件事——\"给定金额，算出应付\"：\n\n```java\npublic interface PromotionStrategy {\n\n    double calculate(double amount);\n}\n```\n\n第二步，每个分支一个类：\n\n```java\npublic class FullReductionStrategy implements PromotionStrategy {\n\n    @Override\n    public double calculate(double amount) {\n        if (amount >= 300) {\n            System.out.println(\"  [满减] 满 300 减 50\");\n            return amount - 50;\n        }\n        System.out.println(\"  [满减] 未满 300，不参与\");\n        return amount;\n    }\n}\n\npublic class DiscountStrategy implements PromotionStrategy {\n\n    @Override\n    public double calculate(double amount) {\n        System.out.println(\"  [打折] 全场 8 折\");\n        return amount * 0.8;\n    }\n}\n\npublic class InstantReductionStrategy implements PromotionStrategy {\n\n    @Override\n    public double calculate(double amount) {\n        System.out.println(\"  [立减] 无门槛立减 10 元\");\n        return amount - 10;\n    }\n}\n\npublic class FixedPriceStrategy implements PromotionStrategy {\n\n    @Override\n    public double calculate(double amount) {\n        System.out.println(\"  [N元购] 一口价 9.9 元\");\n        return 9.9;\n    }\n}\n```\n\n第三步，Context 拿着策略干活：\n\n```java\npublic class PromotionContext {\n\n    private final PromotionStrategy strategy;\n\n    public PromotionContext(PromotionStrategy strategy) {\n        this.strategy = strategy;\n    }\n\n    public double checkout(double amount) {\n        return strategy.calculate(amount);\n    }\n}\n```\n\n测试类让反面教材和正解同台对比：\n\n```java\npublic class StrategyTest {\n\n    public static void main(String[] args) {\n        System.out.println(\"========== 反面教材：if-else 巨无霸 ==========\");\n        BadPromotionCalculator bad = new BadPromotionCalculator();\n        System.out.println(\"满减 350 元 -> \" + bad.calculate(\"满减\", 350));\n        System.out.println(\"打折 350 元 -> \" + bad.calculate(\"打折\", 350));\n        System.out.println(\"立减 350 元 -> \" + bad.calculate(\"立减\", 350));\n        System.out.println(\"N元购 350 元 -> \" + bad.calculate(\"N元购\", 350));\n        System.out.println(\"周年庆 350 元 -> \" + bad.calculate(\"周年庆\", 350));\n\n        System.out.println();\n        System.out.println(\"========== 正解一：显式传入策略 ==========\");\n        PromotionContext context = new PromotionContext(new FullReductionStrategy());\n        System.out.println(\"满减 350 元 -> \" + context.checkout(350));\n        System.out.println(\"满减 250 元 -> \" + context.checkout(250));\n        context = new PromotionContext(new DiscountStrategy());\n        System.out.println(\"打折 350 元 -> \" + context.checkout(350));\n        context = new PromotionContext(new FixedPriceStrategy());\n        System.out.println(\"N元购 350 元 -> \" + context.checkout(350));\n\n        System.out.println();\n        System.out.println(\"========== 正解二：工厂 + Map，选择处也不再 if-else ==========\");\n        System.out.println(\"满减 350 元 -> \" + new PromotionContext(PromotionStrategyFactory.get(\"满减\")).checkout(350));\n        System.out.println(\"立减 350 元 -> \" + new PromotionContext(PromotionStrategyFactory.get(\"立减\")).checkout(350));\n        System.out.println(\"周年庆（没配，兜底） -> \" + new PromotionContext(PromotionStrategyFactory.get(\"周年庆\")).checkout(350));\n    }\n}\n```\n\n运行输出：\n\n```text\n========== 反面教材：if-else 巨无霸 ==========\n[满减] 满 300 减 50\n满减 350 元 -> 300.0\n[打折] 全场 8 折\n打折 350 元 -> 280.0\n[立减] 无门槛立减 10 元\n立减 350 元 -> 340.0\n[N元购] 一口价 9.9 元\nN元购 350 元 -> 9.9\n[无活动] 原价购买\n周年庆 350 元 -> 350.0\n\n========== 正解一：显式传入策略 ==========\n  [满减] 满 300 减 50\n满减 350 元 -> 300.0\n  [满减] 未满 300，不参与\n满减 250 元 -> 250.0\n  [打折] 全场 8 折\n打折 350 元 -> 280.0\n  [N元购] 一口价 9.9 元\nN元购 350 元 -> 9.9\n\n========== 正解二：工厂 + Map，选择处也不再 if-else ==========\n  [满减] 满 300 减 50\n满减 350 元 -> 300.0\n  [立减] 无门槛立减 10 元\n立减 350 元 -> 340.0\n  [无活动] 原价购买\n周年庆（没配，兜底） -> 350.0\n```\n\n### 4.3 三处细节咂摸一下\n\n**细节一：满减 250 元走的是策略自己的内部逻辑。**\"未满 300 不参与\"这种判断被完整封装进了 FullReductionStrategy，外界无感知。策略的粒度是\"整个算法\"，算法内部再复杂也只污染它自己一个类。\n\n**细节二：反面教材的 else 兜底，正解里是 NoPromotionStrategy。**那个 `else return amount` 其实是一个隐藏策略——\"无活动\"。把它也变成类之后，工厂查不到任何活动时返回它，调用方永远拿到一个能干活的策略，**空指针从根上被消灭了**。这个手法有个正式的名字——空对象模式，㉓ 会专门讲，这里先混个脸熟。\n\n**细节三：测试里\"周年庆\"这个根本不存在的活动类型，两边都正常兜底返回原价。**反面教材靠 else，正解靠工厂默认值——但正解加新活动不用改一行旧代码，这才是本质区别。\n\n### 4.4 工厂救场：选择处也不写 if-else\n\n正解一还有个尾巴：调用方 `new PromotionContext(new FullReductionStrategy())`，还是得认识所有具体策略类——第二节说的\"策略暴露\"缺点没解决。而且真实项目里，活动类型是运营配置的字符串，从数据库里来。怎么办？工厂 + Map：\n\n```java\npublic class PromotionStrategyFactory {\n\n    private static final Map\u003CString, PromotionStrategy> STRATEGIES = new HashMap\u003C>();\n\n    static {\n        STRATEGIES.put(\"满减\", new FullReductionStrategy());\n        STRATEGIES.put(\"打折\", new DiscountStrategy());\n        STRATEGIES.put(\"立减\", new InstantReductionStrategy());\n        STRATEGIES.put(\"N元购\", new FixedPriceStrategy());\n        STRATEGIES.put(\"无活动\", new NoPromotionStrategy());\n    }\n\n    private PromotionStrategyFactory() {\n    }\n\n    public static PromotionStrategy get(String promotionType) {\n        PromotionStrategy strategy = STRATEGIES.get(promotionType);\n        return strategy != null ? strategy : STRATEGIES.get(\"无活动\");\n    }\n}\n```\n\n`Map.get()` 一步到位，选择处一个 if-else 都没有。新活动上线 = 写一个类 + static 块里加一行注册。\n\n### 4.5 Spring 时代的打开方式\n\n上面的 static 注册块，在 Spring 项目里有更优雅的替身——**容器自动收集**：\n\n```java\n@Component\npublic class PromotionStrategyHolder {\n\n    private final Map\u003CString, PromotionStrategy> strategyMap;\n\n    public PromotionStrategyHolder(List\u003CPromotionStrategy> strategies) {\n        this.strategyMap = strategies.stream()\n                .collect(Collectors.toMap(PromotionStrategy::type, Function.identity()));\n    }\n\n    public PromotionStrategy get(String type) {\n        return strategyMap.getOrDefault(type, strategyMap.get(\"无活动\"));\n    }\n}\n```\n\n构造器参数是 `List\u003CPromotionStrategy>`——Spring 会把容器里**所有**实现了这个接口的 bean 打包成 List 注进来。给接口加一个 `String type()` 方法返回活动编码，再让 NoPromotionStrategy 的 type() 返回\"无活动\"，stream 一把转成 Map。\n\n从此新活动上线 = 写一个带 `@Component` 的类，**连注册那一行都省了**。策略模式和 Spring 的自动装配，堪称天作之合。\n\n## 五、在框架里找它\n\n**ThreadPoolExecutor 的拒绝策略。**线程池满了之后怎么办？JDK 给了四种打法：AbortPolicy（抛异常）、CallerRunsPolicy（调用者自己跑）、DiscardPolicy（悄悄丢弃）、DiscardOldestPolicy（丢最老的再试）——四个类，全实现 `RejectedExecutionHandler` 接口，构造线程池时传哪个就是哪个：\n\n```java\nThreadPoolExecutor executor = new ThreadPoolExecutor(\n        4, 8, 60, TimeUnit.SECONDS,\n        new LinkedBlockingQueue\u003C>(10),\n        new ThreadPoolExecutor.CallerRunsPolicy());\n```\n\n想把\"抛异常\"换成\"降级到调用者执行\"？换个构造参数的事。这就是策略模式的运行时可换。\n\n**Comparator。**你每天都在用策略模式而不自知：\n\n```java\nlist.sort(Comparator.comparing(Order::getAmount));            \u002F\u002F 按金额升序\nlist.sort(Comparator.comparing(Order::getAmount).reversed()); \u002F\u002F 按金额降序\nlist.sort(Comparator.comparing(Order::getCreateTime));        \u002F\u002F 按时间排序\n```\n\n`sort` 骨架是 TimSort，不动；比大小的规则是策略，随便换。JDK 把策略模式做成了语言级语法糖——Lambda 本质上就是\"可以传来传去的轻量策略\"。\n\n**Spring 的 InstantiationStrategy。**Bean 怎么实例化？SimpleInstantiationStrategy（反射直调构造器）和 CglibSubclassingInstantiationStrategy（CGLIB 生成子类再实例化，解决方法注入）两个策略来回切。和 ⑬ 代理模式那篇里的 CGLIB 又碰上了。\n\n## 六、策略的黄金搭档\n\n策略模式很少单打独斗，它有两个固定搭档：\n\n**策略 + 工厂**：本文 4.4 已示范。工厂负责\"造和选\"，策略负责\"干\"。这一对组合是面试手写题的钉子户——\"用策略模式 + 工厂模式重构这段 if-else\"，出现频率高到值得单独准备。\n\n**策略 + 空对象**：NoPromotionStrategy 那个兜底位。与其让调用方到处判空，不如给\"没有\"也造一个行为正常的类。㉓ 见。\n\n最后泼一盆冷水：**if-else 不是原罪**。条件分支是计算机科学最伟大的发明之一，罪过只在于\"会增长的分支\"挤在一个方法里。策略模式不是消灭 if-else，而是把每个 if 背后的逻辑送进各自的类，让**变化的影响半径从整个方法缩到一个类**。衡量要不要重构，就看这棵 if-else 树半年内长了几片新叶子。\n\n## 写在最后\n\n策略模式一句话总结：**一族可替换的算法，一个接口统帅，一个类一支兵，Context 指哪打哪。**\n\n回顾它和前几篇的关系：⑭ 模板模式管\"流程里哪几步变化\"（填空），⑮ 策略模式管\"整个算法换掉\"（换人）。一个靠继承，一个靠组合——行为型双雄，就此到齐了一半。\n\n下一篇 ⑯ 观察者模式——策略解决的是\"该用哪个算法\"，观察者解决的是\"该通知谁\"。订单支付成功后要发短信、加积分、扣库存、通知物流……你会怎么写？在 OrderService 里依次调四个方法？那每接一个新下游，下单代码就得改一次。一对多依赖、事件订阅与广播——观察者模式，下一篇见。","\u003Cblockquote>\n \u003Cp>行为型第二站。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>上一篇结尾立了个 flag：你代码里那个嵌套五层、每来一个新需求就得多写一个 else if 的方法，读完这篇就有解了。\u003C\u002Fp>\n\u003Cp>解药就是它——策略模式，GoF 23 里出场率最高的模式之一，江湖人称\"if-else 杀手\"。\u003C\u002Fp>\n\u003Ch2>一、策略模式是什么\u003C\u002Fh2>\n\u003Cp>先看 GoF 的原始定义：\u003C\u002Fp>\n\u003Cblockquote>\n \u003Cp>Define a family of algorithms, encapsulate each one, and make them interchangeable. （定义一族算法，把每一个封装起来，让它们可以互相替换。）\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>翻译成人话：\u003Cstrong>if-else 里的每个分支，抽出来变成一个独立的类，全都实现同一个接口。要用哪个，传哪个。\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cp>就这么多，没有更玄的东西了。\u003C\u002Fp>\n\u003Cp>三个角色：\u003C\u002Fp>\n\u003Ctable>\n \u003Cthead>\n  \u003Ctr>\n   \u003Cth>角色\u003C\u002Fth>\n   \u003Cth>干什么\u003C\u002Fth>\n   \u003Cth>本文对应\u003C\u002Fth>\n  \u003C\u002Ftr>\n \u003C\u002Fthead>\n \u003Ctbody>\n  \u003Ctr>\n   \u003Ctd>Strategy（策略接口）\u003C\u002Ftd>\n   \u003Ctd>统一契约，所有算法长一个样\u003C\u002Ftd>\n   \u003Ctd>PromotionStrategy\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>ConcreteStrategy（具体策略）\u003C\u002Ftd>\n   \u003Ctd>一个分支一个类\u003C\u002Ftd>\n   \u003Ctd>满减、打折、立减、N元购\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>Context（环境类）\u003C\u002Ftd>\n   \u003Ctd>持有策略，把活儿委托出去\u003C\u002Ftd>\n   \u003Ctd>PromotionContext\u003C\u002Ftd>\n  \u003C\u002Ftr>\n \u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>类比：导航 App 里的路线偏好——\"时间最短\"\"距离最短\"\"避开收费\"\"不走高速\"。导航的骨架（定位、算路、播报）一毫米不动，换的只是\"怎么算路\"这一个策略。你点哪个按钮，App 就给 Context 塞哪个策略。\u003C\u002Fp>\n\u003Cp>和上一篇模板模式对比着记：\u003Cstrong>模板换的是流程里的某几步（填空题），策略换的是整个算法（换试卷）\u003C\u002Fstrong>。模板靠继承，策略靠组合——这正是 ⑭ 结尾说的\"这个时代的答案\"。\u003C\u002Fp>\n\u003Ch2>二、优点与缺点\u003C\u002Fh2>\n\u003Cp>\u003Cstrong>优点：\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Col>\n \u003Cli>\u003Cstrong>if-else 消失，开闭原则达成\u003C\u002Fstrong>。新活动上线 = 加一个类 + 注册一行，老方法从此不动。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>每个算法独立演进、独立测试\u003C\u002Fstrong>。满减要升级成阶梯满减，只改 FullReductionStrategy 一个文件，测试也只测这一个类，不用担心牵连打折。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>运行时可换\u003C\u002Fstrong>。策略是个对象，对象可以换——白天满减、晚上八点切 N元购，改个字段的事。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>用组合替代继承\u003C\u002Fstrong>。不烧单继承名额，想叠几个叠几个。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>\u003Cstrong>缺点：\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Col>\n \u003Cli>\u003Cstrong>类膨胀\u003C\u002Fstrong>。五种活动五个类，活动多了目录里一片 Strategy，有人管这叫\"类爆炸\"。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>策略暴露给调用方\u003C\u002Fstrong>。客户端得知道有哪几种策略、区别是什么，才能选。选择困难症从服务端转移到了调用端——所以本文第四节还要请工厂来救场。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>简单场景是杀鸡用牛刀\u003C\u002Fstrong>。就两个固定分支、三年没变过？老老实实 if-else，可读性反而更好。\u003Cstrong>模式是给\"变化\"买的保险，不变化的代码不需要保险。\u003C\u002Fstrong>\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2>三、什么时候用\u003C\u002Fh2>\n\u003Cp>判断标准就一条：\u003Cstrong>这些分支会不会增长？\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cul>\n \u003Cli>\u003Cstrong>业务规则会增长\u003C\u002Fstrong>：促销活动、支付渠道（微信\u002F支付宝\u002F银联）、会员等级价、物流报价\u003C\u002Fli>\n \u003Cli>\u003Cstrong>算法可替换\u003C\u002Fstrong>：压缩（GZIP\u002FZIP\u002Fdeflate）、加密（AES\u002FRSA\u002FSM4）、限流（计数器\u002F滑动窗口\u002F令牌桶\u002F漏桶）\u003C\u002Fli>\n \u003Cli>\u003Cstrong>同一动作多种实现\u003C\u002Fstrong>：导出 Excel\u002FPDF\u002FCSV、发送短信\u002F邮件\u002F钉钉\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>反过来三个字：\u003Cstrong>别硬套\u003C\u002Fstrong>。两个稳定分支硬上策略模式，只会让读代码的人骂娘。\u003C\u002Fp>\n\u003Ch2>四、实现方式：把 if-else 掰碎重装\u003C\u002Fh2>\n\u003Ch3>4.1 反面教材：促销计算巨无霸\u003C\u002Fh3>\n\u003Cp>电商场景，一个方法算促销价。运营配了什么活动，就传什么类型进来：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">public class BadPromotionCalculator {\n\n    public double calculate(String promotionType, double amount) {\n        if (\"满减\".equals(promotionType)) {\n            if (amount &gt;= 300) {\n                System.out.println(\"[满减] 满 300 减 50\");\n                return amount - 50;\n            }\n            System.out.println(\"[满减] 未满 300，不参与\");\n            return amount;\n        } else if (\"打折\".equals(promotionType)) {\n            System.out.println(\"[打折] 全场 8 折\");\n            return amount * 0.8;\n        } else if (\"立减\".equals(promotionType)) {\n            System.out.println(\"[立减] 无门槛立减 10 元\");\n            return amount - 10;\n        } else if (\"N元购\".equals(promotionType)) {\n            System.out.println(\"[N元购] 一口价 9.9 元\");\n            return 9.9;\n        } else {\n            System.out.println(\"[无活动] 原价购买\");\n            return amount;\n        }\n        \u002F\u002F 运营说下个月要上\"买一送一\"和\"满额包邮\"……\n        \u002F\u002F 这个方法已经 50 行了，圈复杂度快爆表，改一处手抖全崩\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>现在四个活动还勉强看得过去。但是——\u003C\u002Fp>\n\u003Cul>\n \u003Cli>下个月运营要上\"买一送一\"，得改这里；\u003C\u002Fli>\n \u003Cli>大后天要上\"满额包邮\"，还得改这里；\u003C\u002Fli>\n \u003Cli>满减要升级成阶梯满减（满 300 减 50、满 600 减 150），继续改这里。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>三个月后这个方法两百行，圈复杂度爆表，每次发版回归测试要覆盖所有历史分支。\u003Cstrong>它违反开闭原则的方式和 ⑭ 的反面教材一模一样：所有变化都往一个方法里堆。\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Ch3>4.2 正解：一个分支一个类\u003C\u002Fh3>\n\u003Cp>第一步，把分支的共性提炼成接口。所有活动干的是同一件事——\"给定金额，算出应付\"：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">public interface PromotionStrategy {\n\n    double calculate(double amount);\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>第二步，每个分支一个类：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">public class FullReductionStrategy implements PromotionStrategy {\n\n    @Override\n    public double calculate(double amount) {\n        if (amount &gt;= 300) {\n            System.out.println(\"  [满减] 满 300 减 50\");\n            return amount - 50;\n        }\n        System.out.println(\"  [满减] 未满 300，不参与\");\n        return amount;\n    }\n}\n\npublic class DiscountStrategy implements PromotionStrategy {\n\n    @Override\n    public double calculate(double amount) {\n        System.out.println(\"  [打折] 全场 8 折\");\n        return amount * 0.8;\n    }\n}\n\npublic class InstantReductionStrategy implements PromotionStrategy {\n\n    @Override\n    public double calculate(double amount) {\n        System.out.println(\"  [立减] 无门槛立减 10 元\");\n        return amount - 10;\n    }\n}\n\npublic class FixedPriceStrategy implements PromotionStrategy {\n\n    @Override\n    public double calculate(double amount) {\n        System.out.println(\"  [N元购] 一口价 9.9 元\");\n        return 9.9;\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>第三步，Context 拿着策略干活：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">public class PromotionContext {\n\n    private final PromotionStrategy strategy;\n\n    public PromotionContext(PromotionStrategy strategy) {\n        this.strategy = strategy;\n    }\n\n    public double checkout(double amount) {\n        return strategy.calculate(amount);\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>测试类让反面教材和正解同台对比：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">public class StrategyTest {\n\n    public static void main(String[] args) {\n        System.out.println(\"========== 反面教材：if-else 巨无霸 ==========\");\n        BadPromotionCalculator bad = new BadPromotionCalculator();\n        System.out.println(\"满减 350 元 -&gt; \" + bad.calculate(\"满减\", 350));\n        System.out.println(\"打折 350 元 -&gt; \" + bad.calculate(\"打折\", 350));\n        System.out.println(\"立减 350 元 -&gt; \" + bad.calculate(\"立减\", 350));\n        System.out.println(\"N元购 350 元 -&gt; \" + bad.calculate(\"N元购\", 350));\n        System.out.println(\"周年庆 350 元 -&gt; \" + bad.calculate(\"周年庆\", 350));\n\n        System.out.println();\n        System.out.println(\"========== 正解一：显式传入策略 ==========\");\n        PromotionContext context = new PromotionContext(new FullReductionStrategy());\n        System.out.println(\"满减 350 元 -&gt; \" + context.checkout(350));\n        System.out.println(\"满减 250 元 -&gt; \" + context.checkout(250));\n        context = new PromotionContext(new DiscountStrategy());\n        System.out.println(\"打折 350 元 -&gt; \" + context.checkout(350));\n        context = new PromotionContext(new FixedPriceStrategy());\n        System.out.println(\"N元购 350 元 -&gt; \" + context.checkout(350));\n\n        System.out.println();\n        System.out.println(\"========== 正解二：工厂 + Map，选择处也不再 if-else ==========\");\n        System.out.println(\"满减 350 元 -&gt; \" + new PromotionContext(PromotionStrategyFactory.get(\"满减\")).checkout(350));\n        System.out.println(\"立减 350 元 -&gt; \" + new PromotionContext(PromotionStrategyFactory.get(\"立减\")).checkout(350));\n        System.out.println(\"周年庆（没配，兜底） -&gt; \" + new PromotionContext(PromotionStrategyFactory.get(\"周年庆\")).checkout(350));\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>运行输出：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">========== 反面教材：if-else 巨无霸 ==========\n[满减] 满 300 减 50\n满减 350 元 -&gt; 300.0\n[打折] 全场 8 折\n打折 350 元 -&gt; 280.0\n[立减] 无门槛立减 10 元\n立减 350 元 -&gt; 340.0\n[N元购] 一口价 9.9 元\nN元购 350 元 -&gt; 9.9\n[无活动] 原价购买\n周年庆 350 元 -&gt; 350.0\n\n========== 正解一：显式传入策略 ==========\n  [满减] 满 300 减 50\n满减 350 元 -&gt; 300.0\n  [满减] 未满 300，不参与\n满减 250 元 -&gt; 250.0\n  [打折] 全场 8 折\n打折 350 元 -&gt; 280.0\n  [N元购] 一口价 9.9 元\nN元购 350 元 -&gt; 9.9\n\n========== 正解二：工厂 + Map，选择处也不再 if-else ==========\n  [满减] 满 300 减 50\n满减 350 元 -&gt; 300.0\n  [立减] 无门槛立减 10 元\n立减 350 元 -&gt; 340.0\n  [无活动] 原价购买\n周年庆（没配，兜底） -&gt; 350.0\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>4.3 三处细节咂摸一下\u003C\u002Fh3>\n\u003Cp>\u003Cstrong>细节一：满减 250 元走的是策略自己的内部逻辑。\u003C\u002Fstrong>\"未满 300 不参与\"这种判断被完整封装进了 FullReductionStrategy，外界无感知。策略的粒度是\"整个算法\"，算法内部再复杂也只污染它自己一个类。\u003C\u002Fp>\n\u003Cp>**细节二：反面教材的 else 兜底，正解里是 NoPromotionStrategy。**那个 \u003Ccode>else return amount\u003C\u002Fcode> 其实是一个隐藏策略——\"无活动\"。把它也变成类之后，工厂查不到任何活动时返回它，调用方永远拿到一个能干活的策略，\u003Cstrong>空指针从根上被消灭了\u003C\u002Fstrong>。这个手法有个正式的名字——空对象模式，㉓ 会专门讲，这里先混个脸熟。\u003C\u002Fp>\n\u003Cp>**细节三：测试里\"周年庆\"这个根本不存在的活动类型，两边都正常兜底返回原价。**反面教材靠 else，正解靠工厂默认值——但正解加新活动不用改一行旧代码，这才是本质区别。\u003C\u002Fp>\n\u003Ch3>4.4 工厂救场：选择处也不写 if-else\u003C\u002Fh3>\n\u003Cp>正解一还有个尾巴：调用方 \u003Ccode>new PromotionContext(new FullReductionStrategy())\u003C\u002Fcode>，还是得认识所有具体策略类——第二节说的\"策略暴露\"缺点没解决。而且真实项目里，活动类型是运营配置的字符串，从数据库里来。怎么办？工厂 + Map：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">public class PromotionStrategyFactory {\n\n    private static final Map&lt;String, PromotionStrategy&gt; STRATEGIES = new HashMap&lt;&gt;();\n\n    static {\n        STRATEGIES.put(\"满减\", new FullReductionStrategy());\n        STRATEGIES.put(\"打折\", new DiscountStrategy());\n        STRATEGIES.put(\"立减\", new InstantReductionStrategy());\n        STRATEGIES.put(\"N元购\", new FixedPriceStrategy());\n        STRATEGIES.put(\"无活动\", new NoPromotionStrategy());\n    }\n\n    private PromotionStrategyFactory() {\n    }\n\n    public static PromotionStrategy get(String promotionType) {\n        PromotionStrategy strategy = STRATEGIES.get(promotionType);\n        return strategy != null ? strategy : STRATEGIES.get(\"无活动\");\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Ccode>Map.get()\u003C\u002Fcode> 一步到位，选择处一个 if-else 都没有。新活动上线 = 写一个类 + static 块里加一行注册。\u003C\u002Fp>\n\u003Ch3>4.5 Spring 时代的打开方式\u003C\u002Fh3>\n\u003Cp>上面的 static 注册块，在 Spring 项目里有更优雅的替身——\u003Cstrong>容器自动收集\u003C\u002Fstrong>：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">@Component\npublic class PromotionStrategyHolder {\n\n    private final Map&lt;String, PromotionStrategy&gt; strategyMap;\n\n    public PromotionStrategyHolder(List&lt;PromotionStrategy&gt; strategies) {\n        this.strategyMap = strategies.stream()\n                .collect(Collectors.toMap(PromotionStrategy::type, Function.identity()));\n    }\n\n    public PromotionStrategy get(String type) {\n        return strategyMap.getOrDefault(type, strategyMap.get(\"无活动\"));\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>构造器参数是 \u003Ccode>List&lt;PromotionStrategy&gt;\u003C\u002Fcode>——Spring 会把容器里\u003Cstrong>所有\u003C\u002Fstrong>实现了这个接口的 bean 打包成 List 注进来。给接口加一个 \u003Ccode>String type()\u003C\u002Fcode> 方法返回活动编码，再让 NoPromotionStrategy 的 type() 返回\"无活动\"，stream 一把转成 Map。\u003C\u002Fp>\n\u003Cp>从此新活动上线 = 写一个带 \u003Ccode>@Component\u003C\u002Fcode> 的类，\u003Cstrong>连注册那一行都省了\u003C\u002Fstrong>。策略模式和 Spring 的自动装配，堪称天作之合。\u003C\u002Fp>\n\u003Ch2>五、在框架里找它\u003C\u002Fh2>\n\u003Cp>**ThreadPoolExecutor 的拒绝策略。**线程池满了之后怎么办？JDK 给了四种打法：AbortPolicy（抛异常）、CallerRunsPolicy（调用者自己跑）、DiscardPolicy（悄悄丢弃）、DiscardOldestPolicy（丢最老的再试）——四个类，全实现 \u003Ccode>RejectedExecutionHandler\u003C\u002Fcode> 接口，构造线程池时传哪个就是哪个：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">ThreadPoolExecutor executor = new ThreadPoolExecutor(\n        4, 8, 60, TimeUnit.SECONDS,\n        new LinkedBlockingQueue&lt;&gt;(10),\n        new ThreadPoolExecutor.CallerRunsPolicy());\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>想把\"抛异常\"换成\"降级到调用者执行\"？换个构造参数的事。这就是策略模式的运行时可换。\u003C\u002Fp>\n\u003Cp>**Comparator。**你每天都在用策略模式而不自知：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">list.sort(Comparator.comparing(Order::getAmount));            \u002F\u002F 按金额升序\nlist.sort(Comparator.comparing(Order::getAmount).reversed()); \u002F\u002F 按金额降序\nlist.sort(Comparator.comparing(Order::getCreateTime));        \u002F\u002F 按时间排序\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Ccode>sort\u003C\u002Fcode> 骨架是 TimSort，不动；比大小的规则是策略，随便换。JDK 把策略模式做成了语言级语法糖——Lambda 本质上就是\"可以传来传去的轻量策略\"。\u003C\u002Fp>\n\u003Cp>**Spring 的 InstantiationStrategy。**Bean 怎么实例化？SimpleInstantiationStrategy（反射直调构造器）和 CglibSubclassingInstantiationStrategy（CGLIB 生成子类再实例化，解决方法注入）两个策略来回切。和 ⑬ 代理模式那篇里的 CGLIB 又碰上了。\u003C\u002Fp>\n\u003Ch2>六、策略的黄金搭档\u003C\u002Fh2>\n\u003Cp>策略模式很少单打独斗，它有两个固定搭档：\u003C\u002Fp>\n\u003Cp>\u003Cstrong>策略 + 工厂\u003C\u002Fstrong>：本文 4.4 已示范。工厂负责\"造和选\"，策略负责\"干\"。这一对组合是面试手写题的钉子户——\"用策略模式 + 工厂模式重构这段 if-else\"，出现频率高到值得单独准备。\u003C\u002Fp>\n\u003Cp>\u003Cstrong>策略 + 空对象\u003C\u002Fstrong>：NoPromotionStrategy 那个兜底位。与其让调用方到处判空，不如给\"没有\"也造一个行为正常的类。㉓ 见。\u003C\u002Fp>\n\u003Cp>最后泼一盆冷水：\u003Cstrong>if-else 不是原罪\u003C\u002Fstrong>。条件分支是计算机科学最伟大的发明之一，罪过只在于\"会增长的分支\"挤在一个方法里。策略模式不是消灭 if-else，而是把每个 if 背后的逻辑送进各自的类，让\u003Cstrong>变化的影响半径从整个方法缩到一个类\u003C\u002Fstrong>。衡量要不要重构，就看这棵 if-else 树半年内长了几片新叶子。\u003C\u002Fp>\n\u003Ch2>写在最后\u003C\u002Fh2>\n\u003Cp>策略模式一句话总结：\u003Cstrong>一族可替换的算法，一个接口统帅，一个类一支兵，Context 指哪打哪。\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cp>回顾它和前几篇的关系：⑭ 模板模式管\"流程里哪几步变化\"（填空），⑮ 策略模式管\"整个算法换掉\"（换人）。一个靠继承，一个靠组合——行为型双雄，就此到齐了一半。\u003C\u002Fp>\n\u003Cp>下一篇 ⑯ 观察者模式——策略解决的是\"该用哪个算法\"，观察者解决的是\"该通知谁\"。订单支付成功后要发短信、加积分、扣库存、通知物流……你会怎么写？在 OrderService 里依次调四个方法？那每接一个新下游，下单代码就得改一次。一对多依赖、事件订阅与广播——观察者模式，下一篇见。\u003C\u002Fp>",[],"index,follow",null,{"code":4,"message":5,"data":44},[45,54,63],{"id":46,"slug":47,"title":48,"summary":49,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":50,"likeCount":4,"wordCount":51,"readingMinutes":52,"publishedAt":53},"2113933842262790145","design-pattern-19","设计模式修炼之路 ⑲ 状态模式——状态驱动行为","状态驱动行为、流转规则住进状态家、Thread 状态机、状态 vs 策略终局对决",1320,6715,17,"2024-09-02T16:09:32",{"id":55,"slug":56,"title":57,"summary":58,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":59,"likeCount":4,"wordCount":60,"readingMinutes":61,"publishedAt":62},"2113907939247919106","design-pattern-01","设计模式修炼之路 ① 初识设计模式——先把地图摊开","这是系列的第一篇，没有一行代码：设计模式是什么、模式分哪四大家族、六大原则长什么样。先把地图摊开，路从下一篇开始走。",3268,1903,5,"2023-09-10T12:19:35",{"id":64,"slug":65,"title":66,"summary":67,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":68,"likeCount":4,"wordCount":69,"readingMinutes":70,"publishedAt":71},"2113932151245246465","design-pattern-16","设计模式修炼之路 ⑯ 观察者模式——事件订阅与广播","一对多依赖、订阅退订自由、生产环境三块拼图、Spring 事件机制、观察者 vs 发布订阅",3150,6331,16,"2024-07-07T14:57:29",{"code":4,"message":5,"data":73},{"prev":74,"next":83},{"id":75,"slug":76,"title":77,"summary":78,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":79,"likeCount":4,"wordCount":80,"readingMinutes":81,"publishedAt":82},"2113933745433088002","design-pattern-14","设计模式修炼之路 ⑭ 模板模式——焊死骨架，留白细节","final 焊死流程骨架、四种方法各司其职、钩子是选答题、从 Servlet 到 AQS 全是填空",3418,8834,23,"2024-06-13T15:33:43",{"id":64,"slug":65,"title":66,"summary":67,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":68,"likeCount":4,"wordCount":69,"readingMinutes":70,"publishedAt":71},{"code":4,"message":5,"data":85},[86,92,97,103],{"id":87,"name":88,"slug":89,"description":90,"sortOrder":4,"articleCount":91},"1946000000000000001","antlr4","tech","编程与技术笔记","13",{"id":32,"name":93,"slug":94,"description":95,"sortOrder":4,"articleCount":96},"设计模式","design-pattern","设计模式系列","34",{"id":98,"name":99,"slug":100,"description":101,"sortOrder":33,"articleCount":102},"1946000000000000002","随笔","essay","生活与思考","0",{"id":104,"name":105,"slug":106,"description":107,"sortOrder":108,"articleCount":109},"2105200093764698113","工具","tools","好用的工具推荐",2,"1",{"html":111,"toc":112},"\u003Cblockquote>\n \u003Cp>行为型第二站。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>上一篇结尾立了个 flag：你代码里那个嵌套五层、每来一个新需求就得多写一个 else if 的方法，读完这篇就有解了。\u003C\u002Fp>\n\u003Cp>解药就是它——策略模式，GoF 23 里出场率最高的模式之一，江湖人称\"if-else 杀手\"。\u003C\u002Fp>\n\u003Ch2 id=\"toc-1\">一、策略模式是什么\u003C\u002Fh2>\n\u003Cp>先看 GoF 的原始定义：\u003C\u002Fp>\n\u003Cblockquote>\n \u003Cp>Define a family of algorithms, encapsulate each one, and make them interchangeable. （定义一族算法，把每一个封装起来，让它们可以互相替换。）\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>翻译成人话：\u003Cstrong>if-else 里的每个分支，抽出来变成一个独立的类，全都实现同一个接口。要用哪个，传哪个。\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cp>就这么多，没有更玄的东西了。\u003C\u002Fp>\n\u003Cp>三个角色：\u003C\u002Fp>\n\u003Ctable>\n \u003Cthead>\n  \u003Ctr>\n   \u003Cth>角色\u003C\u002Fth>\n   \u003Cth>干什么\u003C\u002Fth>\n   \u003Cth>本文对应\u003C\u002Fth>\n  \u003C\u002Ftr>\n \u003C\u002Fthead>\n \u003Ctbody>\n  \u003Ctr>\n   \u003Ctd>Strategy（策略接口）\u003C\u002Ftd>\n   \u003Ctd>统一契约，所有算法长一个样\u003C\u002Ftd>\n   \u003Ctd>PromotionStrategy\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>ConcreteStrategy（具体策略）\u003C\u002Ftd>\n   \u003Ctd>一个分支一个类\u003C\u002Ftd>\n   \u003Ctd>满减、打折、立减、N元购\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>Context（环境类）\u003C\u002Ftd>\n   \u003Ctd>持有策略，把活儿委托出去\u003C\u002Ftd>\n   \u003Ctd>PromotionContext\u003C\u002Ftd>\n  \u003C\u002Ftr>\n \u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>类比：导航 App 里的路线偏好——\"时间最短\"\"距离最短\"\"避开收费\"\"不走高速\"。导航的骨架（定位、算路、播报）一毫米不动，换的只是\"怎么算路\"这一个策略。你点哪个按钮，App 就给 Context 塞哪个策略。\u003C\u002Fp>\n\u003Cp>和上一篇模板模式对比着记：\u003Cstrong>模板换的是流程里的某几步（填空题），策略换的是整个算法（换试卷）\u003C\u002Fstrong>。模板靠继承，策略靠组合——这正是 ⑭ 结尾说的\"这个时代的答案\"。\u003C\u002Fp>\n\u003Ch2 id=\"toc-2\">二、优点与缺点\u003C\u002Fh2>\n\u003Cp>\u003Cstrong>优点：\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Col>\n \u003Cli>\u003Cstrong>if-else 消失，开闭原则达成\u003C\u002Fstrong>。新活动上线 = 加一个类 + 注册一行，老方法从此不动。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>每个算法独立演进、独立测试\u003C\u002Fstrong>。满减要升级成阶梯满减，只改 FullReductionStrategy 一个文件，测试也只测这一个类，不用担心牵连打折。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>运行时可换\u003C\u002Fstrong>。策略是个对象，对象可以换——白天满减、晚上八点切 N元购，改个字段的事。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>用组合替代继承\u003C\u002Fstrong>。不烧单继承名额，想叠几个叠几个。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>\u003Cstrong>缺点：\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Col>\n \u003Cli>\u003Cstrong>类膨胀\u003C\u002Fstrong>。五种活动五个类，活动多了目录里一片 Strategy，有人管这叫\"类爆炸\"。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>策略暴露给调用方\u003C\u002Fstrong>。客户端得知道有哪几种策略、区别是什么，才能选。选择困难症从服务端转移到了调用端——所以本文第四节还要请工厂来救场。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>简单场景是杀鸡用牛刀\u003C\u002Fstrong>。就两个固定分支、三年没变过？老老实实 if-else，可读性反而更好。\u003Cstrong>模式是给\"变化\"买的保险，不变化的代码不需要保险。\u003C\u002Fstrong>\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2 id=\"toc-3\">三、什么时候用\u003C\u002Fh2>\n\u003Cp>判断标准就一条：\u003Cstrong>这些分支会不会增长？\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cul>\n \u003Cli>\u003Cstrong>业务规则会增长\u003C\u002Fstrong>：促销活动、支付渠道（微信\u002F支付宝\u002F银联）、会员等级价、物流报价\u003C\u002Fli>\n \u003Cli>\u003Cstrong>算法可替换\u003C\u002Fstrong>：压缩（GZIP\u002FZIP\u002Fdeflate）、加密（AES\u002FRSA\u002FSM4）、限流（计数器\u002F滑动窗口\u002F令牌桶\u002F漏桶）\u003C\u002Fli>\n \u003Cli>\u003Cstrong>同一动作多种实现\u003C\u002Fstrong>：导出 Excel\u002FPDF\u002FCSV、发送短信\u002F邮件\u002F钉钉\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>反过来三个字：\u003Cstrong>别硬套\u003C\u002Fstrong>。两个稳定分支硬上策略模式，只会让读代码的人骂娘。\u003C\u002Fp>\n\u003Ch2 id=\"toc-4\">四、实现方式：把 if-else 掰碎重装\u003C\u002Fh2>\n\u003Ch3 id=\"toc-5\">4.1 反面教材：促销计算巨无霸\u003C\u002Fh3>\n\u003Cp>电商场景，一个方法算促销价。运营配了什么活动，就传什么类型进来：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> BadPromotionCalculator\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> double\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> calculate\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(String \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">promotionType\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">double\u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\"> amount\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        if\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"满减\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">equals\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(promotionType)) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">            if\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (amount \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">>=\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 300\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">                System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"[满减] 满 300 减 50\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">                return\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> amount \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">-\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 50\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">            }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">            System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"[满减] 未满 300，不参与\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">            return\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> amount;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        } \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">else\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> if\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"打折\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">equals\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(promotionType)) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">            System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"[打折] 全场 8 折\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">            return\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> amount \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">*\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 0.8\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        } \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">else\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> if\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"立减\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">equals\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(promotionType)) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">            System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"[立减] 无门槛立减 10 元\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">            return\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> amount \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">-\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 10\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        } \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">else\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> if\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"N元购\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">equals\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(promotionType)) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">            System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"[N元购] 一口价 9.9 元\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">            return\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 9.9\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        } \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">else\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">            System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"[无活动] 原价购买\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">            return\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> amount;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">        \u002F\u002F 运营说下个月要上\"买一送一\"和\"满额包邮\"……\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">        \u002F\u002F 这个方法已经 50 行了，圈复杂度快爆表，改一处手抖全崩\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>现在四个活动还勉强看得过去。但是——\u003C\u002Fp>\n\u003Cul>\n \u003Cli>下个月运营要上\"买一送一\"，得改这里；\u003C\u002Fli>\n \u003Cli>大后天要上\"满额包邮\"，还得改这里；\u003C\u002Fli>\n \u003Cli>满减要升级成阶梯满减（满 300 减 50、满 600 减 150），继续改这里。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>三个月后这个方法两百行，圈复杂度爆表，每次发版回归测试要覆盖所有历史分支。\u003Cstrong>它违反开闭原则的方式和 ⑭ 的反面教材一模一样：所有变化都往一个方法里堆。\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Ch3 id=\"toc-6\">4.2 正解：一个分支一个类\u003C\u002Fh3>\n\u003Cp>第一步，把分支的共性提炼成接口。所有活动干的是同一件事——\"给定金额，算出应付\"：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> interface\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> PromotionStrategy\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    double\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> calculate\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">double\u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\"> amount\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>第二步，每个分支一个类：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> FullReductionStrategy\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> implements\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> PromotionStrategy\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    @\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Override\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> double\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> calculate\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">double\u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\"> amount\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        if\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (amount \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">>=\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 300\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">            System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"  [满减] 满 300 减 50\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">            return\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> amount \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">-\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 50\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"  [满减] 未满 300，不参与\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        return\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> amount;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> DiscountStrategy\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> implements\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> PromotionStrategy\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    @\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Override\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> double\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> calculate\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">double\u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\"> amount\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"  [打折] 全场 8 折\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        return\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> amount \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">*\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 0.8\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> InstantReductionStrategy\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> implements\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> PromotionStrategy\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    @\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Override\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> double\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> calculate\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">double\u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\"> amount\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"  [立减] 无门槛立减 10 元\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        return\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> amount \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">-\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 10\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> FixedPriceStrategy\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> implements\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> PromotionStrategy\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    @\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Override\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> double\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> calculate\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">double\u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\"> amount\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"  [N元购] 一口价 9.9 元\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        return\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 9.9\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>第三步，Context 拿着策略干活：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> PromotionContext\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> final\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> PromotionStrategy strategy;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> PromotionContext\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(PromotionStrategy \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">strategy\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">        this\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">.strategy \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> strategy;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> double\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> checkout\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">double\u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\"> amount\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        return\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> strategy.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">calculate\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(amount);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>测试类让反面教材和正解同台对比：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> StrategyTest\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> static\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> main\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">String\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">[] \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">args\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"========== 反面教材：if-else 巨无霸 ==========\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        BadPromotionCalculator bad \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> BadPromotionCalculator\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"满减 350 元 -> \"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> bad.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">calculate\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"满减\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">350\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">));\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"打折 350 元 -> \"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> bad.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">calculate\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"打折\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">350\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">));\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"立减 350 元 -> \"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> bad.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">calculate\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"立减\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">350\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">));\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"N元购 350 元 -> \"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> bad.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">calculate\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"N元购\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">350\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">));\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"周年庆 350 元 -> \"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> bad.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">calculate\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"周年庆\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">350\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">));\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"========== 正解一：显式传入策略 ==========\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        PromotionContext context \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> PromotionContext\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> FullReductionStrategy\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">());\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"满减 350 元 -> \"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> context.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">checkout\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">350\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">));\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"满减 250 元 -> \"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> context.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">checkout\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">250\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">));\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        context \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> PromotionContext\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> DiscountStrategy\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">());\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"打折 350 元 -> \"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> context.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">checkout\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">350\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">));\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        context \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> PromotionContext\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> FixedPriceStrategy\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">());\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"N元购 350 元 -> \"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> context.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">checkout\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">350\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">));\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"========== 正解二：工厂 + Map，选择处也不再 if-else ==========\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"满减 350 元 -> \"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> PromotionContext\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(PromotionStrategyFactory.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">get\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"满减\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">)).\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">checkout\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">350\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">));\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"立减 350 元 -> \"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> PromotionContext\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(PromotionStrategyFactory.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">get\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"立减\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">)).\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">checkout\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">350\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">));\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"周年庆（没配，兜底） -> \"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> PromotionContext\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(PromotionStrategyFactory.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">get\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"周年庆\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">)).\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">checkout\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">350\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">));\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>运行输出：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan>========== 反面教材：if-else 巨无霸 ==========\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>[满减] 满 300 减 50\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>满减 350 元 -> 300.0\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>[打折] 全场 8 折\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>打折 350 元 -> 280.0\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>[立减] 无门槛立减 10 元\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>立减 350 元 -> 340.0\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>[N元购] 一口价 9.9 元\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>N元购 350 元 -> 9.9\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>[无活动] 原价购买\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>周年庆 350 元 -> 350.0\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>========== 正解一：显式传入策略 ==========\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [满减] 满 300 减 50\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>满减 350 元 -> 300.0\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [满减] 未满 300，不参与\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>满减 250 元 -> 250.0\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [打折] 全场 8 折\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>打折 350 元 -> 280.0\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [N元购] 一口价 9.9 元\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>N元购 350 元 -> 9.9\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>========== 正解二：工厂 + Map，选择处也不再 if-else ==========\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [满减] 满 300 减 50\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>满减 350 元 -> 300.0\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [立减] 无门槛立减 10 元\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>立减 350 元 -> 340.0\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [无活动] 原价购买\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>周年庆（没配，兜底） -> 350.0\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3 id=\"toc-7\">4.3 三处细节咂摸一下\u003C\u002Fh3>\n\u003Cp>\u003Cstrong>细节一：满减 250 元走的是策略自己的内部逻辑。\u003C\u002Fstrong>\"未满 300 不参与\"这种判断被完整封装进了 FullReductionStrategy，外界无感知。策略的粒度是\"整个算法\"，算法内部再复杂也只污染它自己一个类。\u003C\u002Fp>\n\u003Cp>**细节二：反面教材的 else 兜底，正解里是 NoPromotionStrategy。**那个 \u003Ccode>else return amount\u003C\u002Fcode> 其实是一个隐藏策略——\"无活动\"。把它也变成类之后，工厂查不到任何活动时返回它，调用方永远拿到一个能干活的策略，\u003Cstrong>空指针从根上被消灭了\u003C\u002Fstrong>。这个手法有个正式的名字——空对象模式，㉓ 会专门讲，这里先混个脸熟。\u003C\u002Fp>\n\u003Cp>**细节三：测试里\"周年庆\"这个根本不存在的活动类型，两边都正常兜底返回原价。**反面教材靠 else，正解靠工厂默认值——但正解加新活动不用改一行旧代码，这才是本质区别。\u003C\u002Fp>\n\u003Ch3 id=\"toc-8\">4.4 工厂救场：选择处也不写 if-else\u003C\u002Fh3>\n\u003Cp>正解一还有个尾巴：调用方 \u003Ccode>new PromotionContext(new FullReductionStrategy())\u003C\u002Fcode>，还是得认识所有具体策略类——第二节说的\"策略暴露\"缺点没解决。而且真实项目里，活动类型是运营配置的字符串，从数据库里来。怎么办？工厂 + Map：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> PromotionStrategyFactory\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> static\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> final\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> Map&#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">String\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">PromotionStrategy\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">> STRATEGIES \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> HashMap&#x3C;>();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    static\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        STRATEGIES.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">put\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"满减\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> FullReductionStrategy\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">());\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        STRATEGIES.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">put\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"打折\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> DiscountStrategy\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">());\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        STRATEGIES.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">put\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"立减\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> InstantReductionStrategy\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">());\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        STRATEGIES.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">put\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"N元购\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> FixedPriceStrategy\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">());\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        STRATEGIES.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">put\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"无活动\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> NoPromotionStrategy\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">());\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    private\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> PromotionStrategyFactory\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> static\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> PromotionStrategy \u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">get\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(String \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">promotionType\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        PromotionStrategy strategy \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> STRATEGIES.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">get\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(promotionType);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        return\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> strategy \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">!=\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> null\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> ?\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> strategy \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">:\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> STRATEGIES.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">get\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"无活动\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Ccode>Map.get()\u003C\u002Fcode> 一步到位，选择处一个 if-else 都没有。新活动上线 = 写一个类 + static 块里加一行注册。\u003C\u002Fp>\n\u003Ch3 id=\"toc-9\">4.5 Spring 时代的打开方式\u003C\u002Fh3>\n\u003Cp>上面的 static 注册块，在 Spring 项目里有更优雅的替身——\u003Cstrong>容器自动收集\u003C\u002Fstrong>：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">@\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Component\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> PromotionStrategyHolder\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> final\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> Map&#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">String\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">PromotionStrategy\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">> strategyMap;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> PromotionStrategyHolder\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(List&#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">PromotionStrategy\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">> \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">strategies\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">        this\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">.strategyMap \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> strategies.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">stream\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">()\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">                .\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">collect\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(Collectors.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">toMap\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(PromotionStrategy\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">::\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">type, Function.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">identity\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">()));\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> PromotionStrategy \u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">get\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(String \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">type\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        return\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> strategyMap.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getOrDefault\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(type, strategyMap.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">get\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"无活动\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">));\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>构造器参数是 \u003Ccode>List&lt;PromotionStrategy&gt;\u003C\u002Fcode>——Spring 会把容器里\u003Cstrong>所有\u003C\u002Fstrong>实现了这个接口的 bean 打包成 List 注进来。给接口加一个 \u003Ccode>String type()\u003C\u002Fcode> 方法返回活动编码，再让 NoPromotionStrategy 的 type() 返回\"无活动\"，stream 一把转成 Map。\u003C\u002Fp>\n\u003Cp>从此新活动上线 = 写一个带 \u003Ccode>@Component\u003C\u002Fcode> 的类，\u003Cstrong>连注册那一行都省了\u003C\u002Fstrong>。策略模式和 Spring 的自动装配，堪称天作之合。\u003C\u002Fp>\n\u003Ch2 id=\"toc-10\">五、在框架里找它\u003C\u002Fh2>\n\u003Cp>**ThreadPoolExecutor 的拒绝策略。**线程池满了之后怎么办？JDK 给了四种打法：AbortPolicy（抛异常）、CallerRunsPolicy（调用者自己跑）、DiscardPolicy（悄悄丢弃）、DiscardOldestPolicy（丢最老的再试）——四个类，全实现 \u003Ccode>RejectedExecutionHandler\u003C\u002Fcode> 接口，构造线程池时传哪个就是哪个：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">ThreadPoolExecutor executor \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> ThreadPoolExecutor\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">        4\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">8\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">60\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, TimeUnit.SECONDS,\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        new\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> LinkedBlockingQueue&#x3C;>(\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">10\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">),\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        new\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> ThreadPoolExecutor.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">CallerRunsPolicy\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">());\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>想把\"抛异常\"换成\"降级到调用者执行\"？换个构造参数的事。这就是策略模式的运行时可换。\u003C\u002Fp>\n\u003Cp>**Comparator。**你每天都在用策略模式而不自知：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">list.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">sort\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(Comparator.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">comparing\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(Order\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">::\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">getAmount));            \u003C\u002Fspan>\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F 按金额升序\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">list.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">sort\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(Comparator.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">comparing\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(Order\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">::\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">getAmount).\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">reversed\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">()); \u003C\u002Fspan>\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F 按金额降序\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">list.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">sort\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(Comparator.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">comparing\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(Order\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">::\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">getCreateTime));        \u003C\u002Fspan>\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F 按时间排序\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Ccode>sort\u003C\u002Fcode> 骨架是 TimSort，不动；比大小的规则是策略，随便换。JDK 把策略模式做成了语言级语法糖——Lambda 本质上就是\"可以传来传去的轻量策略\"。\u003C\u002Fp>\n\u003Cp>**Spring 的 InstantiationStrategy。**Bean 怎么实例化？SimpleInstantiationStrategy（反射直调构造器）和 CglibSubclassingInstantiationStrategy（CGLIB 生成子类再实例化，解决方法注入）两个策略来回切。和 ⑬ 代理模式那篇里的 CGLIB 又碰上了。\u003C\u002Fp>\n\u003Ch2 id=\"toc-11\">六、策略的黄金搭档\u003C\u002Fh2>\n\u003Cp>策略模式很少单打独斗，它有两个固定搭档：\u003C\u002Fp>\n\u003Cp>\u003Cstrong>策略 + 工厂\u003C\u002Fstrong>：本文 4.4 已示范。工厂负责\"造和选\"，策略负责\"干\"。这一对组合是面试手写题的钉子户——\"用策略模式 + 工厂模式重构这段 if-else\"，出现频率高到值得单独准备。\u003C\u002Fp>\n\u003Cp>\u003Cstrong>策略 + 空对象\u003C\u002Fstrong>：NoPromotionStrategy 那个兜底位。与其让调用方到处判空，不如给\"没有\"也造一个行为正常的类。㉓ 见。\u003C\u002Fp>\n\u003Cp>最后泼一盆冷水：\u003Cstrong>if-else 不是原罪\u003C\u002Fstrong>。条件分支是计算机科学最伟大的发明之一，罪过只在于\"会增长的分支\"挤在一个方法里。策略模式不是消灭 if-else，而是把每个 if 背后的逻辑送进各自的类，让\u003Cstrong>变化的影响半径从整个方法缩到一个类\u003C\u002Fstrong>。衡量要不要重构，就看这棵 if-else 树半年内长了几片新叶子。\u003C\u002Fp>\n\u003Ch2 id=\"toc-12\">写在最后\u003C\u002Fh2>\n\u003Cp>策略模式一句话总结：\u003Cstrong>一族可替换的算法，一个接口统帅，一个类一支兵，Context 指哪打哪。\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cp>回顾它和前几篇的关系：⑭ 模板模式管\"流程里哪几步变化\"（填空），⑮ 策略模式管\"整个算法换掉\"（换人）。一个靠继承，一个靠组合——行为型双雄，就此到齐了一半。\u003C\u002Fp>\n\u003Cp>下一篇 ⑯ 观察者模式——策略解决的是\"该用哪个算法\"，观察者解决的是\"该通知谁\"。订单支付成功后要发短信、加积分、扣库存、通知物流……你会怎么写？在 OrderService 里依次调四个方法？那每接一个新下游，下单代码就得改一次。一对多依赖、事件订阅与广播——观察者模式，下一篇见。\u003C\u002Fp>",[113,116,119,122,125,129,132,135,138,141,144,147],{"id":114,"text":115,"level":108},"toc-1","一、策略模式是什么",{"id":117,"text":118,"level":108},"toc-2","二、优点与缺点",{"id":120,"text":121,"level":108},"toc-3","三、什么时候用",{"id":123,"text":124,"level":108},"toc-4","四、实现方式：把 if-else 掰碎重装",{"id":126,"text":127,"level":128},"toc-5","4.1 反面教材：促销计算巨无霸",3,{"id":130,"text":131,"level":128},"toc-6","4.2 正解：一个分支一个类",{"id":133,"text":134,"level":128},"toc-7","4.3 三处细节咂摸一下",{"id":136,"text":137,"level":128},"toc-8","4.4 工厂救场：选择处也不写 if-else",{"id":139,"text":140,"level":128},"toc-9","4.5 Spring 时代的打开方式",{"id":142,"text":143,"level":108},"toc-10","五、在框架里找它",{"id":145,"text":146,"level":108},"toc-11","六、策略的黄金搭档",{"id":148,"text":149,"level":108},"toc-12","写在最后"]