• 一次简单易懂的多态重构实践,让你理解条件逻辑


    摘要:复杂的条件逻辑是编程中最难理解的东西之一。

    本文分享自华为云社区《简单易懂的多态重构实践》,作者:JavaEdge 。

    1 动机

    复杂的条件逻辑是编程中最难理解的东西之一,因此我一直在寻求给条件逻辑添加结构。很多时候,我发现可以将条件逻辑拆分到不同的场景(或者叫高阶用例),从而拆解复杂的条件逻辑。这种拆分有时用条件逻辑本身的结构就足以表达,但使用类和多态能把逻辑的拆分表述得更清晰。

    2 常见场景

    2.1 构造一组类型,每个类型处理各自的一种条件逻辑

    例如,我会注意到,图书、音乐、食品的处理方式不同,这是因为它们分属不同类型的商品。

    最明显的征兆就是有好几个函数都有基于类型代码的switch语句。若果真如此,我就可以针对switch语句中的每种分支逻辑创建一个类,用多态来承载各个类型特有的行为,从而去除重复的分支逻辑。

    2.2 有一个基础逻辑,在其上又有一些变体

    基础逻辑可能是最常用的,也可能是最简单的。我可把基础逻辑放进超类,这样我可以首先理解这部分逻辑,暂时不管各种变体,然后我可以把每种变体逻辑单独放进一个子类,其中的代码着重强调与基础逻辑的差异。

    多态是面向对象编程的关键特性之一,但也很容易被滥用。有人争论说所有条件逻辑都应该用多态取代,我不赞同这种观点。我的大部分条件逻辑只用到了基本的条件语句——if/else和switch/case,无需劳师动众地引入多态。但如果发现如前所述的复杂条件逻辑,多态是改善这种情况的有力工具。

    做法

    • 如果现有的类尚不具备多态行为,就用工厂函数创建之,令工厂函数返回恰当的对象实例。
    • 在调用方代码中使用工厂函数获得对象实例。
    • 将带有条件逻辑的函数移到超类中。
    • 如果条件逻辑还未提炼至独立的函数,首先对其使用【提炼函数】
    • 任选一个子类,在其中建立一个函数,使之覆写超类中容纳条件表达式的那个函数。将与该子类相关的条件表达式分支复制到新函数中,并对它进行适当调整。
    • 重复上述过程,处理其他条件分支。

    在超类函数中保留默认情况的逻辑。或者,如果超类应该是抽象的,就把该函数声明为abstract,或在其中直接抛出异常,表明计算责任都在子类中。

    案例

    有群鸟,想知道这些鸟飞得有多快,以及它们的羽毛是啥样:

    复制代码
    package com.javaedge.refactor.condition;
    
    import lombok.Getter;
    
    import java.util.List;
    import java.util.Map;
    import java.util.Objects;
    import java.util.function.Function;
    import java.util.stream.Collectors;
    
    /**
     * @author JavaEdge
     * @date 2022/4/10
     */
    @Getter
    public class Bird {
    
        private Objects plumages;
        private String name;
        private String type;
        private int numberOfCoconuts;
        private boolean isNailed;
        private int voltage;
    
        public Map plumages(List birds) {
            return birds.stream().collect(Collectors.toMap(Bird::getPlumages, Function.identity()));
        }
    
        public Map speeds(List birds) {
            return birds.stream().collect(Collectors.toMap(Bird::getName, airSpeedVelocity(b)));
        }
    
        public String plumage(Bird bird) {
            switch (bird.type) {
                case "EuropeanSwallow":
                    return "average";
                case "AfricanSwallow":
                    return (bird.numberOfCoconuts > 2) ? "tired" : "average";
                case "NorwegianBlueParrot":
                    return (bird.voltage > 100) ? "scorched" : "beautiful";
                default:
                    return "unknown";
            }
        }
    
        public Integer airSpeedVelocity(Bird bird) {
            switch (bird.type) {
                case "EuropeanSwallow":
                    return 35;
                case "AfricanSwallow":
                    return 40 - 2 * bird.numberOfCoconuts;
                case "NorwegianBlueParrot":
                    return (bird.isNailed) ? 0 : 10 + bird.voltage / 10;
                default:
                    return null;
            }
        }
    }
    复制代码

    有两个不同的操作,其行为都随着“鸟的类型”发生变化,因此可以创建出对应的类,用多态来处理各类型特有的行为。

    先对airSpeedVelocity和plumage两个函数使用【函数组合成类】。

    复制代码
    package com.javaedge.refactor.condition;
    
    import lombok.Getter;
    
    /**
     * @author JavaEdge
     * @date 2022/4/10
     */
    @Getter
    public class Test {
    
        private Bird bird;
    
        public String plumages() {
            return bird.getPlumage();
        }
    
        public int speeds() {
            return bird.airSpeedVelocity();
        }
    }
    package com.javaedge.refactor.condition;
    
    import lombok.Getter;
    import lombok.NoArgsConstructor;
    
    import java.util.List;
    import java.util.Map;
    import java.util.Objects;
    import java.util.function.Function;
    import java.util.stream.Collectors;
    
    /**
     * @author JavaEdge
     * @date 2022/4/10
     */
    @Getter
    @NoArgsConstructor
    public class Bird {
    
        private Objects plumages;
        private String name;
        private String type;
        private int numberOfCoconuts;
        private boolean isNailed;
        private int voltage;
    
        Bird Bird(Bird bird) {
            return new Bird();
        }
        public String getPlumage() {
            switch (this.type) {
                case "EuropeanSwallow":
                    return "average";
                case "AfricanSwallow":
                    return (this.numberOfCoconuts > 2) ? "tired" : "average";
                case "NorwegianBlueParrot":
                    return (this.voltage > 100) ? "scorched" : "beautiful";
                default:
                    return "unknown";
            }
        }
    
        public Integer airSpeedVelocity() {
            switch (this.type) {
                case "EuropeanSwallow":
                    return 35;
                case "AfricanSwallow":
                    return 40 - 2 * this.numberOfCoconuts;
                case "NorwegianBlueParrot":
                    return (this.isNailed) ? 0 : 10 + this.voltage / 10;
                default:
                    return null;
            }
        }
    }
    复制代码

    然后针对每种鸟创建一个子类,用一个工厂函数来实例化合适的子类对象。

    复制代码
    package com.javaedge.refactor.condition;
    
    import lombok.Getter;
    import lombok.NoArgsConstructor;
    
    import java.util.List;
    import java.util.Map;
    import java.util.Objects;
    import java.util.function.Function;
    import java.util.stream.Collectors;
    
    /**
     * @author JavaEdge
     * @date 2022/4/10
     */
    @Getter
    @NoArgsConstructor
    public class Bird {
    
        private Objects plumages;
        private String name;
        private String type;
        private int numberOfCoconuts;
        private boolean isNailed;
        private int voltage;
    
        public Bird(Bird bird) {
        }
    
        public String getPlumage() {
            switch (this.type) {
                case "EuropeanSwallow":
                    return "average";
                case "AfricanSwallow":
                    return (this.numberOfCoconuts > 2) ? "tired" : "average";
                case "NorwegianBlueParrot":
                    return (this.voltage > 100) ? "scorched" : "beautiful";
                default:
                    return "unknown";
            }
        }
    
        public Integer airSpeedVelocity() {
            switch (this.type) {
                case "EuropeanSwallow":
                    return 35;
                case "AfricanSwallow":
                    return 40 - 2 * this.numberOfCoconuts;
                case "NorwegianBlueParrot":
                    return (this.isNailed) ? 0 : 10 + this.voltage / 10;
                default:
                    return null;
            }
        }
    
        public String plumage(Bird bird) {
            return createBird(bird).getPlumage();
        }
    
        public Integer airSpeedVelocity(Bird bird) {
            return createBird(bird).airSpeedVelocity();
        }
    
        public Bird createBird(Bird bird) {
            switch (bird.type) {
                case "EuropeanSwallow":
                    return new EuropeanSwallow(bird);
                case "AfricanSwallow":
                    return new AfricanSwallow(bird);
                case "NorwegianBlueParrot":
                    return new NorwegianBlueParrot(bird);
                default:
                    return new Bird(bird);
            }
        }
    }
    
    class EuropeanSwallow extends Bird {
        public EuropeanSwallow(Bird bird) {
        }
    }
    
    class AfricanSwallow extends Bird {
        public AfricanSwallow(Bird bird) {
    
        }
    }
    
    class NorwegianBlueParrot extends Bird {
        public NorwegianBlueParrot(Bird bird) {
    
        }
    }
    复制代码

    现在我已经有了需要的类结构,可以处理两个条件逻辑了。先从plumage函数开始,我从switch语句中选一个分支,在适当的子类中覆写这个逻辑。

    复制代码
    class EuropeanSwallow extends Bird {
        public EuropeanSwallow(Bird bird) {
        }
    
        @Override
        public String getPlumage() {
            return "average";
        }
    
        @Override
        public Integer airSpeedVelocity() {
            return 35;
        }
    }
    
    class AfricanSwallow extends Bird {
        private int numberOfCoconuts;
    
        public AfricanSwallow(Bird bird) {
    
        }
    
        @Override
        public String getPlumage() {
            return (this.numberOfCoconuts > 2) ? "tired" : "average";
        }
    
        @Override
        public Integer airSpeedVelocity() {
            return 40 - 2 * this.numberOfCoconuts;
        }
    }
    
    class NorwegianBlueParrot extends Bird {
        private int voltage;
        private boolean isNailed;
    
        public NorwegianBlueParrot(Bird bird) {
    
        }
    
        @Override
        public String getPlumage() {
            return (this.voltage > 100) ? "scorched" : "beautiful";
        }
    
        @Override
        public Integer airSpeedVelocity() {
            return (this.isNailed) ? 0 : 10 + this.voltage / 10;
        }
    }
    复制代码

    父类函数保留下来处理默认情况。

    public String getPlumage() {
      return "unknown";
    }

     

    点击关注,第一时间了解华为云新鲜技术~

  • 相关阅读:
    详解FreeRTOS:FreeRTOS任务调度器开启过程源码分析(进阶篇—5)
    php反序列化个人笔记
    beego框架自学笔记1
    github代码比对颜色介绍
    Java开发树结构数据封装!
    docker给已存在的容器添加或修改端口映射
    【无标题】
    linux--进程通信--管道通信
    Starday为什么是跨境电商卖家的不二之选?
    Java项目:台球室计费管理系统(java+SSM+JSP+HTML+JavaScript+mysql)
  • 原文地址:https://www.cnblogs.com/huaweiyun/p/16152216.html