策略模式
介绍
意图:策略模式定义了一系列的算法,并将每一个算法封装起来,使每个算法可以相互替代,使算法本身和使用算法的客户端分割开来,相互独立。
主要解决: 在有多种算法相似的情况下,使用 if…else 所带来的复杂和难以维护。
何时使用: 一个系统有许多许多类,而区分它们的只是他们直接的行为。
如何解决: 将这些算法封装成一个一个的类,任意地替换。不是如何来实现算法,而是如何组织和调用这些算法,从而让我们的程序结构更加的灵活、可扩展。
关键代码: 实现同一个接口。
优点: 1、算法可以自由切换。 2、避免使用多重条件判断。 3、扩展性良好。
缺点: 1、策略类会增多。 2、所有策略类都需要对外暴露。
使用场景: 1、如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。 2、一个系统需要动态地在几种算法中选择一种。 3、如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。
注意事项: 如果一个系统的策略多于四个,就需要考虑使用混合模式,解决策略类膨胀的问题
图书折扣例子
现实生活中我们到商场买东西的时候,卖场往往根据不同的客户制定不同的报价策略,比如针对新客户不打折扣,针对老客户打9折,针对VIP客户打8折… 要针对不同的客户,商家会提供不同的折扣报价。
接下来我们用买图书来举个例子。
我们很有可能写出下面的代码:
public class Test {
//图书价格
private static int bookPrice = 100;
public static void main(String[] args) {
BigDecimal b = buyBook(BigDecimal.valueOf(bookPrice), "老客户");
System.out.println(b);
}
public static BigDecimal buyBook(BigDecimal originalPrice, String customType){
if ("老客户".equals(customType)) {
System.out.println("恭喜你!老客户打9折!");
originalPrice = originalPrice.multiply(new BigDecimal(0.9)).setScale(2,BigDecimal.ROUND_HALF_UP);
return originalPrice;
}else if ("VIP客户".equals(customType)) {
System.out.println("恭喜你!VIP客户打8折!");
originalPrice = originalPrice.multiply(new BigDecimal(0.8)).setScale(2,BigDecimal.ROUND_HALF_UP);
return originalPrice;
}else if("SVIP客户".equals(customType)){
System.out.println("恭喜你!SVIP客户打7折!");
originalPrice = originalPrice.multiply(new BigDecimal(0.7)).setScale(2,BigDecimal.ROUND_HALF_UP);
return originalPrice;
}
//其他人员都是原价
return originalPrice;
}
}
//控制台输出:
//恭喜你!老客户打9折!
//90.00
上述代码写的很粗糙,把不同客户的报价的算法都放在了同一个方法里面,这样看着代码显得有些臃肿,也会有人说我们把不同用户的优惠都单独放在一个方法里,不就不显得臃肿了吗?
代码如下:
public class Test {
public BigDecimal buyBook(BigDecimal originalPrice, String customType){
if ("老客户".equals(customType)) {
return this.quoteOldCustomer(originalPrice);
}else if ("VIP客户".equals(customType)) {
return this.quoteVIPCustomer(originalPrice);
}else if("SVIP客户".equals(customType)){
return this.quoteSVIPCustomer(originalPrice);
}
//其他人员都是原价
return originalPrice;
}
private BigDecimal quoteSVIPCustomer(BigDecimal originalPrice) {
System.out.println("恭喜!SVIP打7折");
originalPrice = originalPrice.multiply(new BigDecimal(0.7)).setScale(2,BigDecimal.ROUND_HALF_UP);
return originalPrice;
}
/**
* 对VIP客户的报价算法
* @param originalPrice 原价
* @return 折后价
*/
private BigDecimal quoteVIPCustomer(BigDecimal originalPrice) {
System.out.println("恭喜!VIP客户打8折");
originalPrice = originalPrice.multiply(new BigDecimal(0.8)).setScale(2,BigDecimal.ROUND_HALF_UP);
return originalPrice;
}
/**
* 对老客户的报价算法
* @param originalPrice 原价
* @return 折后价
*/
private BigDecimal quoteOldCustomer(BigDecimal originalPrice) {
System.out.println("恭喜!老客户打9折");
originalPrice = originalPrice.multiply(new BigDecimal(0.9)).setScale(2,BigDecimal.ROUND_HALF_UP);
return originalPrice;
}
}
这个代码比刚开始的时候要好一点,它把每个具体的算法都单独抽出来作为一个方法,当某一个具体的算法有了变动的时候,只需要修改响应的报价算法就可以了。
但是当我们新增一个客户类型的时候,首先要添加一个该种客户类型的报价算法方法,然后再buyBook方法中再加一个else if的分支,是不是感觉很是麻烦呢?而且这也违反了设计原则之一的开闭原则。对于扩展是开放的,对于修改是关闭的。
使用策略模式
这个模式涉及到三个角色:
● 环境(Context)角色: 持有一个Strategy的引用。
● 抽象策略(Strategy)角色: 这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。
● 具体策略(ConcreteStrategy)角色: 包装了相关的算法或行为。

抽象的策略对象
/**
* 抽象的策略角色
* @author: zhouzhou
* @date:2021/10/14 14:23
*/
public interface MemberStrategy {
/**
* 计算图书的价格
* @param booksPrice 图书的原价
* @return 计算出打折后的价格
*/
BigDecimal calcPrice(BigDecimal booksPrice);
}
环境对象
/**
* 环境角色 持有一个Strategy的引用。
*
* @author: zhouzhou
* @date:2021/10/14 14:24
*/
public class PriceContext {
//持有一个抽象策略的对象
private MemberStrategy memberStrategy;
/**
* 构造函数,传入一个具体策略对象
* @param memberStrategy 具体策略对象
*/
public PriceContext(MemberStrategy memberStrategy) {
this.memberStrategy = memberStrategy;
}
/**
* 计算图书的价格
* @param booksPrice 图书的原价
* @return 计算出打折后的价格
*/
public BigDecimal quote(BigDecimal booksPrice){
return this.memberStrategy.calcPrice(booksPrice);
}
}
具体的策略对象
/**
* 老用户折扣类
* @author: zhouzhou
* @date:2021/10/14 14:40
*/
public class OldCustomerStrategy implements MemberStrategy{
/**
* 计算图书的价格
*
* @param booksPrice 图书的原价
* @return 计算出打折后的价格
*/
@Override
public BigDecimal calcPrice(BigDecimal booksPrice) {
System.out.println("对于老用户的折扣为:9折");
booksPrice = booksPrice.multiply(new BigDecimal(0.9)).setScale(2,BigDecimal.ROUND_HALF_UP);
return booksPrice;
}
}
/**
* vip会员折扣类
*
* @author: zhouzhou
* @date:2021/10/14 14:40
*/
public class VipCustomerStrategy implements MemberStrategy{
/**
* 计算图书的价格
*
* @param booksPrice 图书的原价
* @return 计算出打折后的价格
*/
@Override
public BigDecimal calcPrice(BigDecimal booksPrice) {
System.out.println("对于VIP用户的折扣为:8折");
booksPrice = booksPrice.multiply(new BigDecimal(0.8)).setScale(2,BigDecimal.ROUND_HALF_UP);
return booksPrice;
}
}
/**
* SVIP会员折扣类
*
* @author: zhouzhou
* @date:2021/10/14 14:40
*/
public class SVipCustomerStrategy implements MemberStrategy{
/**
* 计算图书的价格
*
* @param booksPrice 图书的原价
* @return 计算出打折后的价格
*/
@Override
public BigDecimal calcPrice(BigDecimal booksPrice) {
System.out.println("对于SVIP用户的折扣为:7折");
booksPrice = booksPrice.multiply(new BigDecimal(0.7)).setScale(2,BigDecimal.ROUND_HALF_UP);
return booksPrice;
}
}
测试类
public class Client {
public static void main(String[] args) {
//选择并创建需要使用的策略对象
MemberStrategy m1 = new VipCustomerStrategy();
//创建支付环境
PriceContext priceContext = new PriceContext(m1);
//获取优惠价格
BigDecimal quote = priceContext.quote(BigDecimal.valueOf(100));
System.out.println("图书优惠后的价格为:"+ quote);
}
}
我们忘记了新用户没有优惠怎么办?这个很容易,我们在增加的一个新用户的策略类
/**
* 新用户折扣类
* @author: zhouzhou
* @date:2021/10/14 14:40
*/
public class NewCustomerStrategy implements MemberStrategy{
/**
* 计算图书的价格
*
* @param booksPrice 图书的原价
* @return 计算出打折后的价格
*/
@Override
public BigDecimal calcPrice(BigDecimal booksPrice) {
System.out.println("对不起信用户没有折扣,请充值成VIP用户!");
return booksPrice;
}
}
/**
* 测试类
*
* @author: zhouzhou
* @date:2021/10/14 14:43
*/
public class Client {
public static void main(String[] args) {
//选择并创建需要使用的策略对象
MemberStrategy m1 = new VipCustomerStrategy();
MemberStrategy newUser = new NewCustomerStrategy();
//创建支付环境
PriceContext priceContext = new PriceContext(m1);
PriceContext newUserPrice = new PriceContext(newUser);
//获取优惠价格
BigDecimal quote = priceContext.quote(BigDecimal.valueOf(100));
System.out.println("图书优惠后的价格为:"+ quote);
System.out.println("新用户图书优惠后价格:"+newUserPrice.quote(BigDecimal.valueOf(100)));
}
}

示例二
模拟下单支付提示选择支付方式,如果用户未选,系统也会默认好推荐的支付方式进行结算。
统一返回值
/**
* 统一返回值
*
* @author: zhouzhou
* @date:2021/11/8 10:18
*/
@Data
@AllArgsConstructor
public class MsgResult {
private int code;
private String msg;
private Object data;
}
支付方式抽象类
/**
* 支付渠道
*
* @author: zhouzhou
* @date:2021/11/8 10:20
*/
public abstract class Payment {
public abstract String getName();
//通用逻辑被放到抽象类里实现
public MsgResult pay(String uid, double amount){
if(queryBalance(uid) < amount){
return new MsgResult(500,"支付失败!","余额不足~~");
}
return new MsgResult(200,"支付成功!","支付金额" + amount);
}
//查询存款
public abstract double queryBalance(String uid);
}
具体的支付方式
/**
* 具体的支付方式
* 支付宝
* @author: zhouzhou
* @date:2021/11/8 10:26
*/
public class AliPay extends Payment {
@Override
public String getName() {
return "支付宝";
}
@Override
public double queryBalance(String uid) {
return 800;
}
}
/**
* 具体的支付方式
* 银行卡
* @author: zhouzhou
* @date:2021/11/8 10:26
*/
public class UnionPay extends Payment {
@Override
public String getName() {
return "银行卡";
}
@Override
public double queryBalance(String uid) {
return 500;
}
}
/**
* 具体的支付方式
* 微信支付
* @author: zhouzhou
* @date:2021/11/8 10:26
*/
public class WechatPay extends Payment {
@Override
public String getName() {
return "微信支付";
}
@Override
public double queryBalance(String uid) {
return 300;
}
}
支付策略管理
/**
* 支付策略管理
* @author: zhouzhou
* @date:2021/11/8 10:29
*/
public class PayStrategy {
public static final String ALI_PAY = "AliPay";
public static final String WECHAT_PAY = "WechatPay";
public static final String UNION_PAY = "UnionPay";
public static final String DEFAULT_PAY = ALI_PAY;
private static Map<String, Payment> strategy = new HashMap<>();
static {
strategy.put(ALI_PAY,new AliPay());
strategy.put(WECHAT_PAY,new WechatPay());
strategy.put(UNION_PAY,new UnionPay());
}
public static Payment get(String payKey){
if(!strategy.containsKey(payKey)){
strategy.get(DEFAULT_PAY);
}
return strategy.get(payKey);
}
}
订单类
@AllArgsConstructor
public class Order {
private String uid;
private String orderId;
private double amount;
public MsgResult pay(){
return pay(PayStrategy.DEFAULT_PAY);
}
public MsgResult pay(String payKey){
Payment payment = PayStrategy.get(payKey);
System.out.println("欢迎使用" + payment.getName());
System.out.println("本次交易金额为" + amount + ",开始扣款");
return payment.pay(uid,amount);
}
}

本文通过图书折扣和支付方式选择两个实例,详细介绍了策略模式如何解耦算法,提高代码可维护性和扩展性。通过创建抽象策略接口、具体策略类和环境类,实现场景中不同客户折扣和支付方式的动态切换。
1624

被折叠的 条评论
为什么被折叠?



