This page looks best with JavaScript enabled

时势造英雄-策略模式

 ·  ☕ 3 min read

策略模式

针对同一类情况, 如果因为某个或多个因素和有不同的处理方法, 一般这种就会造成写很多的if else, 甚至嵌套. 策略模式就是针对这种情况的一种让代码遵循开闭原则的方案.

适用场景

  • 针对同一类型问题的多种处理方式, 仅仅是具体行为有差别时
  • 需要安全的封装同一类型的操作时
  • 出现同一个抽象有多个子类, 而又要使用 if else 来判断具体实现时

类图

@startuml

class Context {
+algorithm()
+setStrategy(Strategy)
}

interface Strategy {
+algorithm()
}

class StrategyA {
+algorithm()
}

class StrategyB {
+algorithm()
}

Context o- Strategy

Strategy <|-- StrategyA
Strategy <|-- StrategyB

@enduml
  • Context: 持有策略的上下文
  • Strategy: 策略的抽象
  • StrategyA, StrategyB 具体的策略实现

优点

通过将同一类型问题的处理方式抽象, 然后由具体类实现行为. 在需要使用不同的方式处理时, 将具体实现类注入到持有策略的上下文中. 避免将不同处理方式通过 if else 实现在一个类中.

例子

计算在民用电和工业用电下, 使用相同电, 需要缴纳的电费. 因为民用电和工业电的计算方法不一样, 所以会有分支出现.

常规实现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class ElectricCalculator {

    enum Type {
        V220, // 民用电一般为 220V
        V380  // 商用电压一般为三厢380V
    }

    public double calculate(float deg, Type type) {
        if (type == Type.V220) {
            return calculateV220(deg);
        }
        if (type == Type.V380) {
            return calculateV380(deg);
        }
        return 0;
    }

    private double calculateV220(float deg) {
        return deg * 0.5;
    }

    private double calculateV380(float deg) {
        return deg * 0.86;
    }

    public static void main(String[] args) {
        ElectricCalculator calculator = new ElectricCalculator();
        double feeV220 = calculator.calculate(100, Type.V220); // 计算民用电
        double feeV380 = calculator.calculate(100, Type.V380); // 计算工业用电
    }
}

现在这个类里的逻辑还比较简单, 看起来没什么问题. 如果加上电费的梯度收费、分季节收费, 再加上支持租房用电费计算, 那这个类的逻辑就有点乱了.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public class ElectricCalculator {

    enum Type {
        V220, // 民用电一般为 220V
        V380, // 商用电压一般为三厢380V
        RENT  // 租房用电一般不同于民用电收费
    }

    /**
     * @param deg      本月当前用电
     * @param type     用电类型
     * @param isSummer 是否夏季
     */
    public double calculate(float deg, Type type, boolean isSummer) {
        if (type == Type.V220) {
            return calculateV220(deg, isSummer);
        }
        if (type == Type.V380) {
            return calculateV380(deg, isSummer);
        }
        if (type == Type.RENT) {
            return calculateRent(deg, isSummer);
        }
        return 0;
    }

    // 模拟梯度收费和分夏季收费
    private double calculateV220(float deg, boolean isSummer) {
        float rate = 0.5f;
        if (deg > 500) {
            rate = 0.6f;
        }
        if (deg > 800) {
            rate = 0.7f;
        }
        if (isSummer) {
            rate += 0.1f;
        }
        return deg * rate;
    }

    private double calculateV380(float deg, boolean isSummer) {
        return deg * 0.86;
    }

    private double calculateRent(float deg, boolean isSummer) {
        return deg * 1;
    }
}

现在支持了梯度收费和分季节收费, 而且每个类型的电费计算方式又不一样, 这个类里就会包含很多的分支逻辑. 且当需要修改或添加一种电费类型时, 很容易造成错误.
这种情况下, 就适合使用策略模式, 将电费的计算方法抽象化, ElectricCalculator 中的计算方法的具体实现由外部注入.

重构

@startuml

class ElectricCalculator {
+calculate(int deg)
+setCalculator(Calculator)
}

interface Calculator {
+calculate(int deg)
}

class V220Calculator {
+calculate(int deg)
}

class V220SummerCalculator {
+calculate(int deg)
}

ElectricCalculator o- Calculator

Calculator <|-- V220Calculator
Calculator <|-- V220SummerCalculator

@enduml

策略抽象

1
2
3
interface Calculator {
    double calculate(float deg);
}

实现不同策略

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class V220Calculator implements Calculator {

    @Override
    public double calculate(float deg) {
        float rate = 0.5f;
        if (deg > 600) {
            rate = 0.6f;
        }
        if (deg > 900) {
            rate = 0.7f;
        }
        return deg * rate;
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// 针对夏季的
class V220SummerCalculator implements Calculator {

    @Override
    public double calculate(float deg) {
        float rate = 0.5f;
        if (deg > 500) {
            rate = 0.6f;
        }
        if (deg > 800) {
            rate = 0.7f;
        }
        return deg * rate;
    }
}

注入策略

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class ElectricCalculator {
    private Calculator mCalculator;

    public void setCalculator(Calculator calculator) {
        mCalculator = calculator;
    }

    public double calculate(float deg) {
        if (mCalculator == null) {
            throw new IllegalStateException("Calculator must not null");
        }
        return mCalculator.calculate(deg);
    }

    public static void main(String[] args){
        ElectricCalculator calculator = new ElectricCalculator();
        calculator.setCalculator(new V220Calculator());
        double fee = calculator.calculate(100);
        calculator.setCalculator(new V220SummerCalculator());
        double feeSummer = calculator.calculate(10);
    }
}

重构之后

可以看出, ElectricCalculator 类中的逻辑变简单了, 而且每个不同的分支由不同的策略实现类代替, 保证了增加新策略的扩展性和修改代码的封闭性(不用修改ElectricCalculator中代码).

Support the author with
alipay QR Code
wechat QR Code

Yang
WRITTEN BY
Yang
Developer