策略模式

背景

  公司Java编码规范中,往往强调禁止使用大量的if-else语句。那么在面对不同的业务逻辑时,除了if-else还有什么选择呢?
  策略模式就是专门为此而生的,它能消除大片大片的if-else,提升代码的可读性和可维护性,但也因为策略模式对此类问题的针对性,此时不用,别的地方也用不上它了…不过策略模式也有缺陷,通常要结合工厂模式一同使用。但本文只介绍作者对于策略模式的理解。
  用伪代码和if-else写一个简单的商品折扣功能感受下:

    /**
     * if-else编写打折方案示例:
     * 1、普通客户:无折扣、可购买送货上门服务;
     * 2、普通会员:一律九折、可购买送货上门服务,消费金额大于等于100元可免服务费;
     * 3、超级会员:一律八折、无门槛免费送货上门。
     * @param 商品原价
     * @param 客户级别
     * @return BigDecimal 应付金额或错误码
     *
     * @author 青青橙
     * @since 2019年10月23日
     */
    public BigDecimal discountByIfElse(商品原价, 客户级别) {
        初始化送货上门服务费为0;
        if (客户级别为普通客户) {
            if (购买送货上门服务) {
                计算送货上门服务费;
            }
            将商品原价加上送货上门服务费作为返回结果;
        } else if (客户级别为普通会员) {
            if (购买送货上门服务且消费金额小于100) {
                计算送货上门服务费;
            }
            将商品原价*九折后再加上送货上门服务费作为返回结果;
        } else (客户级别为超级会员) {
            将商品原价*八折后作为返回结果;
        }
        返回错误码;
    }

  这仅仅只是一个最简模型,而且还用的是伪代码,就已经让人不得不一步一步跟进if块中认真考虑上下文逻辑了。倘若后续需要办活动,满减?优惠券?如何修改又得仔细分析一番。到最后这片代码将变得越来越复杂庞大,实在没法维护时整个功能都得推倒重来。

特点

  策略模式是有专用性的,它只能解决下列规则范围内的问题。即使用场景
   1. 在一个模块里面有许多行为,它们之间是平等的,可以相互替换的,互斥的;
   2. 只允许在这些行为中选择一种(互斥);
   3. 这些行为是动态的,它们后续极可能会变化,甚至废弃。

  策略模式的优点
   1. 策略模式可类比多态,多态有啥优点它就有啥优点;
   2. 扩展性良好,符合了开闭原则。只要实现接口就可以在现有的系统中增加一个策略, 其他的都不用修改;
   3. 避免使用多重条件判断,提升了代码可维护性和可读性;
   4. 避免代码重复,策略模式提供了管理这些算法的方法,并恰当地使用了继承,使得公共代码可以提取到父类中。

  策略模式的缺点
   1. 每个行为都要提取为一个算法类,算法类的数量可能难以控制;
   2. 所有的策略类都需要对外暴露。调用者必须知道有那些策略,并自行决定使用哪一个策略类。这就意味着调用者必须理解这些算法的区别,以便适时选择恰当的算法类。这与迪米特法则是相违背的。我们可以使用其他模式来修正这个缺陷,如工厂方法模式、代理模式或享元模式。

专用名词解释

名词解释
Strategy —— 抽象策略类策略、算法家族的抽象父接口,能解耦,定义每个策略或算法必须具有的方法和属性
ConcreteStrategy —— 具体策略类Strategy的具体实现类,即由行为提取出来的策略类,定义单个策略或算法特定的逻辑
Context —— 上下文类封装对Strategy的调用,屏蔽高层模块对策略、算法的直接访问。调用者通过它来使用不同策略

运用

  针对上文if-else方式实现折扣价的算法,来试着写出使用策略模式设计的算法。
  首先不考虑上下文业务逻辑,只考虑行为主体是谁?有什么行为?对,是所有客户的打折行为。它就是Strategy了。

/**
 * StrategyPattern编写打折方案示例。
 * 行为主体:所有策略接口(Strategy)
 * 
 * @author 青青橙
 * @since 2019年10月23日
 */
public interface DiscountByMemberType {
	/**
	 * 行为:计算应付价格
     * @param 商品原价
	 * @return BigDecimal 应付金额
	 * 
	 * @author 青青橙
	 * @since 2019年10月23日
	 */
	public BigDecimal discountedPrice(商品原价);
}

  再考虑具体有哪些打折行为呢?它们各自的内部业务逻辑是什么?有三种,内部业务逻辑具体是:

用户类型折扣方案
普通客户无折扣、可购买送货上门服务
普通会员一律九折、可购买送货上门服务,消费金额大于等于100元可免服务费
超级会员一律八折、无门槛免费送货上门

这就是ConcreteStrategy了。

/**
 * 普通客户的折扣策略
 * 
 * @author 青青橙
 * @since 2019年10月23日
 */
public class OrdinaryCustomersDiscount implements discountByStrategy {
	/**
	 * 该特定策略的内部逻辑(ConcreteStrategy)
	 * 计算普通客户应付价格
     * @param 商品原价
	 * @return BigDecimal 应付金额
	 * 
	 * @author 青青橙
	 * @since 2019年10月23日
	 */
	@Override
	public BigDecimal discountedPrice(商品原价) {
		if (购买送货上门服务) {
			计算送货上门服务费;
		}
		将商品原价加上送货上门服务费作为返回结果;
	}
}
/**
 * 普通会员的折扣策略
 * 
 * @author 青青橙
 * @since 2019年10月23日
 */
public class OrdinaryMembersDiscount implements discountByStrategy {
    /**
	 * 该特定策略的内部逻辑(ConcreteStrategy)
     * 计算普通会员应付价格
   	 * @param 商品原价
     * @return BigDecimal 应付金额
     * 
     * @author 青青橙
     * @since 2019年10月23日
     */
    @Override
    public BigDecimal discountedPrice(商品原价) {
		if (购买送货上门服务且消费金额小于100) {
			计算送货上门服务费;
		}
		将商品原价*九折后再加上送货上门服务费作为返回结果;
	}
}
/**
 * 超级会员的折扣策略
 * 
 * @author 青青橙
 * @since 2019年10月23日
 */
public class SuperMembersDiscount implements discountByStrategy {
    /**
	 * 该特定策略的内部逻辑(ConcreteStrategy)
     * 计算超级会员应付价格
   	 * @param 商品原价
     * @return BigDecimal 应付金额
     * 
     * @author 青青橙
     * @since 2019年10月23日
     */
    @Override
    public BigDecimal discountedPrice(商品原价) {
		将商品原价*八折后作为返回结果;
    }
}

  那么现在,怎么调用这些行为呢?那就是Context了。

/**
 * 针对所有策略的上下文类(Context)
 * (这里为了和上文if-else处形成对比,使用了同名变量。实际开发严禁中文命名)
 */
public class Cashier {
    /**
     * 行为主体:策略。
     */
    private DiscountByMemberType 客户级别;
	
    public Cashier(DiscountByMemberType 客户级别) {
        this.客户级别 = 客户级别;
    }

	/**
	 * 策略对应的行为
	 */
    public BigDecimal computePrice(BigDecimal 商品原价) {
        return this.客户级别.discountedPrice(商品原价);
    }
}

  整个打折业务已经使用策略模式改进完毕,现在可以调用啦。

public class Test {
    public static void main(String[] args) {
        //创建需要使用的策略对象
        DiscountByMemberType 客户级别 = new OrdinaryCustomersDiscount();
        //创建上下文对象
        Cashier cashier = new Cashier(客户级别);
        //计算价格
        BigDecimal cash = cashier.computePrice(商品原价);
        System.out.println("普通客户应付:" + cash.doubleValue());
		
		//可复用
        客户级别 = new SuperMembersDiscount();
        cashier = new Cashier(客户级别);
        cash = cashier.computePrice(商品原价);
        System.out.println("超级会员应付:" + cash.doubleValue());
    }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值