本篇文章着重讲解策略模式。那么什么是策略模式呢?所谓策略模式是一种定义一系列算法的方法,从概念上来看,所有这些算法完成的都是相同的工作,只是实现不同,它可以以相同的方式调用所有的算法,减少了各种算法类与使用算法类之间的耦合。
下面我们看看策略模式的结构图(图片引自百度百科)和基本代码:
策略模式(Strategy)结构图
基本代码:
Strategy类,定义是所有支持的算法的公共接口。
package com.lwx.strategy;/** * Created with IntelliJ IDEA. * Description: 算法接口 * User: lwx * Date: 2019-02-24 * Time: 16:14 */public interface Strategy { //算法方法 void algorithmMethod();}
ConcreteStrategy,封装了具体的算法或行为,实现Strategy接口。
package com.lwx.strategy;/** * Created with IntelliJ IDEA. * Description: 算法A具体实现 * User: lwx * Date: 2019-02-24 * Time: 16:16 */public class ConcreteStrategyA implements Strategy { //算法A实现方法 public void algorithmMethod() { System.out.println("算法A实现"); }}
package com.lwx.strategy;/** * Created with IntelliJ IDEA. * Description: 算法B具体实现 * User: lwx * Date: 2019-02-24 * Time: 16:17 */public class ConcreteStrategyB implements Strategy { //算法A实现方法 public void algorithmMethod() { System.out.println("算法B实现"); }}
package com.lwx.strategy;/** * Created with IntelliJ IDEA. * Description:算法C具体实现 * User: lwx * Date: 2019-02-24 * Time: 16:17 */public class ConcreteStrategyC implements Strategy { //算法C实现方法 public void algorithmMethod() { System.out.println("算法C实现"); }}
Context,用一个ConcreteStrategy来配置,维护一个队Strategy对象的引用。
package com.lwx.strategy;/** * Created with IntelliJ IDEA. * Description: * User: lwx * Date: 2019-02-24 * Time: 16:19 */public class Context { private Strategy strategy; // 初始化时传入具体的策略对象 public Context(Strategy strategy) { this.strategy = strategy; } //根据具体的策略对象,调用其算法的方法 public void contextMethod() { strategy.algorithmMethod(); }}
测试类。
package com.lwx.strategy;/** * Created with IntelliJ IDEA. * Description: 测试类 * User: lwx * Date: 2019-02-24 * Time: 16:20 */public class StrategyTest { public static void main(String[] args) { Context context; // 由于实例化不同的策略,所以最终调用context.contextMethod()时,所获得的结果就不尽相同 context = new Context(new ConcreteStrategyA()); context.contextMethod(); context = new Context(new ConcreteStrategyB()); context.contextMethod(); context = new Context(new ConcreteStrategyC()); context.contextMethod(); }}
上面我们替换了几次策略,但是调用方式不变,下面看下运行结果。
上面的例子代码清晰但却理解起来很生硬,下面举一个具有实际意义的例子。
这里引用大化设计模式一书中的商场收银软件来举例,这个商场中的顾客分为普通顾客,会员和超级会员;针对各个顾客,有不同的打折方式,那么我们就可以使用策略模式来实现此功能。
首先要有一个计算价格的策略接口,如下:
package com.lwx.strategy.example;/** * Created with IntelliJ IDEA. * Description: 现金收费接口 * User: lwx * Date: 2019-02-24 * Time: 20:12 */public interface CashSuper { /** * 收取现金 * @param money 原价 * @return 当前价 */ double acceptCash(double money);}
自己写一个注解来区分计算价格的实现类对应的顾客类型:
package com.lwx.strategy.example;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/** * Created with IntelliJ IDEA. * Description: 顾客类型注解 * User: lwx * Date: 2019-02-24 * Time: 20:51 */@Target(ElementType.TYPE) //表示只能给类添加该注解@Retention(RetentionPolicy.RUNTIME) //这个必需将注解保留在运行时public @interface CustomerType { int type() default 1;}
接下来给出三种顾客集体计算价格的实现类:
package com.lwx.strategy.example;/** * Created with IntelliJ IDEA. * Description: 正常收费实现类 * User: lwx * Date: 2019-02-24 * Time: 20:14 */@CustomerType //普通顾客public class CashNormal implements CashSuper { //正常收费,原价返回 public double acceptCash(double money) { return money; }}
package com.lwx.strategy.example;/** * Created with IntelliJ IDEA. * Description: 打折收费实现类 * User: lwx * Date: 2019-02-24 * Time: 20:16 */@CustomerType(type = 2) //会员顾客public class CashRebate implements CashSuper { private double moneyRebate = 1d; //打折收费,初始化时,必需要输入折扣率,如八折就是0.8,为了方便用反射所以参数给了数组 public CashRebate(double... parameters) { this.moneyRebate = parameters[0]; } public double acceptCash(double money) { return money * moneyRebate; }}
package com.lwx.strategy.example;/** * Created with IntelliJ IDEA. * Description: 返利收子类 * User: lwx * Date: 2019-02-24 * Time: 20:19 */@CustomerType(type = 3) //超级会员public class CashReturn implements CashSuper { private double moneyRebate = 1d; private double moneyCondition = 0.0d; private double moneyReturn = 0.0d; //返利收费子类,初始化时必须要输入折扣率、返利条件和返利值,为了方便用反射所以参数给了数组 public CashReturn(double... parameters) { this.moneyRebate = parameters[0]; this.moneyCondition = parameters[1]; this.moneyReturn = parameters[2]; } public double acceptCash(double money) { double result = money; if (money >= moneyCondition) { //超级会员先判断支付金额大于返利条件,则减去需要返利的值,再打折 result = (money - Math.floor(money / moneyCondition) * moneyReturn) * moneyRebate; } return result; }}
简单的写一个顾客实体类
package com.lwx.strategy.example;/** * Created with IntelliJ IDEA. * Description: 顾客类 * User: lwx * Date: 2019-02-24 * Time: 21:11 */public class Customer { //总价格 private double totalPrice; //顾客类型 private int type; public Customer(double totalPrice, int type) { this.totalPrice = totalPrice; this.type = type; } public double getTotalPrice() { return totalPrice; } public void setTotalPrice(double totalPrice) { this.totalPrice = totalPrice; } public int getType() { return type; } public void setType(int type) { this.type = type; }}
然后我们使用简单工厂根据顾客类型利用反射创建对应的计算实现类
package com.lwx.strategy.example;import java.io.File;import java.io.FileFilter;import java.lang.reflect.Constructor;import java.util.ArrayList;import java.util.List;/** * Created with IntelliJ IDEA. * Description: * User: lwx * Date: 2019-02-24 * Time: 21:07 */public class CashFactory { private CashFactory() { this.init(); } //这里是一个常量,表示我们要扫描的包 private static final String CASH_PACKAGE = "com.lwx.strategy.example"; //初始化类加载器,任何类运行时信息必需来自该类加载器 private ClassLoader classLoader = getClass().getClassLoader(); private List> cashList; //策略列表 //根据顾客的类型产生相应的策略 public CashSuper createCashPush(Customer customer, double... parameters) { //在策略列表中查找策略 for (Class clazz : cashList) { CustomerType annotation = clazz.getAnnotation(CustomerType.class); if (annotation.type() == customer.getType()) { try { CashSuper cashSuper = null; if (customer.getType() != 1) { Constructor constructor1 = clazz.getDeclaredConstructor(double[].class); cashSuper = constructor1.newInstance(parameters); } else { cashSuper = clazz.newInstance(); } return cashSuper; } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("策略获取失败"); } } } throw new RuntimeException("策略获取失败"); } //在工程初始化时要初始化策略列表 private void init() { cashList = new ArrayList >(); //获取包下所有的class文件 File[] resources = getClassResource(); Class cashSuperClass = null; try { //使用相同的加载器加载策略父接口 cashSuperClass = (Class ) classLoader.loadClass(CashSuper.class.getName()); } catch (ClassNotFoundException e) { e.printStackTrace(); throw new RuntimeException("未找到策略资源"); } for (int i = 0; i < resources.length; i++) { try { //载入包下的类 Class clazz = classLoader.loadClass(CASH_PACKAGE + "." + resources[i].getName().replace(".class", "")); //判断是否是CashSuper的实现类并且不是CashSuper本身 if (CashSuper.class.isAssignableFrom(clazz) && clazz != cashSuperClass) { cashList.add((Class ) clazz); } } catch (ClassNotFoundException e) { e.printStackTrace(); throw new RuntimeException("未找到策略资源"); } } } //获取扫描的包下面所有的class文件 private File[] getClassResource() { try { File file = new File(classLoader.getResource(CASH_PACKAGE.replace(".", "/")).toURI()); return file.listFiles(new FileFilter() { public boolean accept(File pathname) { //只扫描class文件 if (pathname.getName().endsWith(".class")) { return true; } return false; } }); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("未找到策略资源"); } } public static CashFactory getInstance() { return CashFactoryInstance.instance; } private static class CashFactoryInstance { private static CashFactory instance = new CashFactory(); }}
最后建一个Context类维护一个对CashSuper对应的引用
package com.lwx.strategy.example;/** * Created with IntelliJ IDEA. * Description: cash上下文类 * User: lwx * Date: 2019-02-24 * Time: 19:59 */public class CashContext { private CashSuper cashSuper = null; private Customer customer; public CashContext(Customer customer, double... parameters) { this.customer = customer; this.cashSuper = CashFactory.getInstance().createCashPush(customer, parameters); } public double getResult() { return cashSuper.acceptCash(customer.getTotalPrice()); }}
接下来给出测试类以及运行结果
package com.lwx.strategy.example;/** * Created with IntelliJ IDEA. * Description: 测试类 * User: lwx * Date: 2019-02-24 * Time: 20:29 */public class CashTest { public static void main(String[] args) { CashContext cashContext = null; Customer customer = null; customer = new Customer(500, 1); cashContext = new CashContext(customer); System.out.println("普通顾客花费500,最后需支付的价格:" + cashContext.getResult()); customer = new Customer(500, 2); cashContext = new CashContext(customer, 0.8); System.out.println("会员顾客花费500,最后需支付的价格:" + cashContext.getResult()); customer = new Customer(500, 3); cashContext = new CashContext(customer, 0.8 , 300, 100); System.out.println("超级会员顾客花费500,最后需支付的价格:" + cashContext.getResult()); }}
总结一下策略模式的优点和缺点。
优点:
1. 当不同的行为堆砌在一个类时,就很难避免使用条件语句来选择合适的行为。将这些行为封装在一个个独立的类中,可以在使用这些行为的类中消除条件语句。(策略模式封装了变化)
2. 简化单元测试,因为每个算法都有自己的类,可以通过自己的接口单独测试。
缺点:
1. 客户端必需知道所有的策略实现类,并决定使用哪一个。
2. 会产生很多策略类。
当然还有别的优点和缺点这里只简单列出几个。
写在最后:
这是lz第一次写博客,写的不好大家多见谅,有什么建议也可以在评论区留言。最后附上demo的githup地址: