Loading...

设计模式-策略模式

前言

在 Java 开发中,设计模式是常用的一种编码方式。合理使用设计模式,可以帮助开发人员更快地写出可靠、容易维护的代码。本期继续更新设计模式系列,总共 23 种设计模式会用一篇一篇文章来讲解,相关代码已经开源:Gitee点击跳转

在上一篇《Java设计模式(19)状态模式》介绍了订单在不同状态下如何切换处理逻辑。本文是这个系列的第二十篇,继续介绍行为型设计模式中的策略模式

策略模式状态模式在代码结构上有些相似,都通过接口隔离具体实现。不过它们解决的问题不一样。状态模式关心对象当前处于什么阶段,状态变化后,行为也跟着变化;策略模式关心的是同一件事有多种做法,调用方可以根据条件选择其中一种。

策略模式

策略模式是一种行为型设计模式。它把一组可以互相替换的算法或处理方式分别封装起来,对外提供统一接口。调用方只负责选择策略,不需要把每一种处理逻辑都写进自己的方法。

比如制定旅游计划,不同日期安排不同目的地。直接写的话,通常会先判断今天是星期几,再通过 if、else if 或 switch 执行对应计划。七天的逻辑都挤在一个方法里,短期看没什么问题,但后面增加节假日计划、临时计划或者其他规则时,这个方法会越来越长。

策略模式可以把星期一到星期天的计划拆成七个类。它们都实现相同的 TravelPlanStrategy 接口,调用方通过统一的 plan 方法执行计划。至于选择哪个实现,可以交给 Map、工厂类或者配置中心处理。

使用场景

  • 同一个业务动作有多种处理方式,并且运行时需要从中选择一种,例如支付方式、优惠计算、物流计费和排序算法。
  • 代码中存在较长的 if、else if 或 switch,每个分支内部都是一套相对独立的业务逻辑。
  • 算法经常新增或调整,但调用流程基本不变。
  • 希望调用方只依赖统一接口,不直接了解每种算法的实现细节。

代码实现

下面使用项目中的旅游计划示例。七天对应七种不同的旅游计划,程序把这些计划注册到 StrategyMap 中,再通过星期编号找到并执行对应策略。

先定义旅游计划接口。所有具体策略都实现 plan 方法,因此调用方拿到 TravelPlanStrategy 后,可以用同一种方式执行。

/**
 * 旅游计划接口
 * @author Jensen
 * @date 2024-01-19
 */
public interface TravelPlanStrategy {
    void plan();
}

接下来实现星期一到星期天的旅游计划。每个类只处理当天的安排,类之间没有相互依赖。

/**
 * 星期一的计划
 * @author Jensen
 * @date 2024-01-19
 */
public class Monday implements TravelPlanStrategy {
    @Override
    public void plan() {
        System.out.println("星期一:去北京");
    }
}

/**
 * 星期二的计划
 * @author Jensen
 * @date 2024-01-19
 */
public class Tuesday implements TravelPlanStrategy {
    @Override
    public void plan() {
        System.out.println("星期二:去上海");
    }
}

/**
 * 星期三的计划
 * @author Jensen
 * @date 2024-01-19
 */
public class Wednesday implements TravelPlanStrategy {
    @Override
    public void plan() {
        System.out.println("星期三:去重庆");
    }
}

/**
 * 星期四的计划
 * @author Jensen
 * @date 2024-01-19
 */
public class Thursday implements TravelPlanStrategy {
    @Override
    public void plan() {
        System.out.println("星期四:去成都");
    }
}

/**
 * 星期五的计划
 * @author Jensen
 * @date 2024-01-19
 */
public class Friday implements TravelPlanStrategy {
    @Override
    public void plan() {
        System.out.println("星期五:去广州");
    }
}

/**
 * 星期六的计划
 * @author Jensen
 * @date 2024-01-19
 */
public class Saturday implements TravelPlanStrategy {
    @Override
    public void plan() {
        System.out.println("星期六:去深圳");
    }
}

/**
 * 星期天的计划
 * @author Jensen
 * @date 2024-01-19
 */
public class Sunday implements TravelPlanStrategy {
    @Override
    public void plan() {
        System.out.println("星期天:回家");
    }
}

有了具体策略后,再通过 StrategyMap 统一注册。Map 的键是星期编号,值是对应的 TravelPlanStrategy 实现。

/**
 * 策略注册
 * @author Jensen
 * @date 2024-01-19
 */
public class StrategyMap {
    final Map<Integer, TravelPlanStrategy> StrategyMap = new HashMap<>();
    {
        StrategyMap.put(1, new Monday());
        StrategyMap.put(2, new Tuesday());
        StrategyMap.put(3, new Wednesday());
        StrategyMap.put(4, new Thursday());
        StrategyMap.put(5, new Friday());
        StrategyMap.put(6, new Saturday());
        StrategyMap.put(7, new Sunday());
    }
    public Map<Integer, TravelPlanStrategy> getStrategyMap() {
        return StrategyMap;
    }
}

解释

旅游计划接口(TravelPlanStrategy):定义统一的 plan 方法。调用方不需要判断拿到的是 Monday 还是 Sunday,只要按照接口调用 plan 即可。

具体策略类(Monday 至 Sunday):每个类封装一天的旅游计划。修改星期三的目的地时,只需要调整 Wednesday,不会影响其他日期。

策略注册类(StrategyMap):负责保存星期编号与策略对象之间的对应关系。编号 1 对应 Monday,编号 7 对应 Sunday。调用方按编号查找策略,省去了七个条件分支。

这套实现的重点在于“选择”和“执行”分开。StrategyMap 负责找到应该使用的策略,具体策略负责执行旅游计划。后续如果增加新的计划,只要实现 TravelPlanStrategy,并把它注册到 Map 中,原有策略类不需要跟着修改。

测试

System.out.println("------------------------------策略模式-----------------------------");

StrategyMap strategyMap = new StrategyMap();
strategyMap.getStrategyMap().get(1).plan();
strategyMap.getStrategyMap().get(2).plan();
strategyMap.getStrategyMap().get(3).plan();
strategyMap.getStrategyMap().get(4).plan();
strategyMap.getStrategyMap().get(5).plan();
strategyMap.getStrategyMap().get(6).plan();
strategyMap.getStrategyMap().get(7).plan();

输出结果

------------------------------策略模式-----------------------------
星期一:去北京
星期二:去上海
星期三:去重庆
星期四:去成都
星期五:去广州
星期六:去深圳
星期天:回家

测试代码依次取出编号 1 到 7 对应的策略。虽然每次调用写的都是 plan 方法,实际执行的却是不同实现。调用方不需要在执行计划时再次判断星期,也不用直接创建七个具体策略对象。

策略模式和状态模式的区别

策略模式由调用方选择具体实现。例如用户在下单时主动选择微信支付或银行卡支付,系统根据选择执行对应策略。策略之间通常没有固定的先后关系,也不会在执行结束后自动切换到下一个策略。

状态模式则由对象当前状态决定行为。上一篇订单示例中的 NewOrder 会把订单切换到 ProcessingOrder,ProcessingOrder 又会切换到 CompleteOrder。状态之间存在流转关系,调用方只需要继续操作同一个订单。

简单来说,策略模式更像“这次准备用哪种办法”状态模式更像“对象现在到了哪个阶段”。两种模式的类结构相似,但选择权和变化原因不同。

策略模式的优缺点

策略模式把不同算法拆到独立类中,修改某个策略时不容易影响其他策略。调用方依赖的是统一接口,增加策略也不必扩展原来的条件判断。

策略较多时,类的数量也会增加。调用方或策略注册类还得知道如何找到合适的策略,如果选择规则本身很复杂,这部分逻辑仍然需要单独维护。对于只有两种固定处理方式、以后也很少变化的业务,简单判断可能更容易读懂。

在 Spring 项目中,策略实现通常会交给容器管理,再按照名称、业务类型或枚举值组装成 Map。使用时直接根据业务参数取出对应 Bean,这和本文 StrategyMap 的思路差不多,只是对象的创建工作由 Spring 完成。

结语

策略模式把一组可以替换的处理方式封装成独立策略,并通过统一接口调用。本文的旅游计划示例中,TravelPlanStrategy 负责约定行为,七个日期类保存各自计划,StrategyMap 负责建立星期编号与策略之间的关系。

当业务里出现越来越长的条件分支,而且每个分支代表一套独立算法时,可以考虑使用策略模式。它不能让选择逻辑凭空消失,但能把选择策略和执行策略分开,后续增加或修改某种处理方式时,代码会更容易找到,也更容易测试。

0

回到顶部