策略模式
策略模式(Strategy): 定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化,不会影响到使用算法的客户。
策略模式是一种定义一系列算法的方法,从概念上来看,所有这些算法完成的都是相同的工作,只是实现不同,它可以以相同的方式调用所有的算法,减少了各种算法类与使用算法类之间的耦合。
当不同的的行为堆砌在一个类中,就很难避免使用条件语句来选择合适的行为,将这些行为封装在一个个独立的Strategy类中,可以在使用这些行为的类中消除条件语句。
--摘自《大话设计模式》
该模式涉及到三个角色:
- 环境(Context)角色:持有一个Strategy的引用
- 抽象策略(Strategy)角色:这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的绝体策略类所需的接口
- 具体策略(Concrete Stategy)角色:包装了相关的算法或行为
public class Context { //持有一个具体策略的对象 private Strategy strategy; /** * 构造函数,传入一个具体策略对象 * @param strategy 具体策略对象 */ public Context(Strategy strategy){ this.strategy = strategy; } /** * 策略方法 */ public void contextInterface(){ strategy.strategyInterface(); } }
public interface Strategy { /** * 策略方法 */ public void strategyInterface(); }
public class ConcreteStrategyA implements Strategy { @Override public void strategyInterface() { //相关的业务 } } public class ConcreteStrategyB implements Strategy { @Override public void strategyInterface() { //相关的业务 } }
使用场景:
假设有三种类别客户,分别为普通客户,会员,超级会员。针对不同类别的会员,有不同的打折方式。
普通客户原价,会员9折,超级会员8折。
不使用策略模式:
package com.example.demo.designpattern; /** * 假设有3种会员,分别为会员,超级会员以及金牌会员和普通顾客,针对不同类别的会员,有不同的打折方式, * 并且一个顾客每消费10000就增加一个级别 * 以上四种会员分别采用原价(普通顾客),九折(会员),八折(超级会员)和七折(金牌会员)的折扣方式。 */ public class Settlement { /** * 总价 */ private double totalPrice = 0; /** * 单次消费的金额 */ private double amount = 0; /** * 购买的方法 * @param amount 商品价格 * @return double 支付的价格 */ public double buy(double amount) throws Exception { this.amount = amount; this.totalPrice += this.amount; if(this.totalPrice < 10000) { return this.amount; } else if(this.totalPrice >= 10000 && this.totalPrice < 20000) { return this.amount * 0.9; } else if(this.totalPrice >= 20000 && this.totalPrice < 30000) { return this.amount * 0.8; } else { return this.amount * 0.7; } } } --------------------- 作者:zhongxuebin_xq 来源:CSDN 原文:https://blog.csdn.net/zhongxuebin_xq/article/details/81275958 版权声明:本文为博主原创文章,转载请附上博文链接!
这种面向过程的实现方式,再以后的维护和新增会员类型的时候,得需要重新添加条件判断,来满足我们的业务需求变化,是一种不灵活的设计方式,下面进行改造。
package strategy; /** * * Title: IDiscountStrategy * Description: 折扣 策略 * @author yacong_liu Email:2682505646@qq.com * @date 2019年1月22日 */ public interface IDiscountStrategy { double discout(double price); }
普通顾客策略
package strategy; public class OrdinaryCustomerStrategy implements IDiscountStrategy { @Override public double discout(double price) { System.out.println("普通顾客不打折......."); return price; } }
会员策略
package strategy; /** * * Title: Member * Description: 会员 9折 * @author yacong_liu Email:2682505646@qq.com * @date 2019年1月22日 */ public class MemberStrategy implements IDiscountStrategy { @Override public double discout(double price) { System.out.println("会员打9折......"); return price * 0.9; } }
超级会员策略
package strategy; /** * * Title: SuperMember Description: 超级会员 8 折 * * @author yacong_liu Email:2682505646@qq.com * @date 2019年1月22日 */ public class SuperMemberStrategy implements IDiscountStrategy { @Override public double discout(double price) { System.out.println("超级会员打8折......"); return price * 0.8; } }
Context 持有抽象策略 调用具体方法
package strategy; /** * * Title: Context Description: 策略类的引用 * * @author yacong_liu Email:2682505646@qq.com * @date 2019年1月22日 */ public class Context { private IDiscountStrategy strategy; public Context(IDiscountStrategy strategy) { super(); this.strategy = strategy; } public double buy(double price) { return this.strategy.discout(price); } }
客户端
@Test public void testBuy() { Context ctx = new Context(new MemberStrategy()); System.out.println("会员最终价格:" + ctx.buy(300)); Context ctx2 = new Context(new OrdinaryCustomerStrategy()); System.out.println("普通顾客最终价格:" + ctx2.buy(300)); Context ctx3 = new Context(new SuperMemberStrategy()); System.out.println("超级会员最终价格:" + ctx3.buy(300)); /* * 此时 需要客户端自己确定选择那种打折策略进行实例化 需要进行解耦。因此需要借助简单工厂,封装一个产生打折策略对象过程的类 */ }
运行结果
会员打9折...... 会员最终价格:270.0 普通顾客不打折....... 普通顾客最终价格:300.0 超级会员打8折...... 超级会员最终价格:240.0
上述的客户端测试类中可以看到,使用的过程中 客户端需要自己知道调用的具体策略,造成了耦合。因此下面进行使用工厂模式进行解耦。
构造策略工厂类,根据购买价格自行判定 构造具体的策略类
package strategy.factory; import strategy.IDiscountStrategy; import strategy.MemberStrategy; import strategy.OrdinaryCustomerStrategy; import strategy.SuperMemberStrategy; /** * * Title: PriceFactory Description: 策略工厂类 * * @author yacong_liu Email:2682505646@qq.com * @date 2019年1月22日 */ public class PriceFactory { public static PriceFactory getInstance() { return new PriceFactory(); } public IDiscountStrategy getStrategy(double price) { if (price < 1000) { return new OrdinaryCustomerStrategy(); } else if (price >= 1000 && price < 2000) { return new MemberStrategy(); } else { return new SuperMemberStrategy(); } } }
此时的Context 改造如下
package strategy.factory; import strategy.IDiscountStrategy; public class Context { @SuppressWarnings("unused") private IDiscountStrategy strategy = null; public double buy(double price) { IDiscountStrategy strategy = PriceFactory.getInstance().getStrategy(price); return strategy.discout(price); } }
客户端
@Test public void testBuyByFactory() { strategy.factory.Context ctx = new strategy.factory.Context(); System.out.println("顾客最终价格:" + ctx.buy(1000)); System.out.println("顾客最终价格:" + ctx.buy(599)); System.out.println("顾客最终价格:" + ctx.buy(2499)); System.out.println(System.getProperty("user.dir")); }
此时可以看到,客户端不再复杂具体的策略调用,完全根据价格交给工厂类判定。
测试结果
会员打9折...... 顾客最终价格:900.0 普通顾客不打折....... 顾客最终价格:599.0 超级会员打8折...... 顾客最终价格:1999.2 G:\WS-Eclipse-Oxygen\Design_Pattern
此时还可以分析出 构造的 策略工厂类中也需要 知道具体有那些策略类,当增加打折策略时,需要修改这个工厂类,也会违反开闭原则,此时可以使用反射进行处理。但是所有的需求变更都是有成本的! 此处不再介绍如何搭配反射进行改造了。