策略模式最常见的落地场景,就是一段代码里出现了越来越长的 if-else。
比如登录方式有账号密码、短信验证码、微信授权、QQ 授权;支付方式有支付宝、微信、银行卡;优惠计算有满减、折扣、会员价。每一种方式都是一套独立算法,但调用入口又是同一个。
一句话概括:策略模式把一组可替换的算法封装成独立策略类,业务流程只依赖统一接口,不再关心具体算法怎么实现。
从 if-else 开始看问题
先看一个登录例子:
public LoginResp login(LoginReq loginReq) {
if ("account".equals(loginReq.getType())) {
return accountLogin(loginReq);
} else if ("sms".equals(loginReq.getType())) {
return smsLogin(loginReq);
} else if ("we_chat".equals(loginReq.getType())) {
return weChatLogin(loginReq);
}
LoginResp resp = new LoginResp();
resp.setSuccess(false);
return resp;
}
这段代码短的时候还能接受,一旦登录方式继续增加,就会出现几个问题:
| 分支越来越长 | 主流程越来越难读 |
| 新增方式要改核心方法 | 违反开闭原则 |
| 每个分支逻辑差异大 | 测试和维护成本变高 |
| 业务入口承担太多职责 | 登录流程和登录算法耦合 |
#mermaid-svg-BfFSSH1RUl698F9G{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-BfFSSH1RUl698F9G .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-BfFSSH1RUl698F9G .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-BfFSSH1RUl698F9G .error-icon{fill:#552222;}#mermaid-svg-BfFSSH1RUl698F9G .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-BfFSSH1RUl698F9G .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-BfFSSH1RUl698F9G .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-BfFSSH1RUl698F9G .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-BfFSSH1RUl698F9G .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-BfFSSH1RUl698F9G .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-BfFSSH1RUl698F9G .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-BfFSSH1RUl698F9G .marker{fill:#333333;stroke:#333333;}#mermaid-svg-BfFSSH1RUl698F9G .marker.cross{stroke:#333333;}#mermaid-svg-BfFSSH1RUl698F9G svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-BfFSSH1RUl698F9G p{margin:0;}#mermaid-svg-BfFSSH1RUl698F9G .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-BfFSSH1RUl698F9G .cluster-label text{fill:#333;}#mermaid-svg-BfFSSH1RUl698F9G .cluster-label span{color:#333;}#mermaid-svg-BfFSSH1RUl698F9G .cluster-label span p{background-color:transparent;}#mermaid-svg-BfFSSH1RUl698F9G .label text,#mermaid-svg-BfFSSH1RUl698F9G span{fill:#333;color:#333;}#mermaid-svg-BfFSSH1RUl698F9G .node rect,#mermaid-svg-BfFSSH1RUl698F9G .node circle,#mermaid-svg-BfFSSH1RUl698F9G .node ellipse,#mermaid-svg-BfFSSH1RUl698F9G .node polygon,#mermaid-svg-BfFSSH1RUl698F9G .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-BfFSSH1RUl698F9G .rough-node .label text,#mermaid-svg-BfFSSH1RUl698F9G .node .label text,#mermaid-svg-BfFSSH1RUl698F9G .image-shape .label,#mermaid-svg-BfFSSH1RUl698F9G .icon-shape .label{text-anchor:middle;}#mermaid-svg-BfFSSH1RUl698F9G .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-BfFSSH1RUl698F9G .rough-node .label,#mermaid-svg-BfFSSH1RUl698F9G .node .label,#mermaid-svg-BfFSSH1RUl698F9G .image-shape .label,#mermaid-svg-BfFSSH1RUl698F9G .icon-shape .label{text-align:center;}#mermaid-svg-BfFSSH1RUl698F9G .node.clickable{cursor:pointer;}#mermaid-svg-BfFSSH1RUl698F9G .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-BfFSSH1RUl698F9G .arrowheadPath{fill:#333333;}#mermaid-svg-BfFSSH1RUl698F9G .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-BfFSSH1RUl698F9G .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-BfFSSH1RUl698F9G .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-BfFSSH1RUl698F9G .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-BfFSSH1RUl698F9G .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-BfFSSH1RUl698F9G .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-BfFSSH1RUl698F9G .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-BfFSSH1RUl698F9G .cluster text{fill:#333;}#mermaid-svg-BfFSSH1RUl698F9G .cluster span{color:#333;}#mermaid-svg-BfFSSH1RUl698F9G div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-BfFSSH1RUl698F9G .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-BfFSSH1RUl698F9G rect.text{fill:none;stroke-width:0;}#mermaid-svg-BfFSSH1RUl698F9G .icon-shape,#mermaid-svg-BfFSSH1RUl698F9G .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-BfFSSH1RUl698F9G .icon-shape p,#mermaid-svg-BfFSSH1RUl698F9G .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-BfFSSH1RUl698F9G .icon-shape .label rect,#mermaid-svg-BfFSSH1RUl698F9G .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-BfFSSH1RUl698F9G .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-BfFSSH1RUl698F9G .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-BfFSSH1RUl698F9G :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
account
sms
we_chat
登录入口
type 是什么
账号密码登录
短信验证码登录
微信授权登录
QQ 授权登录
返回登录结果
策略模式就是要把这些分支拆出去。
策略模式的三个角色
策略模式通常有三个角色:
| 抽象策略 | 定义所有策略都要实现的接口 |
| 具体策略 | 实现某一种具体算法 |
| 上下文 | 持有策略对象,并在合适时机调用 |
登录场景可以这样抽象:
public interface UserGranter {
LoginResp login(LoginReq loginReq);
}
账号登录策略:
@Component
public class AccountGranter implements UserGranter {
@Override
public LoginResp login(LoginReq loginReq) {
// 账号密码登录逻辑
return new LoginResp();
}
}
短信登录策略:
@Component
public class SmsGranter implements UserGranter {
@Override
public LoginResp login(LoginReq loginReq) {
// 手机验证码登录逻辑
return new LoginResp();
}
}
整体关系变成这样:
#mermaid-svg-0aFTplxiiy8D3yuw{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-0aFTplxiiy8D3yuw .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-0aFTplxiiy8D3yuw .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-0aFTplxiiy8D3yuw .error-icon{fill:#552222;}#mermaid-svg-0aFTplxiiy8D3yuw .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-0aFTplxiiy8D3yuw .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-0aFTplxiiy8D3yuw .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-0aFTplxiiy8D3yuw .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-0aFTplxiiy8D3yuw .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-0aFTplxiiy8D3yuw .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-0aFTplxiiy8D3yuw .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-0aFTplxiiy8D3yuw .marker{fill:#333333;stroke:#333333;}#mermaid-svg-0aFTplxiiy8D3yuw .marker.cross{stroke:#333333;}#mermaid-svg-0aFTplxiiy8D3yuw svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-0aFTplxiiy8D3yuw p{margin:0;}#mermaid-svg-0aFTplxiiy8D3yuw .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-0aFTplxiiy8D3yuw .cluster-label text{fill:#333;}#mermaid-svg-0aFTplxiiy8D3yuw .cluster-label span{color:#333;}#mermaid-svg-0aFTplxiiy8D3yuw .cluster-label span p{background-color:transparent;}#mermaid-svg-0aFTplxiiy8D3yuw .label text,#mermaid-svg-0aFTplxiiy8D3yuw span{fill:#333;color:#333;}#mermaid-svg-0aFTplxiiy8D3yuw .node rect,#mermaid-svg-0aFTplxiiy8D3yuw .node circle,#mermaid-svg-0aFTplxiiy8D3yuw .node ellipse,#mermaid-svg-0aFTplxiiy8D3yuw .node polygon,#mermaid-svg-0aFTplxiiy8D3yuw .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-0aFTplxiiy8D3yuw .rough-node .label text,#mermaid-svg-0aFTplxiiy8D3yuw .node .label text,#mermaid-svg-0aFTplxiiy8D3yuw .image-shape .label,#mermaid-svg-0aFTplxiiy8D3yuw .icon-shape .label{text-anchor:middle;}#mermaid-svg-0aFTplxiiy8D3yuw .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-0aFTplxiiy8D3yuw .rough-node .label,#mermaid-svg-0aFTplxiiy8D3yuw .node .label,#mermaid-svg-0aFTplxiiy8D3yuw .image-shape .label,#mermaid-svg-0aFTplxiiy8D3yuw .icon-shape .label{text-align:center;}#mermaid-svg-0aFTplxiiy8D3yuw .node.clickable{cursor:pointer;}#mermaid-svg-0aFTplxiiy8D3yuw .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-0aFTplxiiy8D3yuw .arrowheadPath{fill:#333333;}#mermaid-svg-0aFTplxiiy8D3yuw .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-0aFTplxiiy8D3yuw .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-0aFTplxiiy8D3yuw .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-0aFTplxiiy8D3yuw .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-0aFTplxiiy8D3yuw .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-0aFTplxiiy8D3yuw .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-0aFTplxiiy8D3yuw .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-0aFTplxiiy8D3yuw .cluster text{fill:#333;}#mermaid-svg-0aFTplxiiy8D3yuw .cluster span{color:#333;}#mermaid-svg-0aFTplxiiy8D3yuw div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-0aFTplxiiy8D3yuw .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-0aFTplxiiy8D3yuw rect.text{fill:none;stroke-width:0;}#mermaid-svg-0aFTplxiiy8D3yuw .icon-shape,#mermaid-svg-0aFTplxiiy8D3yuw .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-0aFTplxiiy8D3yuw .icon-shape p,#mermaid-svg-0aFTplxiiy8D3yuw .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-0aFTplxiiy8D3yuw .icon-shape .label rect,#mermaid-svg-0aFTplxiiy8D3yuw .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-0aFTplxiiy8D3yuw .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-0aFTplxiiy8D3yuw .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-0aFTplxiiy8D3yuw :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
UserService
UserGranter
AccountGranter
SmsGranter
WeChatGranter
账号密码登录算法
短信验证码登录算法
微信授权登录算法
业务入口不再关心每种登录方式的细节,只关心拿到哪个策略。
策略模式为什么常和工厂一起用
策略模式解决“算法怎么封装”,但还有一个问题:运行时怎么找到对应策略。
如果你手动写:
if ("account".equals(type)) {
return new AccountGranter();
}
那只是把 if-else 从登录方法挪到了另一个地方。
更常见的做法是“策略 + 工厂 + Spring 容器”。所有策略交给 Spring 管理,工厂负责按照类型找到对应 Bean。
配置可以长这样:
login:
types:
account: accountGranter
sms: smsGranter
we_chat: weChatGranter
工厂维护一个策略池。这里建议用构造器注入,不要用 static 可变 Map,也不要在工厂里到处拿 ApplicationContext。原因是策略映射属于这个工厂对象的状态,启动时一次性构建并校验,后续只读会更稳。
@Component
public class UserLoginFactory {
private final Map<String, UserGranter> granterPool;
public UserLoginFactory(LoginTypeConfig loginTypeConfig,
Map<String, UserGranter> granterBeans) {
this.granterPool = loginTypeConfig.getTypes().entrySet().stream()
.collect(Collectors.toUnmodifiableMap(
Map.Entry::getKey,
entry -> {
UserGranter granter = granterBeans.get(entry.getValue());
if (granter == null) {
throw new IllegalStateException(
"missing login granter bean: " + entry.getValue());
}
return granter;
}));
}
public UserGranter getGranter(String grantType) {
return granterPool.get(grantType);
}
}
Map<String, UserGranter> 由 Spring 注入时,key 默认是 Bean 名称。上面这段代码会把配置里的业务类型,比如 account,映射到真正的策略 Bean,比如 accountGranter。如果配置写错,启动阶段直接失败,比等到用户登录时才报空指针更容易定位。
调用方只做一件事:
public LoginResp login(LoginReq loginReq) {
UserGranter granter = factory.getGranter(loginReq.getType());
if (granter == null) {
LoginResp resp = new LoginResp();
resp.setSuccess(false);
return resp;
}
return granter.login(loginReq);
}
#mermaid-svg-0MXG5dipfyFiATTe{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-0MXG5dipfyFiATTe .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-0MXG5dipfyFiATTe .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-0MXG5dipfyFiATTe .error-icon{fill:#552222;}#mermaid-svg-0MXG5dipfyFiATTe .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-0MXG5dipfyFiATTe .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-0MXG5dipfyFiATTe .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-0MXG5dipfyFiATTe .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-0MXG5dipfyFiATTe .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-0MXG5dipfyFiATTe .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-0MXG5dipfyFiATTe .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-0MXG5dipfyFiATTe .marker{fill:#333333;stroke:#333333;}#mermaid-svg-0MXG5dipfyFiATTe .marker.cross{stroke:#333333;}#mermaid-svg-0MXG5dipfyFiATTe svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-0MXG5dipfyFiATTe p{margin:0;}#mermaid-svg-0MXG5dipfyFiATTe .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-0MXG5dipfyFiATTe .cluster-label text{fill:#333;}#mermaid-svg-0MXG5dipfyFiATTe .cluster-label span{color:#333;}#mermaid-svg-0MXG5dipfyFiATTe .cluster-label span p{background-color:transparent;}#mermaid-svg-0MXG5dipfyFiATTe .label text,#mermaid-svg-0MXG5dipfyFiATTe span{fill:#333;color:#333;}#mermaid-svg-0MXG5dipfyFiATTe .node rect,#mermaid-svg-0MXG5dipfyFiATTe .node circle,#mermaid-svg-0MXG5dipfyFiATTe .node ellipse,#mermaid-svg-0MXG5dipfyFiATTe .node polygon,#mermaid-svg-0MXG5dipfyFiATTe .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-0MXG5dipfyFiATTe .rough-node .label text,#mermaid-svg-0MXG5dipfyFiATTe .node .label text,#mermaid-svg-0MXG5dipfyFiATTe .image-shape .label,#mermaid-svg-0MXG5dipfyFiATTe .icon-shape .label{text-anchor:middle;}#mermaid-svg-0MXG5dipfyFiATTe .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-0MXG5dipfyFiATTe .rough-node .label,#mermaid-svg-0MXG5dipfyFiATTe .node .label,#mermaid-svg-0MXG5dipfyFiATTe .image-shape .label,#mermaid-svg-0MXG5dipfyFiATTe .icon-shape .label{text-align:center;}#mermaid-svg-0MXG5dipfyFiATTe .node.clickable{cursor:pointer;}#mermaid-svg-0MXG5dipfyFiATTe .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-0MXG5dipfyFiATTe .arrowheadPath{fill:#333333;}#mermaid-svg-0MXG5dipfyFiATTe .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-0MXG5dipfyFiATTe .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-0MXG5dipfyFiATTe .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-0MXG5dipfyFiATTe .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-0MXG5dipfyFiATTe .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-0MXG5dipfyFiATTe .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-0MXG5dipfyFiATTe .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-0MXG5dipfyFiATTe .cluster text{fill:#333;}#mermaid-svg-0MXG5dipfyFiATTe .cluster span{color:#333;}#mermaid-svg-0MXG5dipfyFiATTe div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-0MXG5dipfyFiATTe .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-0MXG5dipfyFiATTe rect.text{fill:none;stroke-width:0;}#mermaid-svg-0MXG5dipfyFiATTe .icon-shape,#mermaid-svg-0MXG5dipfyFiATTe .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-0MXG5dipfyFiATTe .icon-shape p,#mermaid-svg-0MXG5dipfyFiATTe .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-0MXG5dipfyFiATTe .icon-shape .label rect,#mermaid-svg-0MXG5dipfyFiATTe .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-0MXG5dipfyFiATTe .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-0MXG5dipfyFiATTe .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-0MXG5dipfyFiATTe :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
否
是
登录请求
读取 type
UserLoginFactory
策略池 Map
是否找到策略
返回登录失败
调用 UserGranter.login
返回登录结果
这样新增 QQ 登录时,不需要改 UserService:
策略模式适合哪些业务
只要满足“同一入口,多套可替换规则”,就可以考虑策略模式。
| 登录 | 账号、短信、微信、QQ |
| 支付 | 支付宝、微信、银行卡、余额 |
| 优惠 | 满减、折扣、会员价、优惠券 |
| Excel 解析 | xls、xlsx、csv |
| 运费计算 | 按重量、按地区、按会员等级 |
| 消息发送 | 短信、邮件、站内信、企业微信 |
#mermaid-svg-2hEjNYiRMkrQOP0b{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-2hEjNYiRMkrQOP0b .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-2hEjNYiRMkrQOP0b .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-2hEjNYiRMkrQOP0b .error-icon{fill:#552222;}#mermaid-svg-2hEjNYiRMkrQOP0b .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-2hEjNYiRMkrQOP0b .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-2hEjNYiRMkrQOP0b .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-2hEjNYiRMkrQOP0b .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-2hEjNYiRMkrQOP0b .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-2hEjNYiRMkrQOP0b .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-2hEjNYiRMkrQOP0b .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-2hEjNYiRMkrQOP0b .marker{fill:#333333;stroke:#333333;}#mermaid-svg-2hEjNYiRMkrQOP0b .marker.cross{stroke:#333333;}#mermaid-svg-2hEjNYiRMkrQOP0b svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-2hEjNYiRMkrQOP0b p{margin:0;}#mermaid-svg-2hEjNYiRMkrQOP0b .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-2hEjNYiRMkrQOP0b .cluster-label text{fill:#333;}#mermaid-svg-2hEjNYiRMkrQOP0b .cluster-label span{color:#333;}#mermaid-svg-2hEjNYiRMkrQOP0b .cluster-label span p{background-color:transparent;}#mermaid-svg-2hEjNYiRMkrQOP0b .label text,#mermaid-svg-2hEjNYiRMkrQOP0b span{fill:#333;color:#333;}#mermaid-svg-2hEjNYiRMkrQOP0b .node rect,#mermaid-svg-2hEjNYiRMkrQOP0b .node circle,#mermaid-svg-2hEjNYiRMkrQOP0b .node ellipse,#mermaid-svg-2hEjNYiRMkrQOP0b .node polygon,#mermaid-svg-2hEjNYiRMkrQOP0b .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-2hEjNYiRMkrQOP0b .rough-node .label text,#mermaid-svg-2hEjNYiRMkrQOP0b .node .label text,#mermaid-svg-2hEjNYiRMkrQOP0b .image-shape .label,#mermaid-svg-2hEjNYiRMkrQOP0b .icon-shape .label{text-anchor:middle;}#mermaid-svg-2hEjNYiRMkrQOP0b .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-2hEjNYiRMkrQOP0b .rough-node .label,#mermaid-svg-2hEjNYiRMkrQOP0b .node .label,#mermaid-svg-2hEjNYiRMkrQOP0b .image-shape .label,#mermaid-svg-2hEjNYiRMkrQOP0b .icon-shape .label{text-align:center;}#mermaid-svg-2hEjNYiRMkrQOP0b .node.clickable{cursor:pointer;}#mermaid-svg-2hEjNYiRMkrQOP0b .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-2hEjNYiRMkrQOP0b .arrowheadPath{fill:#333333;}#mermaid-svg-2hEjNYiRMkrQOP0b .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-2hEjNYiRMkrQOP0b .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-2hEjNYiRMkrQOP0b .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-2hEjNYiRMkrQOP0b .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-2hEjNYiRMkrQOP0b .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-2hEjNYiRMkrQOP0b .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-2hEjNYiRMkrQOP0b .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-2hEjNYiRMkrQOP0b .cluster text{fill:#333;}#mermaid-svg-2hEjNYiRMkrQOP0b .cluster span{color:#333;}#mermaid-svg-2hEjNYiRMkrQOP0b div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-2hEjNYiRMkrQOP0b .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-2hEjNYiRMkrQOP0b rect.text{fill:none;stroke-width:0;}#mermaid-svg-2hEjNYiRMkrQOP0b .icon-shape,#mermaid-svg-2hEjNYiRMkrQOP0b .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-2hEjNYiRMkrQOP0b .icon-shape p,#mermaid-svg-2hEjNYiRMkrQOP0b .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-2hEjNYiRMkrQOP0b .icon-shape .label rect,#mermaid-svg-2hEjNYiRMkrQOP0b .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-2hEjNYiRMkrQOP0b .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-2hEjNYiRMkrQOP0b .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-2hEjNYiRMkrQOP0b :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
统一业务入口
策略选择
策略 A
策略 B
策略 C
统一返回结果
策略模式不是为了消灭所有 if。如果只有两个分支,而且未来不会扩展,直接 if 更清楚。策略模式适合分支稳定成为“多种规则”的场景。
常见坑
坑一:策略类太碎
不要把几行简单逻辑也拆成一堆策略类。如果业务没有扩展性,拆完只会增加跳转成本。
坑二:策略选择逻辑仍然很复杂
策略模式应该让选择逻辑变简单。如果选择策略本身要写几十行判断,那可能需要先整理业务规则,或者引入规则引擎、配置化路由,而不是继续堆策略。
坑三:策略返回值不统一
所有策略最好返回统一结果对象,否则调用方还要根据策略类型做二次判断,等于把复杂度又带回来了。
#mermaid-svg-P6seDcVyQmsZhr94{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-P6seDcVyQmsZhr94 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-P6seDcVyQmsZhr94 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-P6seDcVyQmsZhr94 .error-icon{fill:#552222;}#mermaid-svg-P6seDcVyQmsZhr94 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-P6seDcVyQmsZhr94 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-P6seDcVyQmsZhr94 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-P6seDcVyQmsZhr94 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-P6seDcVyQmsZhr94 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-P6seDcVyQmsZhr94 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-P6seDcVyQmsZhr94 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-P6seDcVyQmsZhr94 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-P6seDcVyQmsZhr94 .marker.cross{stroke:#333333;}#mermaid-svg-P6seDcVyQmsZhr94 svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-P6seDcVyQmsZhr94 p{margin:0;}#mermaid-svg-P6seDcVyQmsZhr94 .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-P6seDcVyQmsZhr94 .cluster-label text{fill:#333;}#mermaid-svg-P6seDcVyQmsZhr94 .cluster-label span{color:#333;}#mermaid-svg-P6seDcVyQmsZhr94 .cluster-label span p{background-color:transparent;}#mermaid-svg-P6seDcVyQmsZhr94 .label text,#mermaid-svg-P6seDcVyQmsZhr94 span{fill:#333;color:#333;}#mermaid-svg-P6seDcVyQmsZhr94 .node rect,#mermaid-svg-P6seDcVyQmsZhr94 .node circle,#mermaid-svg-P6seDcVyQmsZhr94 .node ellipse,#mermaid-svg-P6seDcVyQmsZhr94 .node polygon,#mermaid-svg-P6seDcVyQmsZhr94 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-P6seDcVyQmsZhr94 .rough-node .label text,#mermaid-svg-P6seDcVyQmsZhr94 .node .label text,#mermaid-svg-P6seDcVyQmsZhr94 .image-shape .label,#mermaid-svg-P6seDcVyQmsZhr94 .icon-shape .label{text-anchor:middle;}#mermaid-svg-P6seDcVyQmsZhr94 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-P6seDcVyQmsZhr94 .rough-node .label,#mermaid-svg-P6seDcVyQmsZhr94 .node .label,#mermaid-svg-P6seDcVyQmsZhr94 .image-shape .label,#mermaid-svg-P6seDcVyQmsZhr94 .icon-shape .label{text-align:center;}#mermaid-svg-P6seDcVyQmsZhr94 .node.clickable{cursor:pointer;}#mermaid-svg-P6seDcVyQmsZhr94 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-P6seDcVyQmsZhr94 .arrowheadPath{fill:#333333;}#mermaid-svg-P6seDcVyQmsZhr94 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-P6seDcVyQmsZhr94 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-P6seDcVyQmsZhr94 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-P6seDcVyQmsZhr94 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-P6seDcVyQmsZhr94 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-P6seDcVyQmsZhr94 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-P6seDcVyQmsZhr94 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-P6seDcVyQmsZhr94 .cluster text{fill:#333;}#mermaid-svg-P6seDcVyQmsZhr94 .cluster span{color:#333;}#mermaid-svg-P6seDcVyQmsZhr94 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-P6seDcVyQmsZhr94 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-P6seDcVyQmsZhr94 rect.text{fill:none;stroke-width:0;}#mermaid-svg-P6seDcVyQmsZhr94 .icon-shape,#mermaid-svg-P6seDcVyQmsZhr94 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-P6seDcVyQmsZhr94 .icon-shape p,#mermaid-svg-P6seDcVyQmsZhr94 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-P6seDcVyQmsZhr94 .icon-shape .label rect,#mermaid-svg-P6seDcVyQmsZhr94 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-P6seDcVyQmsZhr94 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-P6seDcVyQmsZhr94 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-P6seDcVyQmsZhr94 :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
策略模式落地
统一接口
统一入参
统一返回值
独立策略实现
稳定策略选择方式
面试怎么说
可以这样回答:
我在项目里用策略模式处理过多登录方式、多支付方式这类场景。它们都是同一个业务入口,但具体处理逻辑不同。如果直接写 if-else,新增一种方式就要修改核心业务方法。我们会定义一个统一策略接口,比如 UserGranter,每种登录方式实现自己的策略类,并交给 Spring 容器管理。再提供一个工厂,根据请求里的 type 从策略池中拿到对应策略并调用。这样新增策略时只需要新增实现类和配置,不需要改主流程代码。
小结
策略模式的价值不是“把 if-else 换成类”,而是把可变算法从稳定流程里拆出来。
可以这样记:
策略模式负责封装算法,工厂负责选择算法,Spring 负责管理算法对象。
当一个业务入口后面挂着多套规则,并且这些规则还会继续扩展时,这个组合非常好用。
