一次簡單易懂的多型重構實踐,讓你理解條件邏輯

華為雲開發者社群發表於2022-04-16
摘要:複雜的條件邏輯是程式設計中最難理解的東西之一。

本文分享自華為雲社群《簡單易懂的多型重構實踐》,作者: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<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";
}

 

點選關注,第一時間瞭解華為雲新鮮技術~

相關文章