一、什么是策略模式
策略模式是一种行为型模式,它将对象和行为分开,将行为定义为一个行为接口和具体行为的实现。策略模式最大的特点是行为的变化,行为之间可以相互替换。每个 if 判断都可以理解为就是一个策略。本模式使得算法可独立于使用它的用户而变化。UML结构图如下:
其中的三种角色为:
- 环境/上下文 (Context):持有一个策略类的引用,最终给客户端调用。
- 抽象策略 (Strategy): 策略类,通常是一个接口或者抽象类。
- 具体策略 (ConcreteStrategy):实现了策略类中的策略方法,封装相关的算法和行为。
二、策略模式的使用场景
1、多个类只区别在表现行为不同,可以使用Strategy模式,在运行时动态选择具体要执行的行为。
2、需要在不同情况下使用不同的策略(算法),或者策略还可能在未来用其它方式来实现。
3、对客户隐藏具体策略(算法)的实现细节,彼此完全独立。
三、策略模式的代码示例
例如:超市中不同类型的商品,打折力度不同,在计算商品价格时,可能会这样写:
1、创建商品枚举类
public enum CommondityTypeEnum {
COOKED_FOOD(0,"熟食"),
FRUIT(1,"水果"),
WINE(2,"酒水"),
SNACKS(3,"零食");
private Integer code;
private String msg;
CommondityTypeEnum(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
2、方法中通过判断商品类型,根据折扣的不同,计算每种商品的价格
public BigDecimal calculation(BigDecimal price, int commondityType) {
/**
* 熟食区
*/
if (commondityType == CommondityTypeEnum.COOKED_FOOD.getCode()) {
//晚上八点之后打五折
if (LocalDateTime.now().getHour() >= 20) {
return price.multiply(new BigDecimal(0.5));
}
}
/**
* 水果区
*/
if (commondityType == CommondityTypeEnum.FRUIT.getCode()) {
//晚上八点之后打六折
if (LocalDateTime.now().getHour() >= 20) {
return price.multiply(new BigDecimal(0.6));
}
}
/**
* 酒水区
*/
if (commondityType == CommondityTypeEnum.WINE.getCode()) {
//不打折
return price;
}
/**
* 零食区
*/
if (commondityType == CommondityTypeEnum.SNACKS.getCode()) {
//周二会员日,均一价10元
if ((Calendar.getInstance().get(Calendar.DAY_OF_WEEK) - 1) == 2) {
return BigDecimal.valueOf(10);
}
}
return price;
}
}
这种多 if 判断的逻辑,很适合用策略模式改造:
在策略模式中,定义一些独立的类来封装不同的算法,每一个类封装一个具体的算法,在这里,每一个封装算法的类都可以称之为策略,为了保证这些策略的一致性,一般会用一个抽象的策略类来做算法的定义,而具体每种算法则对应于一个具体策略类。
1、抽象策略类:
抽象出一个公共接口
public interface SuperMarket {
// 计算价格
BigDecimal calculation(BigDecimal price);
}
2、具体策略类:
/**
* 具体策略类1:熟食区
*/
public class CookedFoodStrategy implements SuperMarket{
@Override
public BigDecimal calculation(BigDecimal price) {
//晚上八点之后打五折
if (LocalDateTime.now().getHour() >= 20) {
return price.multiply(new BigDecimal(0.5));
}
return price;
}
}
/**
* 具体策略2:水果区
*/
public class FruitStrategy implements SuperMarket {
@Override
public BigDecimal calculation(BigDecimal price) {
//晚上八点之后打六折
if (LocalDateTime.now().getHour() >= 20) {
return price.multiply(new BigDecimal(0.6));
}
return price;
}
}
酒水区、零食区等略过,如果后面有了其他商品类型,都很方便扩展;
3、上下文类:
/**
* 上下文类:相当于收银台
*/
public class CashierContext {
private SuperMarket superMarket;
public CashierContext(SuperMarket superMarket) {
this.superMarket = superMarket;
}
public BigDecimal checkOut(BigDecimal price) {
return this.superMarket.calculation(price);
}
}
4、测试类
public class TestMain {
public static void main(String[] args) {
CashierContext cashierContext = new CashierContext(new CookedFoodStrategy());
System.out.println(cashierContext.checkOut(new BigDecimal(100)));
}
}
四、策略模式的优缺点
优点:
- 提供了管理相关的算法族的办法,从而避免重复的代码。
- 提供了可以替换继承关系的办法。因为继承使得动态改变算法或行为变得不可能。
- 避免使用多重条件转移语句。
缺点:
- 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
- 由于策略模式把每个具体的策略实现都单独封装成为类,如果备选的策略很多的话,那么对象的数目就会很多。