博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
设计模式-策略模式
阅读量:7041 次
发布时间:2019-06-28

本文共 5769 字,大约阅读时间需要 19 分钟。

  hot3.png

策略模式(Strategy pattern),定义一系列算法,将每一种算法发封装起来可以相互替换使用,策略模式让算法独立于使用它的客户端而独立变化,也就是算法发生变化,不影响客户端的使用。

策略模式包含三个角色:

上下文(StrategyContext):包含一个Strategy的引用,通过它调用Strategy方法。

抽象策略(Strategy):一般为抽象类或接口。

具体策略类(ConcreteStrategy):策略的具体实现,一般为多个。

221902_X9xh_76720.png

下面我们以商场会员打折为例:商场会员分为普通会员(不打折),银卡会员(打九折)和金卡会员(打八折)。源代码如下:

打折策略接口

public interface DiscountStrategy {		double getDiscountPrice(double price);}

具体打折策略-普通会员

/** * 普通会员策略,不打折。 *  * @author admin * */public class CommonDiscountStrategy implements DiscountStrategy {	@Override	public double getDiscountPrice(double price) {				return price;	}}

具体打折策略-银卡会员

/** * 银卡会员打九折 *  * @author admin * */public class SilverDiscountStrategy implements DiscountStrategy {	@Override	public double getDiscountPrice(double price) {				return price * 0.9;	}}

具体打折策略-金卡会员

/** * 金卡会员打八折 *  * @author admin * */public class GoldDiscountStrategy implements DiscountStrategy {	@Override	public double getDiscountPrice(double price) {				return price * 0.8;	}}

策略上下文

public class DiscountContext {		private DiscountStrategy discountStrategy;		public DiscountContext(DiscountStrategy discountStrategy) {		this.discountStrategy = discountStrategy;	}		/**	 * 获取打折后的最终价格,	 * 该方法可以对计算结果进行格式化,比如加上单位、精确小数位等,是客户端更方便使用。	 * 	 * @param price	 * @return	 */	public String computePrice(double price) {		double discountPrice = discountStrategy.getDiscountPrice(price);		StringBuilder resultBuilder = new StringBuilder("¥")		        .append(discountPrice).append("元");		return resultBuilder.toString();	}}

Client

public class Client {		public static void main(String[] args) {		DiscountStrategy strategy = new GoldDiscountStrategy();		DiscountContext context = new DiscountContext(strategy);		double originalPrice = 100.00;		String discountPrice = context.computePrice(originalPrice);		System.out.println("原价为" + originalPrice + 				", 作为金卡会员,购物花费为:" + discountPrice);	}}

运行结果:

    原价为100.0, 作为金卡会员,购物花费为:¥80.0元

大功告成?且慢!在上述实现中,需要客户端自己去判断选择算法,能不能根据会员的类型,自动去选择呢?能不能增添或删除策略类,而不去修改客户端的方法呢?答案是工厂。我们将DiscountContext与工厂模式相结合,代码如下:

结合工厂之后的上下文:

public class DiscountContext2 {    private DiscountStrategy discountStrategy;		public DiscountContext2(String type) throws Exception {		if (type.equals("普通")) {			discountStrategy = new CommonDiscountStrategy();		} else if (type.equals("银卡")) {			discountStrategy = new SilverDiscountStrategy();		} else if (type.equals("金卡")) {			discountStrategy = new GoldDiscountStrategy();		} else {			throw new Exception("会员类型不对");		}	}		/**	 * 获取打折后的最终价格,	 * 该方法可以对计算结果进行格式化,比如加上单位、精确小数位等,是客户端更方便使用。	 * 	 * @param price	 * @return	 */	public String computePrice(double price) {		double discountPrice = discountStrategy.getDiscountPrice(price);		StringBuilder resultBuilder = new StringBuilder("¥")		        .append(discountPrice).append("元");		return resultBuilder.toString();	}}

修改之后的客户端

public class Client2 {	public static void main(String[] args) {		String type = "金卡";		DiscountContext2 context = null;		try {			context = new DiscountContext2(type);		} catch (Exception e) {			// TODO Auto-generated catch block			e.printStackTrace();		}		double originalPrice = 100.00;		String discountPrice = context.computePrice(originalPrice);		System.out.println("原价为" + originalPrice + 				", 作为" + type + "会员,购物花费为:" + discountPrice);	}}

优化完毕,如果此时添加或者删除一个策略,我只需要修改策略上下文即可,对客户端无任何影响。

修改之后运行结果:

    原价为100.0, 作为金卡会员,购物花费为:¥80.0元

上面实现了策略模式+工厂模式,能不能进一步优化呢?我们知道Spring是天然的工厂,当策略模式遇到了Spring,是不是更加美如画了呢?下面我们来实现一下。

策略的接口和各个具体策略类保持不变,将它们交给Spring容器管理。为了更加直观一些,在这里我们用xml配置方式,当然你也可以选择annotation的方式。

我们需要给策略上下文添加一个Map(Key为会员类型,Value为具体策略对象的一个引用),用于存放各个策略,这样我们可以很方便的根据会员类型获取相应的打折策略。当然我们需要事先将策略注入到Map中。

DiscountContextNew.java

public class DiscountContextNew {    private DiscountStrategy discountStrategy;    // 将打折策略注入到Map中。    Map
 strategyMap = new HashMap
(); /**  * 获取打折后的最终价格,  * 该方法可以对计算结果进行格式化,比如加上单位、精确小数位等,是客户端更方便使用。  *   * @param price  * @return  * @throws Exception   */ public String computePrice(String type, double price) throws Exception { // 获取策略 discountStrategy = strategyMap.get(type); if (discountStrategy == null) { throw new Exception("没有相对应的策略。"); } double discountPrice = discountStrategy.getDiscountPrice(price); StringBuilder resultBuilder = new StringBuilder("¥")         .append(discountPrice).append("元"); return resultBuilder.toString(); } public Map
 getStrategyMap() { return strategyMap; } // 别忘了map的setter方法哦。 public void setStrategyMap(Map
 strategyMap) { this.strategyMap = strategyMap; } }

applicationContext.xml (将策略注入到Map中)

    
        
            
            
            
        
    

客户端需要一些变动,就是先根据Spring配置文件初始化spring容器、获得一个上下文,然后从spring容器中获取DiscountContextNew的实例,接下来就是计算价格了。

ClientNew.java 

String type = "金卡";double originalPrice = 100.00;		ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");DiscountContextNew discountContextNew = ac.getBean(DiscountContextNew.class);		String discountPrice = null;try {    discountPrice = discountContextNew.computePrice(type, originalPrice);} catch (Exception e) {    // TODO Auto-generated catch block    e.printStackTrace();}System.out.println("原价为" + originalPrice + 	", 作为" + type + "会员,购物花费为:" + discountPrice);

如果此时再添加或者删除一个策略,我只需要修改Spring配置文件即可,对客户端无任何影响。

运行main方法,还是我们熟悉的结果:

    原价为100.0, 作为金卡会员,购物花费为:¥80.0元

设计原则:

开闭原则:增加一个策略,只需要增加一个策略抽象类(或接口)的具体实现即可,对原有系统没有任何影响。

单一职责原则:没一种策略都在单独的类中,专注于自身的算法。

适用场景:

  1. 所有算法具备一些共有行为:本例中为计算打折后的价格。

  2. 所有算法都是平等的:因为平等,所以能自由切换。

  3. 运行时策略是唯一的。

总结

策略模式优点:避免了多个if-else分支,也避免了if-else深度嵌套(降低逻辑复杂度,UT写起来也方便了);降低客户端与具体业务的复杂的,符合开闭原则。

缺点:客户端需要自行选择策略(工厂模式解决了这一问题);每一种算法都需要一个具体类去实现,可能会导致程序中的对象过多。

友情提示:

源码地址(GitHub):

整个工程为Maven工程,对Maven不熟悉的同学可以查看我的另一篇博客

转载于:https://my.oschina.net/mup/blog/356901

你可能感兴趣的文章
远程连接docker daemon,Docker Remote API
查看>>
C语言dll文件的说明以及生成、使用方法
查看>>
.NET零基础入门05:委托与事件
查看>>
【阿里云MVP公益共创项目】服务数万爱心教师支教,推动中国渔业生态保护
查看>>
Linux命令复习和练习_03
查看>>
使用 github pages, 快速部署你的静态网页
查看>>
react 之 state 对象
查看>>
Java中的锁原理、锁优化、CAS、AQS
查看>>
“智能厨电+渠道精耕”,华帝迈出“关键一步”
查看>>
Scrapy爬虫(2)爬取新浪旅游图片
查看>>
Nginx反向代理以及负载均衡配置
查看>>
巨头抢滩视频云 金山云稳坐头把交椅
查看>>
索尼富士康领投,AR显示技术厂商Digilens获得2200万美元融资
查看>>
Qt5 GUI 开发的应用易受远程代码执行漏洞的影响
查看>>
搞懂Java动态代理
查看>>
NTKO使用说明
查看>>
django实现目录上传(最简单的方法)
查看>>
用update和replace在sql中替换某一个字段的部分内容
查看>>
Web框架原理
查看>>
dTree JS 基本用法
查看>>