Dart 06 類

1 類

Dart 是一種基於類和 mixin 繼承機制的面向對象的語言。 每個對象都是一個類的實例,所有的類都繼承於 Object. 。 基於 * Mixin 繼承* 意味着每個類(除 Object 外) 都只有一個超類, 一個類中的代碼可以在其他多個繼承類中重複使用。

1.1 構造函數

構造器的名字可以是 ClassName(無名構造器),也可以是 ClassName.identifier(命名構造器),實例化時的 new 關鍵字可以省略。

所有的實例變量都會自動生成隱式的 getter 方法,非 final 的實例變量也會自動生成隱式的 setter 方法,使用 get 和 set 關鍵詞可以創建額外的屬性和其 getter/setter 方法的實現。

1.1.1默認構造函數

  • 在沒有聲明構造函數的情況下, Dart 會提供一個默認的構造函數。 默認構造函數沒有參數並會調用父類的無參構造函數。

  • 當創建一個類時 該類會有一個默認構造函數 不需要顯式的寫出來其默認存在

  • 如果需要添加參數 則要寫帶參數構造 或者命名構造函數

    • 比如

      • class Perosn{
           	    String name;
            int age;
                   		//默認構造函數 不需要顯式的寫出來
             		Person(){}
         }
        

1.1.2 帶參數普通構造函數

  • Person(String name,int age){
        this.name=name;
        this.age=age;
    }
    
  • 簡化寫法

    • Person(String this.name, int this.age);
      

1.1.3 命名構造函數

  • 使用命名構造函數可爲一個類實現多個構造函數, 也可以使用命名構造函數來更清晰的表明函數意圖:

    • class Perosn{
         String name;
         int age;
         //命名構造函數
         Person.fromDictionary(String name){
            this.name=name;
         }
          // 命名構造函數 fromDictionary1 括號中可以爲任意類型
        Person.fromDictionary1(Map dic) {
          this.age = dic["age"];
          this.name = dic["name"];
        }
      }
      

1.1.3.1注意

1.1.3.1.1構造函數不被繼承
  • 子類不能繼承父類的構造器,所以,如果如果子類想利用父類的命名構造器創建實例,就必須實現父類的命名構造器。如果父類沒有無名無參構造器,那麼你就必須手動調用父類的一個構造器:

    • class Point {
        int x;
        int y;
        // 在構造函數體執行之前,
      // 通過初始列表設置實例變量。
        Point.fromJson(Map<String, int> map)
            : x = map["x"],
              y = map["y"] {
          print('In Point.fromJson(): ($x, $y)');
        }
      }
      class Child extends Point {
          // 子類調用父類
        Child.fromJson(Map<String, int> data) : super.fromJson(data) {
          print('in Child');
        }
      }
      main() {
        var emp = new Child.fromJson({"x":10,"y":10});
      }
      //輸出
      //In Point.fromJson(): (10, 10)
      //in Child
      //代碼初始化的順序爲: (初始化參數列表) → (父類的無名構造函數) → (主類的無名構造函數)
      
1.1.3.1.2初始化列表

除了調用超類構造函數之外, 還可以在構造函數體執行之前初始化實例變量。 各參數的初始化用逗號分隔。

  • // 調用方式參考上面代碼 通過super.  
    // 在構造函數體執行之前,
    // 通過初始列表設置實例變量。
    // 初始化列表不支持訪問this
      Point.fromJson(Map<String, int> map)
          : x = map["x"],
            y = map["y"] {
        print('In Point.fromJson(): ($x, $y)');
      }
    
  • 默認構造函數不需要顯式聲明 默認已經存在 若仍顯示聲明,則顯示聲明的默認構造函數不能與帶參數構造函數並存 會提示默認構造函數已經聲明,將其中一個聲明爲命名構造函數

    • 說明 dart 不允許存在同名構造函數

1.1.4 重定向構造函數

  • 有時構造函數的唯一目的是重定向到同一個類中的另一個構造函數。 重定向構造函數的函數體爲空, 構造函數的調用在冒號 (😃 之後。

    • class Point {
        num x;
        num y;
      
        // 主構造函數
        Point(this.x, this.y) {
          print("Point($x, $y)");
        }
      
        // 重定向構造函數,指向主構造函數,函數體爲空
        Point.alongXAxis(num x) : this(x, 0);
      }
      

1.1.5 常量構造函數(單例)

如果該類生成的對象是固定不變的, 那麼就可以把這些對象定義爲編譯時常量。 爲此,需要定義一個 const 構造函數, 並且聲明所有實例變量爲 final

class ImmutablePoint {
  static final ImmutablePoint origin =
      const ImmutablePoint(0, 0);

  final num x, y;

  const ImmutablePoint(this.x, this.y);
}
//常量構造函數創建的實例並不總是常量。

1.1.6 工廠構造函數

  • 工廠類構造函數無法訪問this

當執行構造函數並不總是創建這個類的一個新實例時,如果之前存在過就可以從緩存中獲取返回;使用 factory 關鍵字。

例如,一個工廠構造函數可能會返回一個 cache 中的實例, 或者可能返回一個子類的實例。

class Logger {
  final String name;
  bool mute = false;

  // 從命名的 _ 可以知,
  // _cache 是私有屬性。
  static final Map<String, Logger> _cache =
      <String, Logger>{};

  factory Logger(String name) {
    if (_cache.containsKey(name)) {
      return _cache[name];
    } else {
      final logger = Logger._internal(name);
      _cache[name] = logger;
      return logger;
    }
  }

  Logger._internal(this.name);

  void log(String msg) {
    if (!mute) print(msg);
  }
}

1.2 方法

  • 即類中的函數 可以訪問this和實例變量

1.2.1 getter setter

  • 所有的實例變量都會自動生成隱式的 getter 方法,非 final 的實例變量也會自動生成隱式的 setter 方法,使用 get 和 set 關鍵詞可以創建額外的屬性和其 getter/setter 方法的實現。

    • class Rectangle {
        num left, top, width, height;
      
        Rectangle(this.left, this.top, this.width, this.height);
      
        // 定義兩個計算屬性: right 和 bottom。
        num get right => left + width;
        set right(num value) => left = value - width;
        num get bottom => top + height;
        set bottom(num value) => top = value - height;
      }
      
      void main() {
        var rect = Rectangle(3, 4, 20, 15);
        assert(rect.left == 3);
        rect.right = 12;
        assert(rect.left == -8);
      }
      
  • 抽象方法

    • 只定義接口不進行實現,而是留給其他類去實現。 抽象方法只存在於 抽象類 中。

      定義一個抽象函數,使用分號 (😉 來代替函數體:

      • abstract class Doer {
          // 定義實例變量和方法 ...
        
          void doSomething(); // 定義一個抽象方法。
        }
        
        class EffectiveDoer extends Doer {
          void doSomething() {
            // 提供方法實現,所以這裏的方法就不是抽象方法了...
          }
        }
        

2 抽象類

使用 abstract 修飾符來定義 抽象類 — 抽象類不能實例化但是可以實例化其子類。 抽象類通常用來定義接口,以及部分實現。

抽象類中可以有抽象方法和非抽象方法

abstract class Doer {
// 定義實例變量和方法 ...

void doSomething(); // 定義一個抽象方法。
}

3 隱式接口(接口)

在dart 中類就是接口,每個類都隱式的定義了一個接口,接口包含了該類所有的實例成員及其實現的接口。

一個類可以通過 implements 關鍵字來實現一個或者多個接口, 並實現每個接口要求的 API

  • 示例

    • 如果要創建一個 A 類,A 要支持 B 類的 API ,但是不需要繼承 B 的實現, 那麼可以通過 A 實現 B 的接口。

    • // person 類。 隱式接口裏麪包含了 greet() 方法聲明。
      class Person {
        // 包含在接口裏,但只在當前庫中可見。
        final _name;
      
        // 不包含在接口裏,因爲這是一個構造函數。
        Person(this._name);
      
        // 包含在接口裏。
        String greet(String who) => 'Hello, $who. I am $_name.';
      }
      
      // person 接口的實現。
      class Impostor implements Person {
        get _name => '';
      
        String greet(String who) => 'Hi $who. Do you know who I am?';
      }
      
      String greetBob(Person person) => person.greet('Bob');
      
      void main() {
        print(greetBob(Person('Kathy')));
        print(greetBob(Impostor()));
      }
      
  • 實現多個接口

    • class Point implements Comparable, Location {...}
      

4 擴展類(繼承)

使用 extends 關鍵字來創建子類, 使用 super 關鍵字來引用父類:

class Television {
void turnOn() {

}
// ···
}
class SmartTelevision extends Television {
void turnOn() {
 super.turnOn();
}
// ···
}

4.1 重寫父類方法 通過@override

  • class Television {
      void turnOn() {
     	////
      }
      // ···
    }
    class SmartTelevision extends Television {
     	@override
        void turnOn() {
       
      }
      // ···
    }
    

5 枚舉類

5.1使用枚舉

使用 enum 關鍵字定義一個枚舉類型:

enum Color { red, green, blue }

使用枚舉的 values 常量, 獲取所有枚舉值列表( list )。

List<Color> colors = Color.values;
assert(colors[2] == Color.blue);

可以在 switch 語句 中使用枚舉, 如果不處理所有枚舉值,會收到警告:

var aColor = Color.blue;

switch (aColor) {
  case Color.red:
    print('Red as roses!');
    break;
  case Color.green:
    print('Green as grass!');
    break;
  default: // 沒有這個,會看到一個警告。
    print(aColor); // 'Color.blue'
}

枚舉類型具有以下限制:

  • 枚舉不能被子類化,混合或實現。
  • 枚舉不能被顯式實例化。

6 類變量和類方法

使用 static 關鍵字實現類範圍的變量和方法。

6.1 靜態變量

靜態變量(類變量)對於類級別的狀態是非常有用的:

class Queue {
  static const initialCapacity = 16;
  // ···
}

void main() {
  assert(Queue.initialCapacity == 16);
}

靜態變量只到它們被使用的時候纔會初始化。

6.2 靜態方法

靜態方法(類方法)不能在實例上使用,因此它們不能訪問 this 。 靜態方法只能訪問靜態變量例如:

class Child extends Point {
  Child.fromJson(Map<String,int> data) : super.fromJson(data) {
    print('in Child');
  }
  static var i=10;
  var j=10;
  static void m(){
    i++;
    j++;// 這句話會有err
  }
}

提示: 對於常見或廣泛使用的工具和函數, 應該考慮使用頂級函數而不是靜態方法。

靜態函數可以當做編譯時常量使用。 例如,可以將靜態方法作爲參數傳遞給常量構造函數。

7 Mixin 爲類添加功能

什麼是Mixin

mix-in 上面1 類中提到過 這是一種繼承機制 往下看

mix in官方解釋:?

  • 複用代碼的一種途徑複用的類可以在不同層級,之間可以不用存在繼承關係

什麼意思?

  • 學過java的應該都知道繼承的概念,當一個類A 想要複用類B中的代碼或者功能那非常簡單,直接A 繼承 B ;如果有一個共同模塊功能多數時候我們通過抽象類(或者接口)定義功能類由 A,B去繼承抽象類;

那麼現在問題來了?

  • 如果A,B 並不是同一類對象,比如一個是水鳥,一個是魚,二者同時具備了游泳的能力,如果B(魚)想複用A(水鳥)游泳的代碼,二者並不屬於統一類別物種,這時候使用繼承也不合適,那我們要重寫一份代碼嗎?
    • 顯然不用,我們可以單獨將游泳定義成一個抽象類或者接口ClassSwim A,B去繼承ClassSwim,這時候A,B 與ClassSwim存在繼承關係
    • dart在 dart 2.1 加入的特性MixIin來代替抽象類,這時候我們要實現上述功能只需要將ClassSwim分別混入A,B, A,B類就可以複用swim

在這時候是不是還不太理解爲什麼要引入MixIn ,定義單一抽象類不就行了 ?

  • 官網描述:
    • 在支持類和繼承的語言中,類隱式定義了mixinmixin通常是隱式的-由類主體定義,並構成類與其超類之間的增量。該類實際上是一個mixin應用程序將其隱式定義的mixin應用於其超類的結果。
    • 所以這裏只是將概念提出來並不是新引入。
  • Mixin帶來的是更方便的功能組合,的確我們可以通過extends 繼承一個抽象類,通過implements實現多個接口從而來擴展實現類的功能,那我爲啥要用mixin
    • 我從文檔裏找到了這麼一段描述
    • mixin只能從沒有聲明構造函數的類中提取。此限制避免了由於需要向繼承鏈上傳遞構造函數參數而產生的複雜情況。
      • 前面構造函數我們提到過繼承類構造函數執行順序的問題
    • 從我的淺顯的理解上我感覺是
      • 不用重寫一份代碼,直接混入,引用需要的模塊 方便維護
      • 避免繼承鏈帶來的傳遞構造函數參數傳遞帶來的問題
      • 如果你想了解的更多
        • 複製連接去看吧https://www.dartcn.com/articles/language/mixins

使用Mixin

  • 通過 with 後面跟一個或多個混入的名稱(只要是類都可以,class mixin abstract class),來 使用 Mixin ,

    • 比如一個音樂家 首先是個表演者 其次具有音樂技能

      • class Musician extends Performer with Musical {
          // ···
        }
        
  • 通過創建一個繼承自 Object 且沒有構造函數的類,來 實現 一個 Mixin 。 如果 Mixin 不希望作爲常規類被使用,使用關鍵字 mixin 替換 class 。 例如:

    • mixin Musical {
        bool canPlayPiano = false;
        bool canCompose = false;
        bool canConduct = false;
      
        void entertainMe() {
          if (canPlayPiano) {
            print('Playing piano');
          } else if (canConduct) {
            print('Waving hands');
          } else {
            print('Humming to self');
          }
        }
      }
      
  • 使用 on 來指定可以使用 Mixin 的父類類型:

    • 比如 音樂表演者父類是 音樂家

      • mixin MusicalPerformer on Musician {
          // ···
        }
        

注意

  • with 後面可以跟任何類 只要是類-無論是 mixin adstruct class ,class 修飾的類都可以

  • Mixin 修飾的類不能有構造函數

  • Mixin 內可以存在抽象方法和普通方法 混入後需要實現所有的抽象方法,普通方法則不需要

  • 如果不想讓類想普通類一樣被使用 用mixin修飾即可

總結

  • mixin 一種繼承機制(混入) 使超類和子類不存在繼承關係
  • mixin作用就是爲類擴展功能,替代了abstract class
    • 替代abstract class 就說明dart 初期通過 abstract class來給類擴展功能 將共有行爲定義抽象方法 讓子類實現 (多態?)
  • mixin 方便維護 避免了超類子類構造函數參數傳遞實現超類功能只要混入超類重寫其中需要的功能方法,不需要完全繼承超類,
  • mixin 方便擴展 提高代碼複用 擴展功能只要定義好模塊直接混入即可 也不用指定功能屬於哪個超類 所有的類都可以混入

這是我的一點理解 肯定不全面 或者理解偏差 如果你看到這個文章我建議你跟着https://www.dartcn.com/articles/language/mixins這個連接去了解下 來自己理解一下 這個文章只是記錄了我看的時候的一些理解並不深刻可能有些是錯的 如果有不同看法 歡迎評論區大家一起討論

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章