避免過多if - else的新姿勢:衛語句、小函數、多態、反射

目錄

 

前言

衛語句

代碼:

小函數

迪米特法則

概念:

代碼:

Map 提取對象

Stream

多態

反射


前言

在我們平常開發過程中,由於項目時間緊張,代碼可以用就好,往往會忽視代碼的質量問題。甚至有些複製粘貼過來,不加以整理規範。往往導致項目後期難以維護,更別說後續接手項目的人。所以啊,我們要編寫出優雅的代碼,方便你我他,豈不美哉?

下面分享一些我在開發中常用的編碼中小建議,如有不妥,歡迎大家一起交流學習。

衛語句

衛語句,就是把複雜的條件表達式拆分成多個條件表達式。比如 多個 if-elseif-else 嵌套, 可以拆分成多個 if。如下面代碼

代碼:


-------------------- before  --------------------

public void today() {
    if (isWeekend()) {
        if (isFee()) {
            System.out.println("study Android");
        } else {
            System.out.println("play a game");
        }
    } else {
        System.out.println("go to work");
    }
}


 -------------------- after  (建議) --------------------

public void today() {

    // 提前過濾掉`特殊情況`
    if (!isWeekend()) {
        System.out.println("go to work");
        return; // 提前return
    }

    //提前過濾掉`特殊情況`
    if (isFee()) {
        System.out.println("study Android");
        return; // 提前return
    }

    // 更關注於 `核心業務`代碼實現。
    System.out.println("play a game");
}

提前過濾掉特殊情況,更關注核心業務邏輯

小函數

我們平常開發的時候,應該編寫小而美函數,避免函數過長。一般函數最好在15行以內(建議) 我們看看下面代碼:


-------------------- before  --------------------

if (age > 0 && age < 18){
    System.out.println("小孩子");
}

if (number.length() == 11){
    System.out.println("符合手機號");
}

-------------------- after (建議) --------------------

private static boolean isChild(int age) {
    return age > 0 && age < 18;
}

private static boolean isPhoneNumber(String number) {
    return number.length() == 11;
}

if (isChild(age)){
    System.out.println("小孩子");
}

if (isPhoneNumber(number)){
    System.out.println("符合手機號");
}

把判斷語句抽取成一個個小函數, 這樣代碼更加清晰明瞭。

迪米特法則

概念:

迪米特法則(Law of Demeter)又叫作最少知識原則(Least Knowledge Principle 簡寫LKP),就是說一個對象應當對其他對象有盡可能少瞭解。例如 當一條語句中 一個對象出現兩個 .student.getName().equals("張三")) 就是代碼壞味道的表現,如下代碼所示。

代碼:


-------------------- before  --------------------

public class Student {

    private String name;

    public Student(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

public static void main(String[] args) {

    Student student = new Student("張三");

    // 注意看這裏,
    // 這裏獲取 student的name屬性,在根據name屬性進行判斷
    if (StringUtils.isNotBlank(student.getName()) && student.getName().equals("張三")) {
        System.out.println("我的好朋友是 " + student.getName());
    }
}


 -------------------- after (建議) --------------------
 
 public class Student {

    ... 省略name代碼

    // 新增一個 判斷是否是我的好朋友方法
    public boolean isGoodFriend(){
        return StringUtils.isNotBlank(this.name) && this.name.equals("張三");
    }
}

public static void main(String[] args) {

    Student student = new Student("張三");

    // 根據迪米特法則,把判斷邏輯,抽取到 Student 內部,暴露出方法(isGoodFriend)
    if (student.isGoodFriend()){
        System.out.println("我的好朋友是 " + student.getName());
    }
}

IDEA/Android Studio 抽取方法快捷鍵: option + command + M

Map 提取對象

我們在平常開發中,會使用到map,但是在面向對象開發理念中,一個 map的使用,往往就會錯過了 Java Bean。建議使用 Java Bean 更直觀。如下代碼:

public static void main(String[] args) {

    -------------------- before  --------------------
        Map<String, String> studentMap = new HashMap<>();
        studentMap.put("張三", "男");
        studentMap.put("小紅", "女");
        studentMap.put("李四", "男");

        studentMap.forEach((name, sex) -> {
            System.out.println(name + " : " + sex);
        });

    -------------------- after (建議)  --------------------
    
        List<Student> students = new ArrayList<>();
        students.add(new Student("張三", "男"));
        students.add(new Student("小紅", "女"));
        students.add(new Student("李四", "男"));

        for (Student student : students) {
            System.out.println(student.getName() + ":" + student.getSex());
        }
    }

筆者在編寫這點時候,有所顧慮。肯定有小夥伴跳出來說,mapbean 不是一樣嗎?用map 我還可以省去思考如何命名Class呢。但是從代碼規範來說,這樣代碼設計不是更符合 Java 面向對象的思想嗎?

Stream

Java 8 API添加了一個新的抽象稱爲流Stream,可以讓你以一種聲明的方式處理數據。使得代碼調用起來更加優雅~ 直接來看代碼:

public static void main(String[] args) {
        List<Student> students = new ArrayList<>();
        students.add(new Student("張三", "男"));
        students.add(new Student("李四", "男"));
        students.add(new Student("小紅", "女"));
        students.add(new Student("小花", "女"));
        students.add(new Student("小紅", "女"));
        
        -------------------- before  --------------------
        //統計男生個數
        //傳統的 for each 循環遍歷
        long boyCount = 0;
        for (Student student : students) {
            if (student.isBoy()) {
                boyCount++;
            }
        }

        System.out.println("男生個數 = " + boyCount);

        -------------------- after (建議)  --------------------

        //統計男生個數
        //stream 流遍歷
        long count = students.stream()
                .filter(Student::isBoy) // 等同於.filter(student -> student.isBoy())
                .count();
                
        System.out.println("男生個數 = " + boyCount);
}

相比與 傳統的 For 循環,更推薦大家使用 stream 遍歷。 stream 流的鏈式調用,還有許多騷操作,如 sorted, map, collect等操作符,可以省去不必要if-elsecount等判斷邏輯。

多態

Java 三大特性之一,多態,相信大家都不會陌生,多態的好處就是根據對象不同類型採取不同的的行爲。我們常常在編寫 switch 語句的時候,如果改用多態,可以把每個分支,抽取到一個子類內的覆寫函數中,這就更加靈活。

我們有這樣一個需求,編寫一個簡單計算器方法,我們先來看一小段代碼:


    -------------------- before  --------------------
    public static int getResult(int numberA, int numberB, String operate) {
        int result = 0;
        switch (operate) {
            case "+":
                result = numberA + numberB;
                break;
            case "-":
                result = numberA - numberB;
                break;
            case "*":
                result = numberA * numberB;
                break;
            case "/":
                result = numberA / numberB;
                break;
        }
        return result;
    }
    
    -------------------- after (建議)  --------------------
    
    abstract class Operate {
        abstract int compute(int numberA, int numberB);
    }
    
    class AddOperate extends Operate {

        @Override
        int compute(int numberA, int numberB) {
            // TODO 在這裏處理相關邏輯
            return numberA + numberB;
        }
    }
    
    ... SubOperate, MulOperate, DivOperate 也和 AddOperate一樣這裏就不一一貼出
    
    public static int getResult(int numberA, int numberB, String operate) {
        int result = 0;
        switch (operate) {
            case "+":
                result = new AddOperate().compute(numberA, numberB);
                break;
            case "-":
                result = new SubOperate().compute(numberA, numberB);
                break;
            case "*":
                result = new MulOperate().compute(numberA, numberB);
                break;
            case "/":
                result = new DivOperate().compute(numberA, numberB);
                break;
        }
        return result;
    }

有小夥伴可能會說,你這不是更復雜了嗎?

對比起單純的switch,我們可以這樣理解:

  • 雖然在類上有所增加,但是通過多態,把對應操作的邏輯分離出來,使得代碼耦合度降低。
  • 如果要修改對應加法的邏輯, 我們只需要修改對應 AddOperate類就可以了。避免直接修改getResult 方法
  • 代碼可讀性更好,語義更加明確。

但是這裏會存在一些問題,如果我們新增一個平方根平方等計算方式, 就需要修改 switch 裏面的邏輯,新增一個條件分支。下面我們再來看看更進一步的優化。

反射

通過上面例子,我們可以進一步優化,通過反射生成對應的 Class,然後在調用compute方法。如下代碼:

public static <T extends Operate> int getResult(int numberA, int numberB, Class<T> clz) {
        int result = 0;
        try {
            return clz.newInstance().compute(numberA, numberB);
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
            return result;
        }
}


public static void main(String[] args) {
    // 調用的時候直接傳遞 class 即可
    System.out.println(getResult(1, 2, SumOpearte.class));
}

根據傳入 class 參數,然後生成對應 Opearte處理類, 對比多態方式,我們這裏採用反射,使得代碼耦合度大大降低,如果在增加平方根平方等計算方式。我們只需要 新增一個 class 繼承 Opearte 即可,getResult 不用做任何修改。

需要注意的是,不是所有switch語句都需要這樣替換, 在面對簡單的 switch語句,就不必要了, 避免過度設計的嫌疑。如下代碼:

public String getResult(int typeCode) {
        String type = "";
        switch (typeCode) {
            case 0:
                type = "加法";
                break;
            case 1:
                type = "減法";
                break;
            case 2:
                type = "乘法";
                break;
            case 3:
                type = "觸發";
                break;
        }
        return type;
}


鏈接:https://juejin.im/post/5dafbc02e51d4524a0060bdd
 

發佈了410 篇原創文章 · 獲贊 1345 · 訪問量 208萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章