3

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

 2 years ago
source link: https://juejin.cn/post/7087016490636935175
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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

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

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<Bird> birds) {
        return birds.stream().collect(Collectors.toMap(Bird::getPlumages, Function.identity()));
    }

    public Map speeds(List<Bird> 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";
}
复制代码

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK