策略模式,同样可以消除掉繁杂的if else ,那么它和简单工厂模式的区别在哪?
简单工厂模式是根据传入的参数返回不同的产品,不同的产品内部封装了不同的逻辑。
策略模式类似,但是策略模式内部是封装了算法,也就是封装了某一种策略。
举个例子,超市活动打折优惠,1.普通计费。 2.满300减20 3.满400减80 4.满500打八折
如果用简单工厂模式,那么需要定义四个产品子类,如果将来又新增了类型, 比如说 5.满800减200 ,那么又需要定义一个新的产品子类。
仔细观察会发现,其实 2.满300减20 和3.满400减80 ,是同样的算法,只是参数不同。
然后满500打8折和满800减200,是也是一样的算法,只是参数不同。
因此,我们可以把这两类算法 分为 满减算法和打折算法。
满减算法
//参数为 原金额,优惠门槛,优惠金额
double moneyOff(double d1,double d2,double d3){
if(d1>=d2){
return d1-d3;
}else{
return d1;
}
}
打折算法
//参数为 原金额 打折折扣
duble discount (double d1,double d2){
return d1*d2;
}
策略模式是对算法进行的封装,简单工厂是对不同行为模式或者属性的对象进行的封装,比如说水果工厂, 会产出苹果对象,香蕉对象,他们的形状大小功能各不相同。
简单的来说,策略模式里面,封装了众多算法,但是这些算法 最终都是要为了做同一件事,比如说上文的 计算优惠价。
策略模式的uml类图:
以上的具体实现如下
策略接口
/**
* @Author laixiaoxing
* @Description 策略接口
* @Date 下午7:19 2019/6/8
*/
public interface Strategy {
/**
* 计算金额
* 接受原金额,返回打折后的金额
* @return
*/
double althmCash(double cash);
}
正常金额策略
/**
* @ClassName DiscountstrategyImpl
* @Author laixiaoxing
* @Date 2019/6/8 下午7:23
* @Description 正常计算金额的策略类
* @Version 1.0
*/
public class NomalstrategyImpl implements Strategy {
@Override
public double althmCash(double cash) {
return cash;
}
}
优惠满减策略
/**
* @ClassName MoneyOffStrategyImpl
* @Author laixiaoxing
* @Date 2019/6/8 下午7:25
* @Description 优惠满减策略类
* @Version 1.0
*/
public class MoneyOffStrategyImpl implements Strategy {
/**
* 优惠金额
*/
private double discountMoney;
/**
* 满减金额
*/
private double fullMoney;
public MoneyOffStrategyImpl(double discountMoney,double fullMoney) {
this.discountMoney = discountMoney;
this.fullMoney=fullMoney;
}
@Override
public double althmCash(double cash) {
if (cash >= fullMoney) {
return cash - discountMoney;
} else {
return cash;
}
}
}
打折优惠策略
/**
* @ClassName DiscountstrategyImpl
* @Author laixiaoxing
* @Date 2019/6/8 下午7:23
* @Description 计算打折优惠的策略类
* @Version 1.0
*/
public class DiscountstrategyImpl implements Strategy {
private double discount;
public DiscountstrategyImpl(double discount) {
this.discount = discount;
}
@Override
public double althmCash(double cash) {
return cash*discount;
}
}
策略模式context 维护策略模式的引用 ,
避免将策略模式对象暴露出去,面试对象的对外封闭,外部调用的时候只能通过context来调用。 这样的话,修改stragety的时候,就不需要去修改具改调用方的代码。
/**
* @ClassName context
* @Author laixiaoxing
* @Date 2019/6/8 下午7:29
* @Description 策略模式的上下文,维护对strategy对象的引用
* @Version 1.0
*/
public class Context {
private Strategy strategy;
public Context(Strategy strategy) {
this.strategy = strategy;
}
public double strategyAlthm(double cansh) {
return strategy.althmCash(cansh);
}
}
实际调用的时候
public static void main(String[] args) {
Context context;
String type = "满300减20";
switch (type) {
case "满300减20":
context = new Context(new MoneyOffStrategyImpl(20, 300));
break;
case "满400减80":
context = new Context(new MoneyOffStrategyImpl(80, 400));
break;
case "满500打八折":
context = new Context(new DiscountstrategyImpl(0.8));
break;
default:
context = new Context(new NomalstrategyImpl());
}
double result = context.strategyAlthm(300);
System.out.println("result:"+result);
}
这个时候,我们会发现,又回到了当初if else的时候,极其不友好。
优化一下,将context和简单工厂模式结合
结合后的context
public class Context {
private Strategy strategy;
public Context(String type) {
switch (type) {
case "满300减20":
strategy = new MoneyOffStrategyImpl(20, 300);
break;
case "满400减80":
strategy = new MoneyOffStrategyImpl(80, 400);
break;
case "满500打八折":
strategy = new DiscountstrategyImpl(0.8);
break;
default:
strategy = new NomalstrategyImpl();
}
}
public double strategyAlthm(double cansh) {
return strategy.althmCash(cansh);
}
}
调用方
public static void main(String[] args) {
String type = "满300减20";
Context context = new Context(type);
double result = context.strategyAlthm(300);
System.out.println("result:" + result);
}
和工厂模式的比较:
1.工厂模式封装的是对象,策略模式封装的是算法
2.工厂模式可能需要将工厂和产品都暴露给调用方,因为调用方可能会用到产品的不同方面。
但是策略模式,只需要将context暴露给调用方,其内部算法对调用方不可见,不需要将算法子类暴露出去,因为这些算法,本质上都是完成同一件事的不同方法。
工厂模式的链接:
https://blog.youkuaiyun.com/qq_20009015/article/details/91058102
策略模式的优点:
1.将使用算法的类和封装算法类,进行了解耦,简化了单元测试,可以通过自己的接口进行单独的单元测试,
2.只要是在不同的时间场合应用不同的业务规则,就可以考虑使用策略模式,而不是将多个行为堆积到一个类里面,可以很好的消费条件语句。
我们可以看到,现在不管是在context还是在factory里面,都封装了switch,非常不方便,一旦要修改策略或者产品,就需要改掉这一块。 可以通过反射,将这些都消除掉。
例如:
spring中的应用 ,在bean加载完之后启动的时候,将所有接口的实现子类都加载到一个map中,key为类型,value该类型对应的策略对象,然后在context里面,直接根据map里面的key去获取对象。
具体如下,
实现ApplicationContextAware接口,重写setApplicationContext方法,参数为ApplicationContext ,这样就可以拿到spring的ApplicationContext,这个是spring的上下文,里面有spring加载的所有对象信息。
最后通过applicationContext.getBeansOfType(clazz); 返回值是一个map,里面就是对应的class的对象,如果clazz是一个接口的类型,那么返回的就是这个接口的实现类。
/**
* @ClassName ApplicationContextHelper
* @Author laixiaoxing
* @Date 2019/3/13 下午4:09
* @Description 获取springbean的工具类
* @Version 1.0
*/
@Component
public class ApplicationContextHelper implements ApplicationContextAware {
ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public <T> T getBean(Class<T> clazz) {
return applicationContext.getBean(clazz);
}
public <T> Map<String, T> getBeansOfType(Class<T> clazz) {
return applicationContext.getBeansOfType(clazz);
}
}
或者
参考这个文章https://mp.weixin.qq.com/s/K0eTW7jvQlY6XToYw8z0mw