把书读薄 | 《设计模式之美》设计模式与范式(行为型-策略模式)

这是我参与8月更文挑战的第2天,活动详情查看: 8月更文挑战

0x0、引言

? 被虐完继续肝!!!本文对应设计模式与范式:行为型(60-61),策略模式 (Strategy Pattern),常被用于避免冗长的if-else或switch分支判断,而它的作用不止如此,还可用于 解耦策略的定义、创建和使用

Tips:二手知识加工难免有所纰漏,感兴趣有时间的可自行查阅原文,谢谢。


0x1、定义

原始定义

定义一系列算法,封装每个算法,并使它们可以互相替换。策略模式使得算法的变化独立于使用它的客户端。

这里的算法和上节的模板模式说的”算法”一样,不特指数据结构和算法中的算法,可理解为广义上的 业务逻辑


0x2、写个简单例子

Talk is cheap, show you the code,一个简易计算器的例子,没使用策略模式前:

public class Calculator {
    public static void main(String[] args) {
        System.out.println("计算:4 + 2 = " + compute("+", 4, 2));
        System.out.println("计算:4 - 2 = " + compute("-", 4, 2));
        System.out.println("计算:4 * 2 = " + compute("*", 4, 2));
        System.out.println("计算:4 / 2 = " + compute("/", 4, 2));
    }

    public static float compute(String operator, int first, int second) {
        float result = 0.0f;
        if(operator.equals("+")) {
            result = first + second;
        } else if(operator.equals("-")){
            result = first - second;
        } else if(operator.equals("*")){
            result = first * second;
        } else if(operator.equals("/")){
            result = first / second;
        }
        return result;
    }
}
复制代码

写出上述代码IDE还是提示可以使用switch替换,不过是换汤不换药。对了,还可以把计算逻辑从compute()函数中抽离出来,独立成四个计算函数,以避免单个函数过长的问题。

接着使用策略模式重构下这个计算器,三步走,先是 策略的定义

// 计算策略接口
public interface ICompute {
    String compute(int first, int second);
}

// 具体策略
public class AddCompute implements ICompute {
    @Override public String compute(int first, int second) {
        return "计算:" + first + " + " + second + " = " + (first + second);
    }
}

public class SubCompute implements ICompute {
    @Override public String compute(int first, int second) {
        return "计算:" + first + " - " + second + " = " + (first - second);
    }
}


public class MulCompute implements ICompute {
    @Override public String compute(int first, int second) {
        return "计算:" + first + " * " + second + " = " + (first * second);
    }
}

public class DivCompute implements ICompute {
    @Override public String compute(int first, int second) {
        return "计算:" + first + " / " + second + " = " + (first / second);
    }
}
复制代码

然后是 策略的创建和使用

public class Context {
    private ICompute compute;
    public Context() { this.compute = new AddCompute(); }
    public void setCompute(ICompute compute) { this.compute = compute; }
    
    // 使用策略
    public void calc(int first, int second) { 
        System.out.println(compute.compute(first, second));
    }
}

// 测试用例
public class TestCompute {
    public static void main(String[] args) {
        Context context = new Context();

        context.setCompute(new AddCompute());
        context.calc(4, 2);

        context.setCompute(new SubCompute());
        context.calc(4, 2);

        context.setCompute(new MulCompute());
        context.calc(4, 2);

        context.setCompute(new DivCompute());
        context.calc(4, 2);
    }
}
复制代码

代码运行结果如下:

运行结果和初始代码一致,而且if-else不见了,是吧?但,这其实没有发挥策略模式的优势,而是退化成了:面向对象的多态 或 基于接口而非实现编程,非动态,直接在代码中指定了使用哪种策略。

而实际开发中场景更多的是:事先并不知道会使用那种策略,而是在程序运行期间,根据配置、用户输入、计算记过等不确定因素,动态地决定使用那种策略。对于上述这种 无状态的(不包含成员变量,只是纯粹的算法实现),策略对象可以共享的场景,可以把Context改写为工厂类的实现方式:

public class Context {
    private static final Map<String, ICompute> computes = new HashMap<>();

    static {
        computes.put("+", new AddCompute());
        computes.put("-", new SubCompute());
        computes.put("*", new MulCompute());
        computes.put("/", new DivCompute());
    }

    public void calc(String operator, int first, int second) {
        System.out.println(computes.get(operator).compute(first, second));
    }
}

// 修改后的测试用例
public class TestCompute {
    public static void main(String[] args) {
        Context context = new Context();
        context.calc("+", 4, 2);
        context.calc("-", 4, 2);
        context.calc("*", 4, 2);
        context.calc("/", 4, 2);
    }
}
复制代码

重构后的代码除了复用策略对象外,依旧没有if-else语句,分支判断真的被我们去掉了吗?实际上,是被转移了,借助 "查表法",把判断逻辑转移到HashMap.get()里了。还有一种类似的方式,通过遍历列表返回适合的策略,同样是转移~

对于有状态的、策略对象不可共享(每次都要new)的场景,在Java中可以利用 反射 + 注解 的技术手段来隐藏if-else,限于篇幅,不展开讲,很多开源库都有用到。

最后,同样带出UML类图、组成角色及优缺点的讲解~

  • Strategy (抽象策略类) → 定义策略的共有方法,一般是接口;
  • ConcreteStrategy (具体策略类) → 实现抽象策略中类定义的共有方法;
  • Context (上下文信息类) → 存放和执行需要使用的具体策略类、客户端调用的逻辑;

使用场景

  • 系统需要动态地切换几种算法;
  • 多重条件选择语句,想对分支判断进行隐藏,可用策略模式把行为转移到具体策略类中;
  • 只希望客户端直接使用已封装好算法,而不用关心算法的具体实现细节;

优点

  • 定义一系列算法实现,让算法可互相替换,提高代码扩展性和灵活性;
  • 降低多重条件嵌套语句的理解难度(转移到具体策略类中);

缺点

  • 调用者可自行选择使用哪种策略,但需了解每种策略的不同,且策略发生更改都需要知道;
  • 策略比较庞大时,具体策略类也会剧增,增加维护成本;
  • 小型的策略,不如函数式编程简洁 (匿名函数实现不同版本算法);

以上就是本节的全部内容,谢谢~

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享