淺談 Dart 類與類的基本方法

ssssyoki發表於2021-08-26

一、Dart 語言

Dart 是一門物件導向的語言。

Dart 語言中所有的變數引用都是一個物件,並且所有的物件都對應一個類的例項。無論是數字、函式和null ,都是一個物件,所有物件繼承自Object類。在 2.12 版本 Dart 新增空安全後(sound null safety),null不再是Object的子類。

Object

Object 具有下列特性:

  • 兩個屬性:hashCoderuntimeType
  • 兩個方法:noSuchMethodtoString
  • 操作符:Operator

hashCode

hashCode 是 read-only 屬性的 int 型別資料,因為 Dart 所有的變數引用均繼承於Object,所以每個變數引用都有自己的hashCodehashCode的設計目的是為了提高 Dart VM 引擎在解析資料結構時的效率,並且參與判斷物件的邏輯運算,與操作符 operator 相輔相成。考察如下程式碼:

void main() {
    P p1 = P("yo","no");
    P p2 = P("yo","no");
  
    print(p1 == p2);
    print(p1.hashCode);
    print(p2.hashCode);
    
    Map<P, String> myMap = {};
    myMap[p1] = "first Item";

    print(myMap.containsKey(p2));
}

class P {
    var naam;
    var kaam;
    P(this.naam, this.kaam);
    
    @override
    int get hashCode {
        int result = 11 * naam.hashCode;
        result = 13 * (result + kaam.hashCode);
        return result;
    } 
    
    @override
    bool operator == (dynamic otherObj) {
        return (otherObj is P) && (otherObj.naam == naam) && (otherObj.kaam == kaam);
    }
}
複製程式碼

程式碼重寫了P物件的比較相等的邏輯,從而令p1p2的比較結果返回為true。但同時需要重寫hashCode的邏輯,否則返回值仍舊為false

Dart 建議hashCodeOperator如有必要重寫其中之一,那麼另一個也需要重寫,從而令雜湊對映正常工作,否則Operator重寫無效。

runtimeType

runtimeType是 read-only 屬性,表達資料結構在執行時的型別。runtimeType涉及 Dart VM 執行機制,此處留坑以後再來填。

noSuchMethod

當呼叫物件上不存在的方法時,就會觸發noSuchMethod,考察如下程式碼:

void main() {
    P p = P();
    print(p.add(888));
}

class P {
    @override
    noSuchMethod(Invocation invocation) => 
        'Got the ${invocation.memberName} with arguments ${invocation.positionalArguments}';
}
複製程式碼

在 DartPad 編輯器執行上述程式碼,發現並未觸發noSuchMethod方法, 而是直接丟擲了編譯錯誤。這是因為noSuchMethod觸發需要有兩個條件:

  • 呼叫方必須是dynamic型別;
  • 呼叫方具有被呼叫方法的定義但未實現,同時noSuchMethod也被重寫;

所以只有如下兩種情況,才會觸發noSuchMethod

void main() {
    dynamic p = P();
    Q q = Q(); // 該情況下,q 可以是 Q 型別,也可以是 dynamic
    print(p.add(123));
    print(q.add(123));
}

class P {
    @override
    noSuchMethod(Invocation invocation) => 'Got the ${invocation.memberName} with arguments ${invocation.positionalArguments}';
    }
    
class Q {
    miss(int data);
    @override
    noSuchMethod(Invocation invocation) => 'Got the ${invocation.memberName} with arguments ${invocation.positionalArguments}';
}
複製程式碼

toString

toString方法使用字串來表達物件的資訊,也可以在將數字轉換為字串的場景下使用。開發者也可以重寫 toString方法,以加入自定義內容。

void main() {
    P p = P();
    p.toString();
}

class P {
    @override
    toString() {
        return 'this is custom text'
    } 
}
複製程式碼

二、Dart 類與基本方法

1.類的定義和建構函式

class P {
    String name;
    int age;
    
    P(String dataName, int dataAge) {
        name = dataName;
        age = dataAge;
    } 
    
    bool isOld() {
        return age > 30 ? true : false;
    } 
}

void main() {
    var p = new P('tom', 12);
    print(p.name);
}
複製程式碼

上述程式碼中,宣告瞭P類,P 中含有兩個屬性:nameage。同時也宣告瞭建構函式,通過向建構函式傳入引數從而建立例項p

建立例項仍然使用傳統的new方法,但在 Dart 2 以上版本,new關鍵字可以省略,同時建構函式也可以用語法糖簡化,程式碼寫法如下:

class P {
    String name;
    int age;
    
    P(this.name, this.age);
    
    bool isOld() {
        return age > 30 ? true : false;
    }
}

void main() {
    var p = P('tom', 12);
    print(p.name); // tom
}
複製程式碼

命名建構函式

除預設的建構函式外,Dart 提供命名建構函式方法。程式碼如下:

class P {
    String name;
    int age;
    
    P(this.name, this.age);
    
    P.init(String dataName, int dataAge) {
        name = dataName;
        age = dataAge;
    } 
    
    bool isOld() {
        return age > 30 ? true : false;
    }
}

void main() {
    var p = P.init('tom', 12);
    print(p.name); // tom
}
複製程式碼

命名建構函式的功能看起來與預設建構函式的功能類似,那設計該機制的目的是什麼呢?原因是 Dart 不支援建構函式的過載,無法使用不同的引數來執行構造方法,所以提供命名建構函式的機制來實現多方式建立例項。

工廠建構函式

除了上述兩種建構函式外,Dart 還提供第三種建構函式:工廠建構函式。它的使用場景是:如果呼叫建構函式時,如果例項已存在,不會重新建立例項,而是使用已存在的例項,保證環境內只有一個例項存在,也就是單例模式。考察如下程式碼:

class P {
    String name;
    static Map<String, dynamic> _cache = {};
    
    factory P(String name) {
        if (_cache.containsKey(name)) {
            return _cache[name];
        } else {
            final p = P._internal(name);
            _cache[name] = p;
            return p;
        }
    }
    
    P._internal(this.name);
}

void main() {
    final p = P('tom');
    print(p.name);
}
複製程式碼

工廠建構函式不允許訪問this,所以_cache的形態必須是static。每次建立例項時,如果例項已經在_cache中存在,那麼返回已存在的例項。換句話說,工廠建構函式並沒有自動建立例項,而是把決定權交給開發者。

2.類的屬性和方法

靜態變數和靜態方法

和大多數語言一樣,Dart 提供靜態變數和靜態方法:

class P {
    static age;
    static bool isOld() {
        return age > 30 ? true : false;
    }
}
複製程式碼

靜態方法無法使用this,也不能訪問非靜態成員,類的例項也無法呼叫靜態方法,並且靜態變數只有在被使用的時候才會初始化。

私有屬性和私有方法

Dart 不提供類似publicprotected等關鍵字,在變數前新增下劃線即可宣告私有:

class P {
    int _age;
    P(this._age);
    
    bool _isOld() {
        return this._age > 30 ? true : false; 
    } 
    
    bool isOld() { 
        return _isOld(); 
    }
}

void main() {
    final p = P(20);
    print(p._age);     // error
    print(p._isOld()); // error 
    print(p.isOld());  // false 
}
複製程式碼

例項無法直接呼叫私有方法,但是可以通過呼叫公有方法的形式間接呼叫私有方法。

3.類的繼承

建構函式

Dart 通過extends關鍵字實現繼承,子類繼承父類中公有的屬性和方法,不會繼承建構函式,所以子類的建構函式需通過super關鍵字來呼叫或改造父類的建構函式:

class P {
    num name;
    num age;
    
    P(this.name, this.age);
    P.xxx(this.name, this.age); 
}

// class Q extends P {
//     Q(num name, num age): super(name, age); 
// } 

// class Q extends P { 
//     num sex; 
//     Q(num sex, num name, num age): super.xxx(name, age) {
//         this.sex = sex;
//     } 
// } 

class Q extends P { 
    num sex; 
    Q(num sex, num name, num age): super(name, age) { 
        this.sex = sex;
    }
}

void main() {
    final q = Q(12, 13, 14);
    print(q.sex); // 12 
}
複製程式碼

上述程式碼演示了子類中三種建構函式的表現:

  • 直接複用父類建構函式
  • 複用和改造父類預設建構函式
  • 複用和改造父類命名建構函式

子類呼叫父類方法

如果子類需要呼叫父類方法,同樣使用super關鍵字,此時方法內部的this指向子類:

class P {
    num name;
    num age; 
    
    P(this.name, this.age); 
    
    bool childCheck() { 
        return age > 20 ? true : false; 
    } 
} 

class Q extends P { 
    Q(num name, num age): super(name, age); 
    
    bool check() { 
        return super.check(); 
    } 
}

void main() { 
    final a = Q(12, 13); 
    print(a.childCheck()); // false
}
複製程式碼

子類重寫父類方法

class P { 
    num name; 
    num age; 
    
    P(this.name, this.age); 
    
    bool check() { 
        return age > 20 ? true : false; 
    } 
} 

class Q extends P { 
    Q(num name, num age): super(name, age); 
    
    @override
    bool childCheck() { 
        return age == 13 ? true : false; 
    } 
}

void main() { 
    final a = Q(12, 13); 
    print(a.childCheck()); // true 
}
複製程式碼

子類在重寫父類方法時,只要方法名稱與父類相同,便可實現邏輯重寫,@override為可選項,對於複雜邏輯的類功能建議新增@override

相關文章