什么是策略模式?
定义一系列算法,把它们一个个封装起来,并且使它们可以相互替换,这就叫策略模式 strategy。 比如你打算去旅行(目标),你可以选 “坐飞机”、“开私驾车” 或 “骑自行车”(不同策略)。这些方式随你挑,但不影响你 “去旅行” 这个最终目的。
简单示例
商场有多种促销手段:原价、打 8 折、满 300 减 50。使用策略模式实现一个商场打折计算器。
第一步:定义策略接口 (Strategy)
1 2 3
| public interface DiscountStrategy { double calculate(double price); }
|
第二步:实现具体的策略类 (ConcreteStrategy)
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class NoDiscount implements DiscountStrategy { public double calculate(double price) { return price; } }
class PercentDiscount implements DiscountStrategy { public double calculate(double price) { return price * 0.8; } }
class FullReductionDiscount implements DiscountStrategy { public double calculate(double price) { return price >= 300 ? price - 50 : price; } }
|
第三步:定义上下文 (Context)
1 2 3 4 5 6 7 8 9 10 11
| public class Cashier { private DiscountStrategy strategy;
public void setStrategy(DiscountStrategy strategy) { this.strategy = strategy; }
public double quote(double price) { return strategy.calculate(price); } }
|
第四步:客户端调用
1 2 3 4 5 6 7 8 9
| public class Client { public static void main(String[] args) { Cashier cashier = new Cashier(); cashier.setStrategy(new PercentDiscount()); System.out.println("应付金额:" + cashier.quote(500)); } }
|
Shiro 如何通过策略模式处理多个 Realm?
在 Shiro 中,当你配置了多个数据源(比如一个从 LDAP 查,一个从数据库查),ModularRealmAuthenticator(认证器)就会面临一个问题:“只要一个对就行,还是全都要对?”
这就是典型的策略模式应用。Shiro 定义了一个接口:AuthenticationStrategy。Shiro 提供的三种内置策略:
FirstSuccessfulStrategy:只要第一个 Realm 验证成功,就算成功,后面的不看了。
AtLeastOneSuccessfulStrategy(默认):只要有一个 Realm 成功就行,但它会尝试完所有的 Realm。
AllSuccessfulStrategy:必须所有的 Realm 都认证成功才行。
核心源码实现
在 ModularRealmAuthenticator 的认证逻辑中,它会循环遍历所有的 Realm,并在循环前后调用策略的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| protected AuthenticationInfo doAuthenticate(AuthenticationToken token) { AuthenticationStrategy strategy = getAuthenticationStrategy();
AuthenticationInfo aggregate = strategy.beforeAllAttempts(realms, token);
for (Realm realm : realms) { aggregate = strategy.beforeAttempt(realm, token, aggregate); try { AuthenticationInfo info = realm.getAuthenticationInfo(token); aggregate = strategy.afterAttempt(realm, token, info, aggregate, null); } catch (Throwable t) { aggregate = strategy.afterAttempt(realm, token, null, aggregate, t); } }
return strategy.afterAllAttempts(token, aggregate); }
|
经过这样设计,认证器(Authenticator)只负责“跑循环”,至于“怎么算成功”,全部外包给“策略对象”。如果你公司有奇葩需求(比如:必须两个特定的数据库都通过才行),你只需要写一个类实现 AuthenticationStrategy 接口并注入进去,而不需要修改 Shiro 的核心源码。