设计模式之策略模式
情景模式:
设计一个超市收银系统,超市可能会根据不同节日给出不同的折扣
代码:
@Setter
public class Market {
private double price;
private double discount=1;
public double getPriceAfterDiscount() {
return price * discount;
}
}
分析:
需求非常简单,用户可以根据需要输入相关的折扣,然后获取到最终的价格。但是,如果这个系统就是用来专门做超市收银,也就是说Market
类是本系统的核心类的话,那上面的代码就太过简单了:扩展性不高,作为系统的核心类,需求肯定会变,比如可能后面不仅仅是打折,可能还有积分活动,VIP活动等等,那个时候就得重构代码
情景模式二:
设计一个超市系统,超市可以根据用户购买的金额进行满减,并且用户可以通过积分进行一定额度的抵扣,并且在不同的节假日可以一定的折扣活动。
分析:正如上面所说的一样,随着超市的活动形式的增加,折扣的策略越来越多,如果我们依然按照上面的代码修改,会导致Market
这个类越来越臃肿,这样严重的违背了SOLID
设计原则。因此我们可以对代码进行修改:
代码:
首先抽象出打折接口:
public interface Strategy {
double getPriceAfterDiscount();
void setPrice(double price);
}
这里为了方便,抽象出一个统一的抽象类:
public abstract class AbstractStrategy implements Strategy {
protected double price;
public void setPrice(double price){
this.price=price;
}
public abstract double getPriceAfterDiscount();
}
定义打折类:
public class StrategyByDiscount extends AbstractStrategy {
private double disCount;
@Override
public double getPriceAfterDiscount() {
return disCount*price;
}
public StrategyByDiscount(double disCount){
this.disCount=disCount;
}
}
定义满减类:
public class StrategyByPriceBreak extends AbstractStrategy{
private double limit;
private double reduce;
public void setLimit(double limit){
this.limit=limit;
}
public void setReduce(double reduce){
this.reduce=reduce;
}
@Override
public double getPriceAfterDiscount() {
return price-((price/limit)*reduce);
}
public StrategyByPriceBreak(double limit,double reduce){
this.limit=limit;
this.reduce=reduce;
}
}
最后,根据用户用户选项,生成对应的类:
public class StrategyFactory {
public static Strategy createStrategy(double price, String strategyStr) {
Strategy strategy = null;
switch (strategyStr) {
case "打八折":
strategy = new StrategyByDiscount(0.8);
break;
case "打九折":
strategy = new StrategyByDiscount(0.9);
break;
case "每满100减20":
strategy = new StrategyByPriceBreak(100, 20);
break;
default:
strategy = new StrategyByDiscount(1);
}
strategy.setPrice(price);
return strategy;
}
}
分析:上面其中便是简单工厂的解决方案,使用简单工厂,我们能够生成对应的类,执行不同的操作,在以后的开发中,需要添加新的策略,直接继承并实现新的类即可,基本完成需求。
但是,到这里并没有完,Market
所做的并不只是这些,还有一些通用的功能,比如扫描条形码获取商品价格,记录会员信息,添加新的商品,判断商品是否过期等等。这些信息,和商品是否打折并不相关,不管价格打折与否,上面的方法都是通用的,因此,此时我们不应该是简单的把Strategy
交给用户直接使用,而是应该将其作为一个属性,传递给Market
类使用:
public class Market {
private Strategy strategy;
public Market(Strategy strategy){
this.strategy=strategy;
}
public double getTotalPrice(){
return strategy.getPriceAfterDiscount();
}
public double getSingleGoodsPrice(String key){
//...获取单个商品信息
return 0;
}
public Date getAcquisitionDate(String key){
//...获取商品生产日期
return null;
}
}
其中,Market
可以和StrategyFactory
结合起来,从而使用户更加便于使用:
public class Market{
private Strategy strategy;
public Market(String strategyStr){
this.strategy=StrategyFactory.createStrategy(strategyStr);
}
}
策略模式
从上面的代码中我们可以发现,通过封装不同的Strategy
类,我们实现了不同的打折策略。
但是对于Market
类来说,并没有什么影响,它完全不关心所传入的Strategy
的具体实现,而只用在需要调用它的地方调用相应的方法即可,我们结合了简单工厂生成对应的对象,传入给Market
,而Market
类中保存的都是与打折无关的通用的方法。
Q:明明用继承也能实现。为什么这里要用变量的形式传入
A:因为组合优于继承,在能使用组合的情况下,最好优先使用继承,需要使用继承的情况也有,当存在多处需要变化的时候,可以使用继承,这便是后面需要学习的模板方法模式
定义:定义一系列算法,将每个算法封装到具有公共接口的一系列策略中,从而使他们可以相互替换/或是让算法在不想影响客户端的情况下发生变化
策略模式是对象行为型模式
UML图:
Context
作为一个总的环境类,表示需要使用Strategy
对象的类-
Strategy
表示策略接口 -
ConcreteStrategeA
:表示具体的实现类
针对场景:
- 如果一个对象有很多类,而这些类的区别仅仅在于其某个行为不同,此时可以将变化抽象出来,将不变封装起来。
- 一个系统需要动态的在几种算法中选择其中一种
策略模式的核心思想,便是通过面向接口编程,将变化的东西封装起来,从而实现封装变化,策略模式也一般用于只有少量的变化地方,如果变化的内容过多,可能就需要使用模板方法替代。
在策略模式中,包含了三个角色:
- 环境类:真正使用策略的地方
- 策略接口
- 具体的策略实现
可以看出来,策略模式是典型的组合大于继承和面向接口编程的体现
策略类是一个比较简单的对象行为型的类,即使没有学习过设计模式,也可能在日常编码中用到