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

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

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 (上下文信息类) → 存放和执行需要使用的具体策略类、客户端调用的逻辑;


使用场景


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


优点


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


缺点


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


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


相关文章
|
1月前
|
设计模式 算法 Kotlin
Kotlin - 改良设计模式 - 策略模式
Kotlin - 改良设计模式 - 策略模式
48 4
|
26天前
|
设计模式 算法 Kotlin
Kotlin教程笔记(53) - 改良设计模式 - 策略模式
Kotlin教程笔记(53) - 改良设计模式 - 策略模式
41 1
|
29天前
|
设计模式 前端开发 JavaScript
JavaScript设计模式及其在实战中的应用,涵盖单例、工厂、观察者、装饰器和策略模式
本文深入探讨了JavaScript设计模式及其在实战中的应用,涵盖单例、工厂、观察者、装饰器和策略模式,结合电商网站案例,展示了设计模式如何提升代码的可维护性、扩展性和可读性,强调了其在前端开发中的重要性。
29 2
|
1月前
|
设计模式 算法 Kotlin
Kotlin教程笔记(53) - 改良设计模式 - 策略模式
Kotlin教程笔记(53) - 改良设计模式 - 策略模式
46 2
|
2月前
|
设计模式 算法 Kotlin
Kotlin教程笔记(53) - 改良设计模式 - 策略模式
本教程详细讲解Kotlin语法,适合深入学习。快速入门可参考“简洁”系列教程。本文通过游泳运动员的案例,介绍策略模式及其在Kotlin中的改良应用,利用高阶函数简化代码结构,提高灵活性。
38 3
|
2月前
|
设计模式 算法 Kotlin
Kotlin教程笔记(53) - 改良设计模式 - 策略模式
本教程详细讲解Kotlin语法,适合深入学习。快速入门可参考“简洁”系列教程。本文介绍策略模式在Kotlin中的应用,通过游泳运动员的例子,展示如何使用接口和高阶函数实现策略模式,使代码更简洁、灵活。
34 2
|
2月前
|
设计模式 算法 Kotlin
Kotlin教程笔记(53) - 改良设计模式 - 策略模式
Kotlin教程笔记(53) - 改良设计模式 - 策略模式
67 3
|
2月前
|
设计模式 算法 Kotlin
Kotlin - 改良设计模式 - 策略模式
Kotlin - 改良设计模式 - 策略模式
|
2月前
|
设计模式 算法 PHP
PHP中的设计模式:策略模式的深入解析与实践
【10月更文挑战第12天】 在软件开发的世界中,设计模式是解决常见问题的最佳实践。它们不是具体的代码,而是一种编码和设计经验的总结。在PHP开发中,合理运用设计模式可以极大地提高代码的可维护性、扩展性和复用性。本文将深入探讨策略模式(Strategy Pattern)的原理、实现方式及其在PHP中的应用。通过具体示例,我们将展示如何利用策略模式来解耦算法与对象,从而让代码更加灵活和易于管理。
24 0
|
2月前
|
设计模式 存储 算法
PHP中的设计模式:策略模式的深入解析与实践
【10月更文挑战第9天】 在PHP开发领域,设计模式是提升代码可维护性、扩展性和重用性的关键技术之一。本文聚焦于策略模式这一行为型设计模式,通过理论阐述与实例分析,揭示其在PHP应用程序中优化算法切换和业务逻辑解耦方面的强大效用。不同于常规摘要,本文不直接概述研究方法或结果,而是基于实际开发场景,探讨策略模式的应用价值和实现方式,旨在为PHP开发者提供一种高效应对复杂业务需求变化和技术债务累积问题的策略思维。