• 策略模式的应用——应对频繁的需求变更


    秋招结束后,间接性堕落了一段时间,学习几乎停止下来了。内心甚是焦灼,感觉生活很无趣!为了在参加工作后能够快速上手和成为一名优秀的中级开发者,从这篇文章开始将不断学习优秀的编码经验,学习是永无止境的。需要静下来,慢慢来!下面进入新篇章,技术提升篇。

    应用情景

    在工作中,往往我们的需求是多变的,那么如果我们只是简单的硬编码解决暂时的需求,那么当需求进行变更的时候我们的接口就需要变更来应对新的需求。但是有些情况下我们可以写一些通用接口来因对这种变化,即把频繁变化的东西提取出来,交给接口调用者来设计。在这种情况下,我们的接口就无需变化,而当需求变化了只需要重写频繁变化的对象即可。这就是策略模型的一种应用,接下来将用具体的例子来说明这种编码方式的具体实现。

    此时,我们有个实体集合,需要通过对实体类的属性进行过滤选择出符合我们条件的一些实体。例如,第一周产品经理要求对属性一进行过滤,第二周需求变更又要把属性二加进去过滤,第三周又要把属性三考虑进去。。。。。于是,为了应对这种变化,我们的接口应该使用策略模式。如下:

    具体实现

    假设我们的实体简单一些,使用经典Student作为实体,代码如下:

    package strategy.demo.entity;
    
    public class Student {
        private String name;
        private int age;
        private int score;
        private String address;
        public Student(String name, int age, int score, String address){
            this.name = name;
            this.age = age;
            this.score = score;
            this.address = address;
        }
        public String getName() {
            return name;
        }
        public int getAge() {
            return age;
        }
        public int getScore() {
            return score;
        }
        public String getAddress() {
            return address;
        }
        public String toString(){
            return "Student [name=" + name + ", age=" + age + ", score=" + score + ", address=" + address + "]";
        }
    }
    
    • 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

    首先,我们看下如果是硬编码的话,两个不同的需求应该会写下如下代码:

    /**
         * 需求1:根据分数过滤学生
         * @param students
         * @param age
         * @return
         */
        public List<Student> filterStudentByAge(List<Student> students, int age){
            ArrayList<Student> result = new ArrayList<>();
            for (Student student : students) {
                if (student.getAge() > age){
                    result.add(student);
                }
            }
            return result;
        }
    
        /**
         * 需求2:根据分数和年龄过滤学生
         * @param students
         * @param age
         * @param score
         * @return
         */
        public List<Student> filterStudentByScore(List<Student> students,int age , int score){
            ArrayList<Student> result = new ArrayList<>();
            for (Student student : students) {
                if (student.getScore() > score && student.getAge() > age){
                    result.add(student);
                }
            }
            return result;
        }
    
    • 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

    上面的代码,需求2的代码是对需求1的升级维护,如果后面继续改动这个接口呢?所以,我们需要把频繁变动的东西提取出来作为一个策略对象,这里频繁变动的东西就是对实体Student属性的判断,因此我们抽取出一个接口:

    package strategy.demo.service;
    import strategy.demo.entity.Student;
    public interface filterStrategy {
    	/**
         * 根据stduent的不同属性进行谓词过滤
         * @param student
         * @return
         */
        public boolean filterStudent(Student student);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    这个接口只有一个待实现的方法,接受一个Student对象,返回是否符合过滤条件。而这个接口是由调用者实现的,其实这个设计思路在JDK中非常常见。我们需要对一个对象集合 sort 排序,需要按照自己的排序策略重写Comparator接口,还记得嘛?如下:

    List<Student> allStudents = StudentHelps.getAllStudents();
    allStudents.sort(new Comparator<Student>() {
        @Override
        public int compare(Student o1, Student o2) {
            return o1.getAge()-o2.getAge;
        }
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    因此,使用策略模式,我们只需要写下一个过滤方法即可,其他的过滤策略由调用者去实现,如下:

    public List<Student> filterStudentByStrategy(List<Student> students, filterStrategy strategy){
            ArrayList<Student> result = new ArrayList<>();
            for (Student student : students) {
                if (strategy.filterStudent(student)){
                    result.add(student);
                }
            }
            return result;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    调用者实现时如下:

    List<Student> allStudents = StudentHelps.getAllStudents();
    List<Student> students3 = filterStudentByStrategy(allStudents, new filterStrategy() {
        @Override
        public boolean filterStudent(Student student) {
            return student.getScore() > 80 && student.getAge() > 20;
        }
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    如此一来,无论对student的过滤需求如何变化,始终可以通过 filterStrategy 接口实现,因此就更大程度上遵守高内聚低耦合的开发原则。这就是设计模式的美妙之处。

    扩展

    其实,在很多情况下,接口开发者会提供一个基本功能的 filterStrategy 接口实现类,里面有常用的过滤策略,调用者只需要用即可不需要每次都自己定义。这在JDK源码和SpringBoot中有太多的例子了,简单常用的由接口方提供,复杂不常用的我允许调用者自定义!

    另外一个扩展就是,调用者在这里写匿名类可以使用Lambda语法,具体参考我的博客 Lambda表达式常见用法Lambda语法也是提供工作效率的神器,有时间推荐学习!因此,调用者使用Lambda可以这样编写代码:

    List<Student> students4 = filterStudentByStrategy(allStudents, student -> student.getScore() > 80 && student.getAge() > 20);
    
    • 1

    也就相当于把判断条件参数化了,这种做法叫做自定义函数接口(看起来是函数作为一个参数传递),看不懂的话去看下我的博客就会了,以上就是本期内容!共勉加油!

  • 相关阅读:
    [激光器原理与应用-5]:激光二极管LD (Laser Diode)与激光二极管驱动器(LD驱动器)
    消息队列 随笔 6-spring-kafka
    中秋味的可视化大屏 【以python pyecharts为工具】
    PHP获取Opcode及C源码
    BGP高级特性——BGP路由控制
    Array_JavaScript
    Go 处理粘包拆包
    CuteOneP 一款php的OneDrive多网盘挂载程序 带会员 同步等功能
    从搭建SpringMVC环境到实现增删改查&&文件上传-文件下载
    白皮书 |得帆云低代码aPaaS X OA全新解决方案,解锁数字化协作新境界
  • 原文地址:https://blog.csdn.net/cj151525/article/details/134420712