设计模式-3.策略模式

在讲策略模式之前,我们先看一个日常生活中的小例子:

  现实生活中我们到商场买东西的时候,卖场往往根据不同的客户制定不同的报价策略,比如针对新客户不打折扣,针对老客户打9折,针对VIP客户打8折...

  现在我们要做一个报价管理的模块,简要点就是要针对不同的客户,提供不同的折扣报价。

如果是有你来做,你会怎么做?

我们很有可能写出下面的代码:

package strategy.examp02;
 
import java.math.BigDecimal;
 
public class QuoteManager {
 
    public BigDecimal quote(BigDecimal originalPrice,String customType){
        if ("新客户".equals(customType)) {
            System.out.println("抱歉!新客户没有折扣!");
            return originalPrice;
        }else if ("老客户".equals(customType)) {
            System.out.println("恭喜你!老客户打9折!");
            originalPrice = originalPrice.multiply(new BigDecimal(0.9)).setScale(2,BigDecimal.ROUND_HALF_UP);
            return originalPrice;
        }else if("VIP客户".equals(customType)){
            System.out.println("恭喜你!VIP客户打8折!");
            originalPrice = originalPrice.multiply(new BigDecimal(0.8)).setScale(2,BigDecimal.ROUND_HALF_UP);
            return originalPrice;
        }
        //其他人员都是原价
        return originalPrice;
    }
 
}

经过测试,上面的代码工作的很好,可是上面的代码是有问题的。上面存在的问题:把不同客户的报价的算法都放在了同一个方法里面,使得该方法很是庞大(现在是只是一个演示,所以看起来还不是很臃肿)。

下面看一下上面的改进,我们把不同客户的报价的算法都单独作为一个方法
 

package strategy.examp02;
 
import java.math.BigDecimal;
 
public class QuoteManagerImprove {
 
    public BigDecimal quote(BigDecimal originalPrice, String customType){
        if ("新客户".equals(customType)) {
            return this.quoteNewCustomer(originalPrice);
        }else if ("老客户".equals(customType)) {
            return this.quoteOldCustomer(originalPrice);
        }else if("VIP客户".equals(customType)){
            return this.quoteVIPCustomer(originalPrice);
        }
        //其他人员都是原价
        return originalPrice;
    }
 
    /**
     * 对VIP客户的报价算法
     * @param originalPrice 原价
     * @return 折后价
     */
    private BigDecimal quoteVIPCustomer(BigDecimal originalPrice) {
        System.out.println("恭喜!VIP客户打8折");
        originalPrice = originalPrice.multiply(new BigDecimal(0.8)).setScale(2,BigDecimal.ROUND_HALF_UP);
        return originalPrice;
    }
 
    /**
     * 对老客户的报价算法
     * @param originalPrice 原价
     * @return 折后价
     */
    private BigDecimal quoteOldCustomer(BigDecimal originalPrice) {
        System.out.println("恭喜!老客户打9折");
        originalPrice = originalPrice.multiply(new BigDecimal(0.9)).setScale(2,BigDecimal.ROUND_HALF_UP);
        return originalPrice;
    }
 
    /**
     * 对新客户的报价算法
     * @param originalPrice 原价
     * @return 折后价
     */
    private BigDecimal quoteNewCustomer(BigDecimal originalPrice) {
        System.out.println("抱歉!新客户没有折扣!");
        return originalPrice;
    }
 
}

上面的代码比刚开始的时候要好一点,它把每个具体的算法都单独抽出来作为一个方法,当某一个具体的算法有了变动的时候,只需要修改响应的报价算法就可以了。

但是改进后的代码还是有问题的,那有什么问题呢?

1.当我们新增一个客户类型的时候,首先要添加一个该种客户类型的报价算法方法,然后再quote方法中再加一个else if的分支,是不是感觉很是麻烦呢?而且这也违反了设计原则之一的开闭原则(open-closed-principle).

开闭原则:

  对于扩展是开放的(Open for extension)。这意味着模块的行为是可以扩展的。当应用的需求改变时,我们可以对模块进行扩展,使其具有满足那些改变的新行为。也就是说,我们可以改变模块的功能。

  对于修改是关闭的(Closed for modification)。对模块行为进行扩展时,不必改动模块的源代码或者二进制代码。

2.我们经常会面临这样的情况,不同的时期使用不同的报价规则,比如在各个节假日举行的各种促销活动时、商场店庆时往往都有普遍的折扣,但是促销时间一旦过去,报价就要回到正常价格上来。按照上面的代码我们就得修改if else里面的代  码很是麻烦

那有没有什么办法使得我们的报价管理即可扩展、可维护,又可以方便的响应变化呢?当然有解决方案啦,就是我们下面要讲的策略模式。

 

定义:

  策略模式定义了一系列的算法,并将每一个算法封装起来,使每个算法可以相互替代,使算法本身和使用算法的客户端分割开来,相互独立。

结构:

  1.策略接口角色IStrategy:用来约束一系列具体的策略算法,策略上下文角色ConcreteStrategy使用此策略接口来调用具体的策略所实现的算法。

  2.具体策略实现角色ConcreteStrategy:具体的策略实现,即具体的算法实现。

  3.策略上下文角色StrategyContext:策略上下文,负责和具体的策略实现交互,通常策略上下文对象会持有一个真正的策略实现对象,策略上下文还可以让具体的策略实现从其中获取相关数据,回调策略上下文对象的方法。

UML类图:

UML序列图: 

 

策略模式代码的一般通用实现:

策略接口

package strategy.examp01;
 
//策略接口
public interface IStrategy {
    //定义的抽象算法方法 来约束具体的算法实现方法
    public void algorithmMethod();
}

 具体的策略实现:

package strategy.examp01;
 
// 具体的策略实现
public class ConcreteStrategy implements IStrategy {
    //具体的算法实现
    @Override
    public void algorithmMethod() {
        System.out.println("this is ConcreteStrategy method...");
    }
}
package strategy.examp01;
 
 // 具体的策略实现2
public class ConcreteStrategy2 implements IStrategy {
     //具体的算法实现
    @Override
    public void algorithmMethod() {
        System.out.println("this is ConcreteStrategy2 method...");
    }
}

策略上下文:

package strategy.examp01;
 
/**
 * 策略上下文
 */
public class StrategyContext {
    //持有一个策略实现的引用
    private IStrategy strategy;
    //使用构造器注入具体的策略类
    public StrategyContext(IStrategy strategy) {
        this.strategy = strategy;
    }
 
    public void contextMethod(){
        //调用策略实现的方法
        strategy.algorithmMethod();
    }
}

 

package strategy.examp01;
 
//外部客户端
public class Client {
    public static void main(String[] args) {
        //1.创建具体测策略实现
        IStrategy strategy = new ConcreteStrategy2();
        //2.在创建策略上下文的同时,将具体的策略实现对象注入到策略上下文当中
        StrategyContext ctx = new StrategyContext(strategy);
        //3.调用上下文对象的方法来完成对具体策略实现的回调
        ctx.contextMethod();
    }
}

策略模式的优点:

  1.策略模式的功能就是通过抽象、封装来定义一系列的算法,使得这些算法可以相互替换,所以为这些算法定义一个公共的接口,以约束这些算法的功能实现。如果这些算法具有公共的功能,可以将接口变为抽象类,将公共功能放到抽象父类里面。

  2.策略模式的一系列算法是可以相互替换的、是平等的,写在一起就是if-else组织结构,如果算法实现里又有条件语句,就构成了多重条件语句,可以用策略模式,避免这样的多重条件语句。

  3.扩展性更好:在策略模式中扩展策略实现非常的容易,只要新增一个策略实现类,然后在使用策略实现的地方,使用这个新的策略实现就好了。

 

策略模式的缺点:

    1.客户端必须了解所有的策略,清楚它们的不同:

     如果由客户端来决定使用何种算法,那客户端必须知道所有的策略,清楚各个策略的功能和不同,这样才能做出正确的选择,但是这暴露了策略的具体实现。

  2.增加了对象的数量:

    由于策略模式将每个具体的算法都单独封装为一个策略类,如果可选的策略有很多的话,那对象的数量也会很多。

  3.只适合偏平的算法结构:

    由于策略模式的各个策略实现是平等的关系(可相互替换),实际上就构成了一个扁平的算法结构。即一个策略接口下面有多个平等的策略实现(多个策略实现是兄弟关系),并且运行时只能有一个算法被使用。这就限制了算法的使用层级,且不能被嵌套。

 

策略模式的本质:

  分离算法,选择实现。

  如果你仔细思考策略模式的结构和功能的话,就会发现:如果没有上下文,策略模式就回到了最基本的接口和实现了,只要是面向接口编程,就能够享受到面向接口编程带来的好处,通过一个统一的策略接口来封装和分离各个具体的策略实现,无需关系具体的策略实现。

  貌似没有上下文什么事,但是如果没有上下文的话,客户端就必须直接和具体的策略实现进行交互了,尤其是需要提供一些公共功能或者是存储一些状态的时候,会大大增加客户端使用的难度;引入上下文之后,这部分工作可以由上下文来完成,客户端只需要和上下文进行交互就可以了。这样可以让策略模式更具有整体性,客户端也更加的简单。

  策略模式体现了开闭原则:策略模式把一系列的可变算法进行封装,从而定义了良好的程序结构,在出现新的算法的时候,可以很容易的将新的算法实现加入到已有的系统中,而已有的实现不需要修改。

  策略模式体现了里氏替换原则:策略模式是一个扁平的结构,各个策略实现都是兄弟关系,实现了同一个接口或者继承了同一个抽象类。这样只要使用策略的客户端保持面向抽象编程,就可以动态的切换不同的策略实现以进行替换。

 

二.很多时候我们通过spring去管理具体的策略对象。

 

策略接口:

public interface PayStrategy {

	public String payType(String type);
}

具体策略实现1

@Component
public class AliPayStrategy implements PayStrategy{

	@Override
	public String payType(String type) {
		// TODO Auto-generated method stub
		return "支付宝付款";
	}

}

具体策略实现2

@Component
public class WeixinPayStrategy implements PayStrategy	{

	@Override
	public String payType(String type) {
		// TODO Auto-generated method stub
		return "微信付款";
	}

}

我们将策略实现对象的beanId保存到数据库中,spring的上下文对象可以通过beanId获取具体的策略实现对象,将策略实现对象交给spring管理,这里也可以通过工厂的方式去创建具体的策略实现对象

spring的工具类,通过beanid获取spring管理的对象

@Component
public class SpringUtils implements ApplicationContextAware{
    private static ApplicationContext applicationContext;
	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		
		this.applicationContext = applicationContext;
		
	}

	public static ApplicationContext getApplicationContext() {
		
		return applicationContext;
	}
	//通过name获取bean
	public static Object getBean(String name) {
		
		return applicationContext.getBean(name);
	}
	//通过class 获取Bean
	public static <T> T getBean(Class<T> clazz) {
		
		return applicationContext.getBean(clazz);
	} 
	//通过name class 获取bean
	public static <T> T getBean(String name, Class<T> clazz) {
		return applicationContext.getBean(name,clazz);
	}
	
}

具体的实现策略

@Component
public class PayContextStrategy {

	@Autowired
	PayChannelRepository payChannelRepository;
	
//	public String payType(String type) {
//	if(StringUtils.isEmpty(type)) {
//		return "type is not null";
//		
//	}	
//	PayStrategy startegy = PayStrategyFactory.getPayStrategy(type);
//	if(startegy == null) {
//		return "no pay startegy";
//	}
//	return startegy.payType(type);
//	}
	
	    public String payType(String type) {
	    Optional<PayChannel> optional = payChannelRepository.findById(type);
	    PayChannel payChannel = optional.isPresent()?optional.get() : null;
	    if(payChannel==null) {
	    	return null;
	    }
	    String beanId = payChannel.getStrategyBeanId();	
	    PayStrategy startegy = (PayStrategy) SpringUtils.getBean(beanId);
	    if(startegy==null) {
	    	return null;
	    }
		return startegy.payType(type);
		}
}

执行策略代码,通过type选择不同的策略执行。

@RestController
public class PayController {

	@Autowired
	PayContextStrategy payContextStrategy;
	
	@RequestMapping("/payType")
	public String payType(String type) {
		return payContextStrategy.payType(type);
     }
	
	
	
 }

思考:看到上面的代码是不是感觉很熟悉,统一的方法接口,对方法接口的不同实现,通过实例化不同实现类去调用接口,和工厂模式及其相似。具体还是有区别的,策略模式是一种平均选择算法,为了减少if else的使用,工厂模式是为了去创建用户所需要的对象,策略模式和工厂模式可以互相实现,但是策略模式比工厂模式更为复杂一些。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值