策略模式 – 设计模式之行为模式
目录
封装角色; DiscountStrategyServiceContext
定义:
Define a family of algorithms, encapsulate each one, and make them interchangeable.(定义一组算法,将每个算法都封装起来,并且使它们之间可以互换)
定义了算法家族,分别封装起来,让它们之间可以相互替换,此模式算法的变化,不会影响到使用算法的用户。
类图:
例子:
用常见的购物场景,普通的不打折,白金会员打8折,铂金会员打6折
类图:
抽象的策略角色: IDiscount
public interface IDiscount {
double compute(double money);
}
具体策略角色1 OrdinaryStrategy
public class OrdinaryStrategy implements IDiscount {
@Override
public double compute(double money) {
System.out.println("普通会员不打折");
System.out.println("积分是1%");
return money;
}
}
具体策略角色2 PlatinumStrategy
public class PlatinumStrategy implements IDiscount {
@Override
public double compute(double money) {
System.out.println("白金会员 优惠50元,再打7折");
System.out.println("积分是5%");
return (money - 50) * 0.7;
}
}
具体策略角色3 GoldStrategy
public class GoldStrategy implements IDiscount {
@Override
public double compute(double money) {
System.out.println("黄金会员 优惠50元,再打6折");
System.out.println("积分是10%");
return (money - 50)*0.6;
}
}
封装角色 DiscountStrategyContext
public class DiscountStrategyContext {
private IDiscount discountStrategy;
/**
* 设置策略接口
* @param discountHandleStrategy
*/
public void setDiscountHandleStrategy(IDiscount discountHandleStrategy) {
this.discountStrategy = discountHandleStrategy;
}
public void computeMoney(double money) {
if (discountStrategy != null) {
double compute = discountStrategy.compute(money);
System.out.println(" pay "+ compute);
}
}
}
测试; DiscountTest
public class DiscountTest {
public static void main(String[] args) {
IDiscount discountStrategy = new OrdinaryStrategy();
DiscountStrategyContext discountStrategyContext = new DiscountStrategyContext();
discountStrategyContext.setDiscountHandleStrategy(discountStrategy);
discountStrategyContext.computeMoney(100);
}
}
结果:
普通会员不打折
积分是1%
pay 100.0
思考: 在使用策略模式的时候,得先知道是哪个策略,还不是得if-else 进行判断,具体使用哪个策略。这个要怎么进行优化呢?只要知道一个名字就可以了,传递相关的数字或是名字进来,反馈一个结果,这才是想要的。
传递名字: 考虑用枚举,如例子中,三种会员的类型,相对固定。
对各个策略进行集中处理,考虑用工厂方法模式来实现策略类的声明。要知道策略的名称,接口那需要添加一个获取类型的方法。
优化1:结合工厂方法
枚举类: UserTypeEnum
public enum UserTypeEnum {
COMMON_USER("COMMON"), PLATINUM_VIP("PLATINUM"), GOLD_VIP("GOLD");
String type;
public String getType() {
return type;
}
UserTypeEnum(String type) {
this.type = type;
}
public static UserTypeEnum matchType(String type) {
for (UserTypeEnum enumType : UserTypeEnum.values()) {
if (enumType.type == type) {
return enumType;
}
}
return UserTypeEnum.COMMON_USER;
}
}
抽象的策略角色: IDiscount
public interface IDiscount {
double compute(double money);
// 用于匹配类型
String getType();
}
具体策略角色1 OrdinaryStrategy
public class OrdinaryStrategy implements IDiscount {
@Override
public double compute(double money) {
System.out.println("普通会员不打折");
System.out.println("积分是1%");
return money;
}
@Override
public String getType() {
return UserTypeEnum.COMMON_USER.getType();
}
}
具体策略角色2 PlatinumStrategy
public class PlatinumStrategy implements IDiscount {
@Override
public double compute(double money) {
System.out.println("白金会员 优惠50元,再打7折");
System.out.println("积分是5%");
return (money - 50) * 0.7;
}
@Override
public String getType() {
return UserTypeEnum.PLATINUM_VIP.getType();
}
}
具体策略角色3 GoldStrategy
public class GoldStrategy implements IDiscount {
@Override
public double compute(double money) {
System.out.println("黄金会员 优惠50元,再打6折");
System.out.println("积分是10%");
return (money - 50)*0.6;
}
@Override
public String getType() {
return UserTypeEnum.GOLD_VIP.getType();
}
}
工厂方法类 DiscountStrategyFactory
public class DiscountStrategyFactory {
private Map<String, IDiscount> map;
private DiscountStrategyFactory() {
List<IDiscount> strategies = new ArrayList<>();
strategies.add(new OrdinaryStrategy());
strategies.add(new GoldStrategy());
strategies.add(new GoldStrategy());
strategies.add(new PlatinumStrategy());
// getType 主要在这边声明的时候用
map = strategies.stream()
.collect(Collectors.toMap(IDiscount::getType, strategy -> strategy, (x, y) -> x));
}
public static class Holder {
private static DiscountStrategyFactory instance = new DiscountStrategyFactory();
}
public static DiscountStrategyFactory getInstance() {
return Holder.instance;
}
public IDiscount getIDiscountByType(String type) {
return map.get(type);
}
}
或是用静态的方式,直接map,这样就不用声明getType() 和实现这个方法了
DiscountStrategyFactoryStatic:
public class DiscountStrategyFactoryStatic {
public static Map<String,IDiscount> DICOUNT_TYPE = new HashMap<>();
static {
DICOUNT_TYPE.put("COMMON",new OrdinaryStrategy());
DICOUNT_TYPE.put("PLATINUM",new GoldStrategy());
DICOUNT_TYPE.put("GOLD",new PlatinumStrategy());
}
public static class Holder {
private static DiscountStrategyFactoryStatic instance = new DiscountStrategyFactoryStatic();
}
public static DiscountStrategyFactoryStatic getInstance() {
return Holder.instance;
}
public IDiscount getIDiscountByType(String type) {
return DICOUNT_TYPE.get(type);
}
}
测试; DiscountFactoryTest
public class DiscountFactoryTest {
private static double getResult(long money, String type) {
if (money < 1000) {
return money;
}
IDiscount strategy = DiscountStrategyFactory.getInstance().getIDiscountByType (type);
if (strategy == null) {
throw new IllegalArgumentException("please input right type");
}
return strategy.compute(money);
}
public static void main(String[] args) {
String type = "GOLD";
UserTypeEnum userTypeEnum = UserTypeEnum.matchType(type);
System.out.println(getResult(3300, userTypeEnum.getType()));
System.out.println(getResult(2300, "PLATINUM"));
}
}
结果:
黄金会员 优惠50元,再打6折
积分是10%
1950.0
白金会员 优惠50元,再打7折
积分是5%
1575.0
这就达到了想要的要求。输入一个名字,一个价格,就可以得到结果。
优化2: 使用静态工厂,动态获取类
动态实例化类: SpringUtils
@Component
public class SpringUtils implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringUtils.applicationContext = applicationContext;
}
public static Object getBean(String name) {
return applicationContext.getBean(name);
}
public static <T> T getBean(Class<T> clazz) {
return applicationContext.getBean(clazz);
}
}
抽象的策略角色: IDiscount
public interface IDiscount {
double compute(double money);
}
具体策略角色1 OrdinaryStrategy
@Service
public class OrdinaryStrategy implements IDiscount {
@Override
public double compute(double money) {
System.out.println("普通会员不打折");
System.out.println("积分是1%");
return money;
}
}
具体策略角色2 PlatinumStrategy
@Service
public class PlatinumStrategy implements IDiscount {
@Override
public double compute(double money) {
System.out.println("白金会员 优惠50元,再打7折");
System.out.println("积分是5%");
return (money - 50) * 0.7;
}
}
具体策略角色3 GoldStrategy
@Service
public class GoldStrategy implements IDiscount {
@Override
public double compute(double money) {
System.out.println("黄金会员 优惠50元,再打6折");
System.out.println("积分是10%");
return (money - 50)*0.6;
}
}
工厂方法类 DiscountStrategyFactory
@Service
public class DiscountStrategyServiceFactory {
private static Map<String,String> DICOUNT_TYPE = new HashMap<>();
static {
DICOUNT_TYPE.put("COMMON","goldStrategyService");
DICOUNT_TYPE.put("PLATINUM","platinumStrategyService");
DICOUNT_TYPE.put("GOLD","goldStrategyService");
}
public IDiscount getDiscountService(String type){
return (IDiscount)SpringUtils.getBean(DICOUNT_TYPE.get(type));
}
}
这边主要用SpringUtils获取bean的方式,实例化type对应的类
封装角色; DiscountStrategyServiceContext
@Service
public class DiscountStrategyServiceContext {
@Resource
private DiscountStrategyServiceFactory discountStrategyServiceFactory;
private double getResult(long money, String type) {
if (money < 1000) {
return money;
}
IDiscount strategy = discountStrategyServiceFactory.getDiscountService(type);
if (strategy == null) {
throw new IllegalArgumentException("please input right type");
}
return strategy.compute(money);
}
public void testResult() {
System.out.println(getResult(3300, "GOLD"));
System.out.println(getResult(2300, "PLATINUM"));
}
}
测试; DisountApplicationTests
@RunWith(SpringRunner.class)
@SpringBootTest
public class DisountApplicationTests {
@Autowired
private DiscountStrategyServiceContext discountStrategyServiceContext;
@Test
public void testResult() {
discountStrategyServiceContext.testResult();
}
}
PS:这个是在springBoot项目里面的,启动后 注入@Service,初始化实例
结果:
黄金会员 优惠50元,再打6折
积分是10%
1950.0
白金会员 优惠50元,再打7折
积分是5%
1575.0
总结:
单纯用策略模式,并不能避免使用多重条件判断,结合枚举和工厂方法模式可以解决 问题,只通过一个名字或是数字,匹配到对应的策略。
策略模式的重点就是封装角色,它是借用了代理模式的思路,大家可以想想,它和代理模式有什么差别,差别就是策略模式的封装角色和被封装的策略类不用是同一个接口,如果是同一个接口那就成为了代理模式。