品设计模式 - (行为型) 策略模式 Strategy

221 阅读16分钟
这里写图片替代文字

简介

定义

是行为型设计模式之一,通过定义一系列算法类,允许在运行时动态选择算法,从而实现更加灵活的代码结构。

功能

该模式的中心不是如何实现算法,而是如何组织、调用这些算法,从而让程序结构更加灵活、具有更好的维护性和扩展性。

本质

分离算法,选择实现;通过将算法封装为独立类,策略模式实现了 ⌈对修改关闭,对扩展开放⌋ 的原则

解决的问题

  • 当程序中存在大量 if-elseswitch-case 判断语句时,代码易于变得难以维护。
  • 当算法的实现经常变更或需要扩展时,直接修改代码可能会破坏现有功能。

Strategy 模式中登场角色

画板

+ Strategy (策略):用来约束一系列具体的策略算法。Context 使用这个接口来调用具体的策略实现定义的算法。如果多个算法具有公共功能的话,可以把 Strategy 实现成为抽象类,然后把多个算法的公共功能实现到 Strategy 内 + **ConcreteStrategy (具体的策略):**具体的策略实现,负责实现 Strategy 角色的接口 (API), 即负责实现具体的策略 (战略、方向、方法和算法) + Context(上下文):上下文,负责和具体的策略类交互,通常上下文会持有一个真正的策略实现,还可以让具体的策略类来获取上下文的数据,甚至让具体的策略类来回调上下文的方法

为什么要特意编写 Strategy 角色

例如,当我们想要通过改善算法来提高算法的处理速度时,如果使用了 Strategy 模式,就不必修改 Strategy 角色的接口,仅仅修改 ConcreteStrategy 角色即可。

使用委托这种弱关联关系就可以很方便地整体替换算法

入门案例 01

不同犬吠

一只狗,可以动态地改变叫声。

策略接口

public interface BarkBehavior {

    void bark();
}

具体策略

public class WawaBarkBehavior implements BarkBehavior{
    @Override
    public void bark() {
        System.out.println("小狗汪汪叫!!!");
    }
}
public class AoaoBarkBehavior implements BarkBehavior{
    @Override
    public void bark() {
        System.out.println("小狗嗷嗷叫!!!!");
    }
}

Context 类

public class Dog {

    private BarkBehavior barkBehavior;


    public void barking() {
        if(barkBehavior != null) {
            barkBehavior.bark();
        }
    }

    public Dog() {
    }

    public void setBarkBehavior(BarkBehavior barkBehavior) {
        this.barkBehavior = barkBehavior;
    }
}

Client

public class Client {

    public static void main(String[] args) {

        Dog dog = new Dog();
        dog.setBarkBehavior(new WawaBarkBehavior());

        dog.barking();

        dog.setBarkBehavior(new AoaoBarkBehavior());
        dog.barking();

        /*
         输出:
            小狗汪汪叫!!!
            小狗嗷嗷叫!!!!
        */
    }
}

入门案例 2 - 促销活动

百货公司的不同促销活动,针对每个节日都使用不同的促销活动;设需要考虑三个不同的节日:春节、中秋节、圣诞节

抽象策略

public interface DiscountStrategy {

    void showDiscount();
}

具体策略

public class CnNewYearDiscountStrategy implements DiscountStrategy {
    @Override
    public void showDiscount() {
        System.out.println("春节折扣!!");
    }
}

public class ChristmasDiscount implements DiscountStrategy{
    @Override
    public void showDiscount() {
        System.out.println("中秋节折扣!!");
    }
}

public class MidAutumnDiscount implements DiscountStrategy{
    @Override
    public void showDiscount() {
        System.out.println("中秋节折扣!!!!");
    }
}

定义环境角色 (Context): 用于连接上下文,即把促销活动推销给客户

public class SaleMan {

    private DiscountStrategy strategy;

    public SaleMan() {
    }

    public void displaySaleManStrategy() {
        strategy.showDiscount();
    }

    public void setStrategy(DiscountStrategy strategy) {
        this.strategy = strategy;
    }
}

客户端调用 - 让销售员进行促销活动

public class Client {

    public static void main(String[] args) {

        SaleMan context = new SaleMan();

        System.out.print("春节:");
        context.setStrategy(new CnNewYearDiscountStrategy());
        context.displaySaleManStrategy();

        System.out.print("中秋节:");
        context.setStrategy(new MidAutumnDiscount());
        context.displaySaleManStrategy();

        System.out.print("圣诞节:");
        context.setStrategy(new ChristmasDiscount());
        context.displaySaleManStrategy();
        
        /*
         输出:
            春节:春节折扣!!
            中秋节:中秋节折扣!!!!
            圣诞节:中秋节折扣!!
        */
    }
}

进阶案例 01 - 猜拳游戏

题目来源 - 《图解设计模式》 —— 策略模式

两种策略:如果这局猜拳获胜,那么下一局也会出现一样的手势 (WinningStrategy);第二种策略 (ProbStrategy) 根据上一局的手势从概率上计算出下一局的手势

设计类图

画板

类和接口一览

名称说明
Hand表示猜拳游戏中的 "手势" 的类
Strategy猜拳游戏中的策略的类
WinningStrategy"如果这局获胜,下次继续出同样手势" 的策略
ProStrategy"根据上一局的手势从概率上计算出下一局的手势从之前猜拳结果计算下一局出各种手势的概率" 的策略
Player进行猜拳游戏的选手的类
Main测试程序的类

Hand 类

  • int 表示所出的手势:0 表示石头、1 表示剪刀、2 表示布;将值保存在 handValue 字段中
  • 创建三个 Hand 实例,使用单例模式; Hand 类持有属性 int 表示各个手势;使用数组存储
  • getHand() 方法用来获取 Hand 类的实例。传入 int idx 下标返回对应的手势实例
  • isStrongerThan(hand2): this 实例战胜了 hand2 则返回 true
  • isWeakerThan(hand3): this 实例输给了 hand2 则返回 false
  • fight(hand2): 比较两个手势的输赢
    • this 获胜的条件:(this.handValue + 1) % 3 == hand2.handValue
    • 打平条件: this.handValue == hand2.handValue
    • 否则 this 战败
public class Hand {

    public static final int HANDVALUE_GUU = 0;  // 表示石头
    public static final int HANDVALUE_CHO = 1;  // 表示剪刀
    public static final int HANDVALUE_PAA = 2;  // 表示布

    public static final String[] names = {
            "石头", "剪刀", "布"
    };

    public static final Hand[] hand = {
            new Hand(HANDVALUE_GUU),    // 0 对应石头
            new Hand(HANDVALUE_CHO),    // 1 对应剪刀
            new Hand(HANDVALUE_PAA),    // 2 对应布
    };

    private int handValue;

    public Hand(int handValue) {
        this.handValue = handValue;
    }

    // 根据手势的值获取其对应的实例
    public static Hand getHand(int handValue) {
        return hand[handValue];
    }

    public boolean isStrongerThan(Hand thatHand) {
        return (this.handValue + 1) % 3 == thatHand.handValue;
    }

    // 表示两个手势的比较结果
    private int fight(Hand thatHand) {
        if (this == thatHand) {
            return 0;   // 单例模式,直接比较地址;相等则打平
        } else if (isStrongerThan(thatHand)) {
            return 1;   // 获胜
        } else {
            return -1;  // 战败
        }
    }

    @Override
    public String toString() {
        return String.valueOf(this.handValue);
    }
}

Strategy 接口

定义猜拳策略的抽象接口;

  • nextHand() 方法的作用是 "获取下一局要出的手势",实现了 Strategy 接口
  • study() 方法作用是学习 "上一局的手势是否获胜";如果上一局中调用 nextHand() 后获胜了,则调用 study(true); 否则调用 study(false)。这样 Strategy 接口的实现类就会改变自己的内部状态。
public interface Strategy {

    Hand nextHand();
    void study(boolean win);
}

WinningStrategy 类

如果上一局手势获胜了,则继续出同样的手势。如果上一局的手势输了,则下一局就需要随机出手势。

public class WinningStrategy implements Strategy {

    private boolean preIsWin = false;
    private Hand preHand;
    private Random random;

    public WinningStrategy() {
        this.random = new Random();
    }

    @Override
    public Hand nextHand() {
        if (!preIsWin) {    // 上一轮未获胜,更新随机手势
            this.preHand = Hand.getHand(random.nextInt(3));
            return preHand;
        }
        return preHand;
    }
    
    @Override
    public void study(boolean win) {
        this.preIsWin = win;
    }
}

ProStrategy 类

随机出手势,每种手势出现的概率会根据以前的猜拳结果改变。

history 字段是一个映射表,被用于根据过去的胜负来进行计算。二维数组,每个数组下标的意思如下:

history[上一局出的手势][这一局所出现的手势]

例如:

history[0][0] 两局分别出石头,获胜的次数

history[0][1] 两局分别出石头和剪刀,获胜的次数

history[0][2] 两局分别出石头和布,获胜的次数

举例,next()规则应用:

  • history[0][0] = 3;
  • history[0][1] = 5;
  • history[0][2] = 7;

则记 ⌈石头、剪刀 和 布⌋ 的比例为 ⌈3 : 5 : 7⌋ 来决定。然后在 0 ~ 15 之间取一个随机数。

  • 随机数在 [0 ~ 3] 之间,那么出 ⌈石头⌋
  • 随机数在 [3 ~ 8] 之间,那么出 ⌈剪刀⌋
  • 随机数在 [8 ~ 15] 之间,那么出 ⌈布⌋

study() 方法: 根据 nextHand() 方法返回的手势的胜负结果来更新 history 字段中的值。

public class ProbStrategy implements Strategy {

    private Random random;
    private int preHandValue = 0;
    private int currentHandValue;

    private int[][] history = {
            {1, 1, 1},
            {1, 1, 1},
            {1, 1, 1}
    };

    public ProbStrategy(int seed) {
        this.random = new Random(seed);
    }

    private int getSum(int hv) {
        int sum = 0;
        for (int i = 0; i < 3; i++) {
            sum += history[hv][i];
        }
        return sum;
    }

    @Override
    public Hand nextHand() {
        int cnt = random.nextInt(getSum(currentHandValue));
        int handValue = 0;
        if (cnt < history[currentHandValue][0]) {   // 小于出拳头比例
            handValue = 0;
        } else if (cnt < history[currentHandValue][0] + history[currentHandValue][1]) { // 小于出剪刀比例
            handValue = 1;
        } else {    // 小于出布的比例
            handValue = 2;
        }
        preHandValue = currentHandValue;    //记录上一个手势
        currentHandValue = handValue;   //更新当前手势
        return Hand.getHand(handValue);
    }

    @Override
    public void study(boolean win) {
        if (win) {
            // 当前获胜,上一局出 preHandValue 本局出 currentHandValue 的获胜场次 + 1
            history[preHandValue][currentHandValue]++;
        } else {
            // 否则,上一局出其余两个手势不战败的获胜场次 + 1
            history[preHandValue][(currentHandValue + 1) % 3]++;
            history[preHandValue][(currentHandValue + 2) % 3]++;
        }
    }
}

Player 类

生成 Player 类的实例的时候,需要向其传递 "姓名" 和 "策略"。

nextHand() 方法是用来获取下一局手势的方法。不过实际上决定下一局手势的是策略

public class Player {

    private String name;
    private Strategy strategy;
    private int winCount;
    private int loseCount;
    private int gameCount;

    public Player(String name, Strategy strategy) {
        this.name = name;
        this.strategy = strategy;
    }

    //下一次出拳
    public Hand nextHand() {
        return this.strategy.nextHand();
    }

    //获胜
    public void win() {
        strategy.study(true);
        winCount++;
        gameCount++;
    }

    //战败
    public void lose() {
        strategy.study(false);
        loseCount++;
        gameCount++;
    }

    //打平的情况
    public void even() {
        gameCount++;
    }

    @Override
    public String toString() {
        return "Player - [" +
                "name='" + name + '\'' +
                ", strategy=" + strategy +
                ", winCount=" + winCount +
                ", loseCount=" + loseCount +
                ", gameCount=" + gameCount +
                ']';
    }
}

Main 类

使用两位选手进行十次比赛

package com.jools.designpattern.strategy.hand;

/**
 * @author Jools He
 * @version 1.0
 * @date 2025/2/3 16:11
 * @description: TODO
 */
public class Main {

    public static void main(String[] args) {

        int seed01 = 1;
        int seed02 = 3;

        Player player01 = new Player("AAAA", new WinningStrategy(seed01));
        Player player02 = new Player("BBBB", new ProbStrategy(seed02));

        for (int i = 0; i < 10; i++) {
            Hand nextHand01 = player01.nextHand();
            Hand nextHand02 = player02.nextHand();
            if (nextHand01.isStrongerThan(nextHand02)) {
                System.out.println("Winner is:" + player01.getName());
                player01.win();
                player02.lose();
            } else if (nextHand02.isStrongerThan(nextHand01)) {
                System.out.println("Winner is:" + player02.getName());
                player02.win();
                player01.lose();
            } else {
                System.out.println("Even");
                player01.even();
                player02.even();
            }
        }

        System.out.println("Total:");
        System.out.println(player01);
        System.out.println(player02);

        /*
         输出:
            Winner is:BBBB
            Winner is:AAAA
            Winner is:BBBB
            Winner is:AAAA
            Winner is:BBBB
            Even
            Winner is:BBBB
            Even
            Winner is:BBBB
            Winner is:AAAA
            Total:
            Player - [name='AAAA', strategy=WinningStrategy, winCount=3, loseCount=5, gameCount=10]
            Player - [name='BBBB', strategy=ProbStrategy, winCount=5, loseCount=3, gameCount=10]
         */
    }
}

思考题

请编写添加一个新策略,随机出手势

UML 图设计

画板

代码实现

public class RandomStrategy implements Strategy{

    private Random random;

    public RandomStrategy(int seed) {
        this.random = new Random(seed);
    }

    @Override
    public Hand nextHand() {
        return Hand.getHand(random.nextInt(3));
    }

    @Override
    public void study(boolean win) {
        // Not operate
    }
}

执行结果

public class Main {

    public static void main(String[] args) {

        int seed01 = 1;
        int seed02 = 3;

        Player player01 = new Player("AAAA", new RandomStrategy(seed01));
        Player player02 = new Player("BBBB", new ProbStrategy(seed02));

        for (int i = 0; i < 10; i++) {
            Hand nextHand01 = player01.nextHand();
            Hand nextHand02 = player02.nextHand();
            if (nextHand01.isStrongerThan(nextHand02)) {
                System.out.println("Winner is:" + player01.getName());
                player01.win();
                player02.lose();
            } else if (nextHand02.isStrongerThan(nextHand01)) {
                System.out.println("Winner is:" + player02.getName());
                player02.win();
                player01.lose();
            } else {
                System.out.println("Even");
                player01.even();
                player02.even();
            }
        }

        System.out.println("Total:");
        System.out.println(player01);
        System.out.println(player02);

        /*
         输出:
            Winner is:BBBB
            Winner is:AAAA
            Winner is:BBBB
            Winner is:BBBB
            Winner is:AAAA
            Winner is:AAAA
            Even
            Even
            Even
            Even
            Total:
            Player - [name='AAAA', strategy=RandomStrategy, winCount=3, loseCount=3, gameCount=10]
            Player - [name='BBBB', strategy=ProbStrategy, winCount=3, loseCount=3, gameCount=10]
         */
    }
}

为什么 Hand 类中的 fight()方法可以直接通过 this == thatHand 进行判断而不需要写成 this.handValue == thatHand.handValue 呢?

由于基于常量数组实现的单例模式,每次出拳都返回该数组内的实例。虽然是引用类型但是指向相同的空间

    public static final Hand[] hand = {
            new Hand(HANDVALUE_GUU),    // 0 对应石头
            new Hand(HANDVALUE_CHO),    // 1 对应剪刀
            new Hand(HANDVALUE_PAA),    // 2 对应布
    };

进阶案例 02 - 超时打折

原题来源:卡码网设计模式专题之策略模式 kamacoder.com/problempage…

题目描述

小明家的超市推出了不同的购物优惠策略,你可以根据自己的需求选择不同的优惠方式。其中,有两种主要的优惠策略:

  1. 九折优惠策略:原价的90%。
  2. 满减优惠策略:购物满一定金额时,可以享受相应的减免优惠。

具体的满减规则如下:

  1. 满100元减5元
  2. 满150元减15元
  3. 满200元减25元
  4. 满300元减40元

请你设计一个购物优惠系统,用户输入商品的原价和选择的优惠策略编号,系统输出计算后的价格。

输入输出

UML 设计

画板

抽象折扣策略 DiscountStrategy

// Strategy
interface DiscountStrategy {
    
    public int makeDiscount(int raw);
}

具体折扣策略

// ConcreteStrategy 01
class PercentStrategy implements DiscountStrategy {
    
    @Override
    public int makeDiscount(int raw) {
        return (int) (raw * 0.9);
    }
}

// ConcreteStrategy 02 
class SumDiscountStrategy implements DiscountStrategy {
    
    
    @Override
    public int makeDiscount(int raw) {
        if(raw >= 300) {
            return raw - 40;
        } else if(raw >= 200){
            return raw - 25;
        } else if (raw >= 150) {
            return raw - 15;
        } else if (raw >= 100){
            return raw - 5;
        }
        return raw;
    }
    
}

Context 类 Store

//Context 
class Store {
    
    private static final DiscountStrategy percentStrategy = new PercentStrategy();
    private static final DiscountStrategy sumDiscountStrategy = new SumDiscountStrategy();
    
    private DiscountStrategy strategy;
    
    public Store() {
        
    }
    
    public void setStrategy(int flag) {
        if(flag == 1) {
            this.strategy = percentStrategy;
        } else if (flag == 2) {
            this.strategy = sumDiscountStrategy;
        }
        
    }

    // 可变参数; 当输入参数个数为 2 的时候同步设置策略
    public int makeBill(int... args) {
        int sum = args[0];
        if(args.length == 2) {
            setStrategy(args[1]);
        }
        return this.strategy.makeDiscount(sum);
    }
}

执行结果

扩展案例 01 - 容错恢复机制

系统中所有操作都要有日志记录,且日志需管理界面,通常将日志记录在数据库以便后续管理。但记录日志到数据库可能出错,如暂时连不上,可先记录在文件,合适时再转录到数据库。

此功能设计可采用策略模式,将日志记录到数据库和文件作为两种策略,运行期间按需动态切换。

策略接口 LogStrategy

public interface LogStrategy {


    /**
     * 记录日志
     *
     * @param msg 需要记录的日志信息
     */
    void log(String msg);
}

具体策略 DbLog / FileLog

public class DbLog implements LogStrategy {
    @Override
    public void log(String msg) {
        //制造错误
        if (msg != null && msg.trim().length() > 5) {
            int a = 5 / 0;
        }
        System.out.println("现在把 '" + msg + "' 记录到数据库中");
    }
}
public class FileLog implements LogStrategy{
    @Override
    public void log(String msg) {
        System.out.println("暂时先把 " + msg + " 记录到文件中");
    }
}

策略上下文 LogContext

public class LogContext {

    /**
     * 记录日志的方法,提供给客户端使用
     *
     * @param msg
     */
    public void log(String msg) {
        LogStrategy strategy = new DbLog();
        try {
            strategy.log(msg);
        } catch (Exception e) {
            strategy = new FileLog();
            strategy.log(msg);
        }
    }
}

Client 客户端选择具体实现策略算法

public class Client {

    public static void main(String[] args) {

        LogContext log = new LogContext();
        log.log("记录日志");
        log.log("再次记录日志");

        /*
         输出:
            现在把 '记录日志' 记录到数据库中
            暂时先把 再次记录日志 记录到文件中
        */
    }
}

Context 和 Strategy 的关系

一般来说是上下文使用具体的策略实现类,但是策略实现类也可以从上下文中获取所需要的数据

扩展案例 02 - 工资支付

需求分析

工资支付方式的问题,很多企业工资支付方式灵活多样,比如人名币现金、美元现金;银行转账到工资账户或工资卡;一些创业型企业为留骨干员工,还有工资转股权等方式。总之,工资支付方式众多。

公司发展会不断有新工资支付方式,需方便扩展;支付方式由公司和员工协商,不同员工、同一员工不同时间都可能不同,要方便切换。实现此功能,策略模式是好选择,且不同策略算法所需数据不同。

比如:

  1. 现金支付就不需要银行账号,而银行转账需要账号。

解决方案之一,把上下文当作参数传递给策略对象。

UML 设计

画板

待支付员工的上下文 PayContext

public class PayContext {

    // 被支付工资的人员
    private String userName = null;

    // 应该被支付的工资金额
    private double money = 0.0;

    // 支付工资的方式策略接口
    private PaymentStrategy strategy = null;

    public PayContext(String userName, double money, PaymentStrategy strategy) {
        this.userName = userName;
        this.money = money;
        this.strategy = strategy;
    }

    public String getUserName() {
        return userName;
    }

    public double getMoney() {
        return money;
    }

    public void payNow() {
        this.strategy.pay(this);
    }
}

抽象策略 PaymentStrategy

public interface PaymentStrategy {

    void pay(PayContext payContext);
}

具体策略

public class RMBCash implements PaymentStrategy{
    @Override
    public void pay(PayContext payContext) {
        System.out.println("给" + payContext.getUserName() + " 支付人名币:" + payContext.getMoney());
    }
}


public class DollarCash implements PaymentStrategy {
    @Override
    public void pay(PayContext payContext) {
        System.out.println("给" + payContext.getUserName() + " 美元现金支付: " + payContext.getMoney());
    }
}

Client 调用

public class Client {

    public static void main(String[] args) {

        RMBCash strategyRMB = new RMBCash();
        DollarCash strategyDollar = new DollarCash();

        //准备员工信息上下文
        PayContext context = new PayContext("Jools wakoo", 5000, strategyRMB);
        //支付
        context.payNow();

        // 切换员工
        PayContext context02 = new PayContext("Jooools", 10000, strategyDollar);
        // 给第二位员工
        context02.payNow();
        
        /*
         输出:
            给 Jools wakoo 支付人名币:5000.0
            给 Jooools 美元现金支付: 10000.0
        */
    }
}

扩展上下文实际:基于银行卡账户支付

新增支付到银行卡的策略实现,通过继承扩展支付上下文,添加银行卡账户等新数据。客户端用新上下文和策略实现,不改变已有实现,遵循开-闭原则。

新建额外上下文

public class PayContextExtra extends PayContext {

    //银行账号
    private String account = null;

    /**
     * 构造方法,传入待支付工资的员工的银行卡号和具体的支付策略
     *
     * @param userName 被支付工资的人员
     * @param money    应该被支付的金额
     * @param strategy 支付策略
     * @param account  转入的账户
     */
    public PayContextExtra(String userName, double money, PaymentStrategy strategy, String account) {
        super(userName, money, strategy);
        this.account = account;
    }

    public String getAccount() {
        return account;
    }
}

增加具体策略 - 卡转账

public class CardPayment implements PaymentStrategy {
    @Override
    public void pay(PayContext payContext) {
        // 获取账户
        PayContextExtra contextExtra = (PayContextExtra) payContext;
        System.out.println("不使用现金,给" + contextExtra.getUserName() +
                " 转账" + contextExtra.getMoney() +
                " 到账户:" + contextExtra.getAccount());
    }
}

或者就是实现策略抽象算法再添加自己需要的数据的方式

扩展上下文方式的好处

所有策略实现风格更统一,所需数据统一从上下文获取,使用方法也统一。

在上下文中添加新数据,可作公共数据供其他算法使用。

但缺点是,若数据仅特定算法用则浪费;且每次添加新算法都扩展上下文,易形成复杂的上下文对象层次,未必必要。

策略模式结合模板方法模式

问题: 就是发现这一系列算法的实现上存在公共功能,或者算法的实现步骤一样,只是在某些局部步骤上有所不同。

三种改进方式:

  1. 上下文当中实现公共功能,让具体的策略算法回调(传入 context 实例)
  2. 将策略的接口修改为抽象类,然后在里面实现具体算法的公共功能
  3. 给所有的策略算法定义了一个抽象的父类,让这个父类去实现策略的接口,然后在这个父类里面去实现公共的功能

示例结构

图片来源于网络

优化 - 容错恢复机制

增加一个实现该策略接口的抽象类

public abstract class LogStrategyTemplate implements LogStrategy {

    @Override
    public final void log(String msg) {
        // 第一步: 给消息添加记录日志的时间
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.CHINA);
        msg = df.format(new Date()) + " 内容是:" + msg;
        // 第二步: 真正执行日志记录
        doLog(msg);
    }

    // 真正执行日志记录,让子类去具体实现
    protected abstract void doLog(String msg);
}

修改具体策略,实现抽象类方法

public class FileLog extends LogStrategyTemplate {

    @Override
    protected void doLog(String msg) {
        System.out.println("时间:" + msg + " 记录到文件中去");
    }
}


public class DbLog extends LogStrategyTemplate {

    @Override
    protected void doLog(String msg) {
        //制造错误
        if (msg != null && msg.trim().length() > 5) {
            int a = 5 / 0;
        }
        System.out.println("时间: " + msg + "记录到数据库中");
    }
}

由于算法实现不改变算法的上下文,所以不变

public class LogContext {

    /**
     * 记录日志的方法,提供给客户端使用
     *
     * @param msg
     */
    public void log(String msg) {
        LogStrategy strategy = new DbLog();
        try {
            strategy.log(msg);
        } catch (Exception e) {
            strategy = new FileLog();
            strategy.log(msg);
        }
    }
}

客户端

public class Client {

    public static void main(String[] args) {

        LogContext log = new LogContext();
        log.log("记录日志");
        log.log("再次记录日志");

        /*
         输出:
            时间:2025-02-03 19:33:16 内容是:记录日志 记录到文件中去
            时间:2025-02-03 19:33:16 内容是:再次记录日志 记录到文件中去
        */
    }
}

策略模式优缺点

优点

  1. 算法可自由切换: 策略类实现了相同接口,便于在运行时动态切换算法。
  2. 易于扩展: 新增算法只需添加新的策略类,无需修改已有代码,符合开闭原则。
  3. 消除条件语句: 将复杂的 if-else 或 switch-case 判断逻辑移到策略类中,代码结构更清晰。

缺点

  1. 增加类的数量: 每种算法需要一个策略类,可能导致类数量增多。
  2. 客户端需了解策略: 客户端需要知道所有策略类,并自行决定使用哪一个策略。

参考


  1. 《图解设计模式》— 策略模式
  2. 策略模式 - 简书
  3. 策略模式 —— 简书
  4. 行为型 - 策略模式