策略模式概述
在策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式。
在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法。
介绍
目的: 定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。
主要解决: 在有多种算法相似的情况下,使用 if…else 所带来的复杂和难以维护。
何时使用: 一个系统有许多许多类,而区分它们的只是他们直接的行为。
如何解决: 将这些算法封装成一个一个的类,任意地替换。
关键代码: 实现同一个接口。
优点:
- 多重条件语句不易维护,而使用策略模式可以避免使用多重条件语句,如 if…else 语句、switch…case 语句。
- 策略模式提供了一系列的可供重用的算法族,恰当使用继承可以把算法族的公共代码转移到父类里面,从而避免重复的代码。
- 策略模式可以提供相同行为的不同实现,客户可以根据不同时间或空间要求选择不同的。
- 策略模式提供了对开闭原则的完美支持,可以在不修改原代码的情况下,灵活增加新算法。
- 策略模式把算法的使用放到环境类中,而算法的实现移到具体策略类中,实现了二者的分离。
缺点:
- 策略类会增多。
- 所有策略类都需要对外暴露,客户端必须理解所有策略算法的区别,以便适时选择恰当的算法类。
使用场景:
- 如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。
- 一个系统需要动态地在几种算法中选择一种。
- 如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。
应用实例:
- 诸葛亮的锦囊妙计,每一个锦囊就是一个策略。
- 旅行的出游方式,选择骑自行车、坐汽车,每一种旅行方式都是一个策略。
- JAVA AWT 中的 LayoutManager。
注意事项: 如果一个系统的策略多于四个,就需要考虑使用混合模式,解决策略类膨胀的问题。
结构
三种角色
环境类(Context)
持有一个策略类的引用,最终给客户端调用。
抽象策略类(Strategy)
定义了一个公共接口,各种不同的算法以不同的方式实现这个接口,环境角色使用这个接口调用不同的算法,一般使用接口或抽象类实现。
具体策略类(ConcreteStrategy)
实现了抽象策略定义的接口,提供具体的算法实现。
结构图
案例实现
案例描述
有两种优惠方式,第一种是打折优惠,用商品总金额乘折扣百分比;第二种是满减优惠,到达满减门槛后减去对应优惠金额。
案例类图
代码实现
抽象策略类
Discount 优惠接口
public interface Discount {
/**
* 实际金额
* @param money double
* @return double
*/
Double realMoney(Double money);
}
具体策略类
折扣优惠类(Percentage)
public class Percentage implements Discount {
private final Double rebate;
public Percentage(Double rebate) {
this.rebate = rebate;
}
/**
* 实际金额
*
* @param money double
* @return double
*/
@Override
public Double realMoney(Double money) {
return money * rebate;
}
}
满减优惠类(FullDiscount)
public class FullDiscount implements Discount {
private final Double upperLimit;
private final Double subtract;
public FullDiscount(Double upperLimit, Double subtract) {
this.upperLimit = upperLimit;
this.subtract = subtract;
}
/**
* 实际金额
*
* @param money double
* @return double
*/
@Override
public Double realMoney(Double money) {
return money >= upperLimit ? money - subtract : money;
}
}
环境类
Context 上下文
public class Context {
private Discount discount;
public Context(Discount discount) {
this.discount = discount;
}
public Double getRealMoney(Double money) {
return discount.realMoney(money);
}
}
demo调用
public class StrategyPattern {
public static void main(String[] args) {
getMoney();
}
private static void getMoney(){
Context context = new Context(new Percentage(0.8));
Double realMoney = context.getRealMoney(1000.0);
System.out.println(realMoney);
context = new Context(new FullDiscount(500.0, 100.0));
realMoney = context.getRealMoney(realMoney);
System.out.println(realMoney);
}
}
关于优化
重复new创建对象
使用构造方法时,每次调用都需要重新new一个context对象,那么,在context中加入set方法,就可以解决这个问题。
Context类:
public class Context {
private Discount discount;
public void setDiscount(Discount discount) {
this.discount = discount;
}
public Context(Discount discount) {
this.discount = discount;
}
public Double getRealMoney(Double money) {
return discount.realMoney(money);
}
}
demo调用示例:
public class StrategyPattern {
public static void main(String[] args) {
getMoneyNoNew();
}
private static void getMoneyNoNew(){
Context context = new Context(new Percentage(0.8));
Double realMoney = context.getRealMoney(1000.0);
System.out.println(realMoney);
context.setDiscount(new FullDiscount(500.0, 100.0));
realMoney = context.getRealMoney(realMoney);
System.out.println(realMoney);
}
}