设计模式-策略模式
1、策略模式
策略模式(strategy pattern)的原始定义是:定义一系列算法,将每一个算法封装起来,并使它们可以相互替换。策略模式让算法可以独立于使用它的客户端而变化。
其实我们在现实生活中常常遇到实现某种目标存在多种策略可供选择的情况,例如,出行旅游可以乘坐飞机、乘坐火车、骑自行车或自己开私家车等。
在软件开发中,经常会遇到这种情况,开发一个功能可以通过多个算法去实现,我们可以将所有的算法集中在一个类中,在这个类中提供多个方法,每个方法对应一个算法,或者我们也可以将这些算法都封装在一个统一的方法中,使用if…else…等条件判断语句进行选择,但是这两种方式都存在硬编码的问题,后期需要增加算法就需要修改源代码,这会导致代码的维护变得困难
比如网购,你可以选择工商银行、农业银行、建设银行等等,但是它们提供的算法都是一致的,就是帮你付款。
在软件开发中也会遇到相似的情况,当实现某一个功能存在多种算法或者策略,我们可以根据环境或者条件的不同选择不同的算法或者策略来完成该功能。
2、策略模式实现
策略模式的本质是通过Context类来作为中心控制单元,对不同的策略进行调度分配。
抽象策略类
/**
* 抽象策略类
*
* @author duckquan
*/
public interface Strategy {
void algorithm();
}
具体实现类
/**
* 具体策略-A
*
* @author duckquan
*/
public class ConcreteStrategyA implements Strategy {
@Override
public void algorithm() {
System.out.println("执行策略A");
}
}
/**
* 具体策略-B
*
* @author duckquan
*/
public class ConcreteStrategyB implements Strategy {
@Override
public void algorithm() {
System.out.println("执行策略B");
}
}
环境类
/**
* 环境类
*
* @author duckquan
*/
public class Context {
//维持一个对抽象策略类的引用
private Strategy strategy;
public Context(Strategy strategy) {
this.strategy = strategy;
}
/**
* 调用策略类中的算法
*/
public void algorithm() {
strategy.algorithm();
}
}
测试类
import com.example.study.design.strategy.ConcreteStrategyA;
import com.example.study.design.strategy.Context;
import com.example.study.design.strategy.Strategy;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
/**
* 策略模式测试类
*/
@SpringBootTest
public class StrategyTest {
/**
* 策略模式测试
*/
@Test
public void strategyTest() {
Strategy strategy = new ConcreteStrategyA();
Context context = new Context(strategy);
context.algorithm();
}
}
3、策略模式应用实例
面试问题:如何用设计模式消除代码中的if-else
在某系统中,有多种不同的业务产品线
,不同产品线
在同一个业务上,处理方式与返回数据也略有不同。
这里列举几种产品线:MT001、MT002、MT003,系统在收到请求后,根据不同的产品线
进行对应的业务逻辑处理。
不使用设计模式
- 商品类
import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; /** * 商品类 * * @author duckquan */ @Data @NoArgsConstructor @AllArgsConstructor public class Goods { /** * 产品线编号 * MT001、MT002、MT003 */ private String productCode; /** * 产品线名称 */ private String productName; /** * 商品id */ private String goodsId; /** * 商品名称 */ private String goodsName; }
- 商品服务类
import com.example.study.model.Goods; import java.util.List; /** * 商品服务 * * @author duckquan */ public interface GoodsService { /** * 商品列表 * * @return */ List<Goods> goodsList(); }
import com.example.study.model.Goods; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.List; /** * 商品服务实现类 * * @author duckquan */ @Service public class GoodsServiceImpl implements GoodsService { @Override public List<Goods> goodsList() { //假装查询了数据库 List<Goods> goodsList = new ArrayList<>(); goodsList.add(new Goods("MT001", "产品线1号", "MT001202404151125", "测试商品-001")); goodsList.add(new Goods("MT002", "产品线2号", "MT002202404151135", "测试商品-002")); goodsList.add(new Goods("MT003", "产品线3号", "MT002202404151145", "测试商品-003")); return goodsList; } }
- 测试类
import com.example.study.design.strategy.ConcreteStrategyA; import com.example.study.design.strategy.Context; import com.example.study.design.strategy.Strategy; import com.example.study.model.Goods; import com.example.study.service.GoodsService; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import java.util.List; /** * 策略模式测试类 */ @SpringBootTest public class StrategyTest { @Autowired private GoodsService goodsService; /** * 策略模式测试 */ @Test public void strategyTest() { Strategy strategy = new ConcreteStrategyA(); Context context = new Context(strategy); context.algorithm(); } /** * 无策略模式 */ @Test public void nonStrategyTest() { List<Goods> goodsList = goodsService.goodsList(); for (Goods goods : goodsList) { if ("MT001".equals(goods.getProductCode())) { System.out.println(goods.getProductName() + "执行业务逻辑处理"); } if ("MT002".equals(goods.getProductCode())) { System.out.println(goods.getProductName() + "执行业务逻辑处理"); } if ("MT003".equals(goods.getProductCode())) { System.out.println(goods.getProductName() + "执行业务逻辑处理"); } //...... } } }
使用策略模式进行优化
通过策略模式,将所有的if-else
分支的业务逻辑抽取为各种策略类,让客户端去依赖策略接口,保证具体策略类的改变不影响客户端。
- 策略接口
import com.example.study.model.Goods; /** * 商品处理策略接口 * * @author duckquan */ public interface GoodsHandleStrategy { Goods handleGoods(Goods goods); }
- 具体策略类
import com.example.study.model.Goods; /** * 产品线1号处理策略 * * @author duckquan */ public class Mt001GoodsHandleStrategy implements GoodsHandleStrategy { @Override public Goods handleGoods(Goods goods) { System.out.println("产品线1号处理中:" + goods.getGoodsName()); return goods; } }
import com.example.study.model.Goods; /** * 产品线2号处理策略 * * @author duckquan */ public class Mt002GoodsHandleStrategy implements GoodsHandleStrategy { @Override public Goods handleGoods(Goods goods) { System.out.println("产品线2号处理中:" + goods.getGoodsName()); return goods; } }
- 策略上下文(策略接口持有者)
import com.example.study.model.Goods; import java.util.Objects; /** * 策略上下文(策略接口持有者) * * @author duckquan */ public class GoodsStrategyContext { private GoodsHandleStrategy goodsHandleStrategy; public void setGoodsHandleStrategy(GoodsHandleStrategy goodsHandleStrategy) { this.goodsHandleStrategy = goodsHandleStrategy; } public Goods handleGoods(Goods goods) { if (Objects.isNull(goodsHandleStrategy)) { return null; } return goodsHandleStrategy.handleGoods(goods); } }
- 策略工厂
import lombok.NoArgsConstructor; import java.util.HashMap; import java.util.Map; /** * 策略工厂 * * @author duckquan */ @NoArgsConstructor public class GoodsHandleStrategyFactory { //使用Map集合存储策略信息,彻底消除if...else private static Map<String, GoodsHandleStrategy> strategyMap; //初始化具体策略,保存到map集合 public static void init() { strategyMap = new HashMap<>(); strategyMap.put("MT001", new Mt001GoodsHandleStrategy()); strategyMap.put("MT002", new Mt002GoodsHandleStrategy()); } //根据产品线code获取对应策略对象 public static GoodsHandleStrategy getGoodsHandleStrategy(String productCode) { return strategyMap.get(productCode); } }
- 测试
import com.example.study.design.strategy.*; import com.example.study.model.Goods; import com.example.study.service.GoodsService; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import java.util.List; /** * 策略模式测试类 */ @SpringBootTest public class StrategyTest { @Autowired private GoodsService goodsService; /** * 策略模式测试 */ @Test public void strategyTest() { Strategy strategy = new ConcreteStrategyA(); Context context = new Context(strategy); context.algorithm(); } /** * 无策略模式 */ @Test public void nonStrategyTest() { List<Goods> goodsList = goodsService.goodsList(); for (Goods goods : goodsList) { if ("MT001".equals(goods.getProductCode())) { System.out.println(goods.getProductName() + "执行业务逻辑处理"); } if ("MT002".equals(goods.getProductCode())) { System.out.println(goods.getProductName() + "执行业务逻辑处理"); } if ("MT003".equals(goods.getProductCode())) { System.out.println(goods.getProductName() + "执行业务逻辑处理"); } //...... } } /** * 应用实例策略模式测试 */ @Test public void goodsStrategyTest() { List<Goods> goodsList = goodsService.goodsList(); //策略上下文 GoodsStrategyContext context = new GoodsStrategyContext(); for (Goods goods : goodsList) { //获取具体策略 GoodsHandleStrategyFactory.init(); GoodsHandleStrategy strategy = GoodsHandleStrategyFactory.getGoodsHandleStrategy(goods.getProductCode()); //设置策略 context.setGoodsHandleStrategy(strategy); //执行策略 context.handleGoods(goods); } } }
经过上面的改造,我们已经消除了if-else的结构,每当新来一种产品线,只需要添加的产品线处理策略,并修改GoodsHandleStrategyFactory
中的Map集合,如果要是的程序符合开闭原则,则需要调整GoodsHandleStrategyFactory
中处理策略的获取方式,通过反射的方式,获取指定包下的所有GoodsHandleStrategy
实现类,然后放到Map集合中去。
4、策略模式总结
策略模式优点:
- 策略类之间可以自由切换:由于策略类都实现同一个接口,所以使它们之间可以自由切换
- 易于扩展:增加一个新的策略只需要添加一个具体的策略类即可,基本不需要改变原有的代码,符合“开闭原则“
- 避免使用多重条件选择语句(if else),充分体现面向对象设计思想
策略模式缺点:
- 客户端必须知道所有的策略类,并自行决定使用哪一个策略类
- 策略模式将造成产生很多策略类
策略模式使用场景:
- 一个系统需要动态地在几种算法中选择一种时,可将每个算法封装到策略类中
策略模式最大的作用在于分离使用算法的逻辑和算法自身实现的逻辑,这样就意味着当我们想要优化算法自身的实现逻辑时就变得非常便捷,一方面可以采用最新的算法实现逻辑,另一方面可以直接弃用旧算法而采用新算法。使用策略模式能够很方便地进行替换。
- 一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现,可将每个条件分支移入它们各自的策略类中以代替这些条件语句
在实际开发中,有许多算法可以实现某一功能,如查找、排序等,通过
if-else
等条件判断语句来进行选择非常方便。但是这就会带来一个问题:当在这个算法类中封装了大量查找算法时,该类的代码就会变得非常复杂,维护也会突然就变得非常困难。虽然策略模式看上去比较笨重,但实际上在每一次新增策略时都通过新增类来进行隔离,短期虽然不如直接写if-else
来得效率高,但长期来看,维护单一的简单类耗费的时间其实远远低于维护一个超大的复杂类。
设计原则和思想其实比设计模式更加的普适和重要,掌握了代码的设计原则和思想,我们自然而然的就可以使用到设计模式,还有可能自己创建出一种新的设计模式