2019-09-20: 九: Flutter之Dart第六節(類和對象)?

九: Flutter之Dart第六節(類和對象)?

Dart 是一個面向對象的語言、面向對象中非常重要的概念就是類、類產生了對象。
這一節,我們就具體來學習類和對象、但是Dart對類進行了很多其他語言沒有的特性、一起探討。

9.1: 類的定義

在Dart中、定義類用class關鍵字。
類通常有兩部分組成:成員(member) 和 方法 (method)。
定義類的偽代碼如下:

class 類名 {
   類型 成員名
   返回值類型 方法名(參數列表) {
     方法體
   }
 }

編寫一個簡單的Person類

  • 這里有一個注意點:我們在方法中使用屬性(成員變量/實例變量)時、并沒有加this;
  • Dart的開發風格中、在方法中通常使用屬性時、會省略this、但是有命名沖突時、this不能省略。
class Person {
 String name;

 eat() {
   print('$name在吃東西');
 }
}

main(List<String> args) {
 // 2: 我們在方法中使用屬性并沒有使用this、一般我們都是省略的、但是如果在方法中使用屬性的時候、如果有命名
 // 沖突的話、this不能省略

 // 1: 創建類的對象, 直接使用Person() 也可以創建
 var p = new Person();

 // 2: 給對象的屬性賦值
 p.name = 'lishengbing';

 // 3: 調用對象方法
 p.eat();

 //打印: lishengbing在吃東西
}

9.2: 構造方法

9.2.1: 普通構造方法

我們知道、當通過類創建一個對象時、會調用這個類的構造方法

  • 當類中沒有明確指定的構造方法時、將默認擁有一個無參的構造方法

  • 前面的Person中我們就是在調用這個構造方法、我們也可以根據自己的需求、定義自己的構造方法。

  • 注意1: 當有了自己的構造方法時、默認的構造方法將會失效、不能使用
    -- 當然、你可能希望明確的寫一個默認的構造方法、但是會和我們自定義的構造方法沖突
    -- 這是因為Dart語言本身不支持函數的重載(名稱相同、參數不同的方式)

  • 注意2: 這里我還實現了toString方法。

class Person {
 String name;
 int age;

 // 明確指定了構造方法、如果沒有指定默認是Person()
 Person(String name, int age) {
   this.name = name;
   this.age = age;
 }

 @override
 String toString() {
   return 'name = ${name}, age = ${age}';
 }
}

另外、在實現構造方法時、通常做的事情就是通過 **參數給屬性 **賦值,如上this.name = name
為了簡化了這一過程、Dart提供了一種更加簡潔的語法糖形式
上面的構造方法可以優化成下面的寫法:

// 明確指定了構造方法、如果沒有指定默認是Person()
 Person(String name, int age) {
   this.name = name;
   this.age = age;
 }

 // 等同于上面優化構造方法
 Person(this.name, this.age);

9.2.2: 命名構造方法

但是在開發中、我們確實希望實現更多的構造方法、怎么辦呢?

  • 因為不支持方法(函數)的重載、所以我們沒辦法創建相同名稱的構造方法。我們需要使用命名構造方法。
class People {
 String name;
 int age;

 People() {
  name = '';
   age = 0;
 }

 // 命名構造方法, withArgments可以隨便變化
 People.withArgments(String name, int age) {
   this.name = name;
   this.age = age;
 }

// 優化上面的構造方法
 //People.withArgments(this.name, this.age);

 @override
 String toString() {
     return 'name=${name} age=${age}';
  }
}

/*
* name= age=0
 name=lishengbing age=28
*/
main(List<String> args) {
 var p = new People();
 print(p);
 var p1 = new People.withArgments('lishengbing', 28);
 print(p1);
}

在之后的開發中、我們也可以利用命名構造方法、提供更加便捷的創建對象的方式

  • 比如開發中、我們需要經常將一個Map轉成對象、可以提供如下的構造方法
// 新的構造方法
 People.fromMap(Map<String, Object> map) {
   this.name = map['name'];
   this.age = map['age'];
 }
var p3 = new People.fromMap({'name': 'lishengbing', 'age': 29});
 // name=lishengbing age=29
 print(p3);

9.2.3: 初始化列表

我們來重新定義一個類Point、傳入x/y、可以得到它的距離distance:

初始化列表

class Point {
 final num x;
 final num y;
 final distance;

// 錯誤寫法

Point(this.x, this.y) {
  distance = sqrt(x * x + y * y);
}*/

 // 正確寫法
 Point(this.x, this.y) : distance = sqrt(x * x + y * y);

}

上面這種初始化變量的方法、我們稱之為初始化列表(Initializer list)

9.2.4: 重定向構造方法

在某些情況下、我們希望在一個構造方法中去調用另一個構造方法、這時候我們就可以使用重定向構造方法。

  • 在一個構造函數中、去調用另一個構造函數(注意:是在冒號后面使用this調用)
class Person01 {
 String name;
 int age;

 Person01(this.name, this.age);
 Person01.fromName(String name) : this('lishengbing', 30);
}

9.2.5: 常量構造方法

在某些時候下、傳入相同值時、我們希望返回同一個對象、這個時候、可以使用常量構造方法
默認情況下、創建對象時、即使傳入相同的參數、創建出來的對象也不是同一個對象,看下面的代碼:

  • 我們使用identical(對象1, 對象2)函數來判斷兩個對象是否是同一個對象
class Person02 {
 String name;
 Person02(this.name);
}

main(List<String> args) {
 var p1 = Person02('lishengbing');
 var p2 = Person02('lishengbing');

 // false
 print(identical(p1, p2));
}
  • 但是如果將構造方法前加上 const進行修飾的話、那么可以保證同一個參數、創建出來的對象是相同的。
    這樣的構造函數就叫做常量構造方法
class Person02 {
final  String name;
const  Person02(this.name);
}

main(List<String> args) {
 var p1 = const Person02('lishengbing');
 var p2 = const Person02('lishengbing');

 // true
 print(identical(p1, p2));
}
常量構造方法有一些注意點:
  • 注意一:擁有常量構造方法的類中、所有的成員變量必須是final學校修飾的、如final String name;
  • 注意二:為了可以使用常量構造方法、創建出相同的對象,不再使用new 關鍵字、而是使用const關鍵字
    -- 如果是將結果賦值給const修飾的標識符時、const可以省略。

9.2.5: 工廠構造方法

Dart 提供了factory關鍵字、用于通過工廠去獲取對象。
工廠獲取得到的對象傳入同一個參數、得到的是同一個對象
傳入不同的參數、獲取的對象不是同一個對象。

main(List<String> args) {
 var p1 = Person03('object');
 var p2 = Person03('object');

 // 工廠構造方法獲取對象比較=true
 print('工廠構造方法獲取對象比較=${identical(p1, p2)}');
}

class Person03 {
 String name;

 static final Map<String, Person03> _cache = <String, Person03>{};

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

 Person03._internal(this.name);

}

9.2.6: setting & getting

默認情況下、Dart中類定義的屬性是可以被外界直接訪問的
但是某些情況下、我們希望監控這個類的屬性被訪問的過程、這個時候就可以使用setting 和 getting了

main(List<String> args) {
 final d = Dog('黃色');

 //打印就是: dog color = 黑色
 d.setColor = '黑色';
 
 //打印就是: dog color = 紅色
 d.color = '紅色';
 print('dog color = ${d.getColor}');
}


class Dog {
 String color;

 String get getColor {
   return color;
 }

 set setColor(String color) {
   this.color = color;
 }

 Dog(this.color);
}

9.2.7: 類的繼承(僅支持單繼承、多繼承不可以)

1: 面向對象的其中一大特性就是繼承、繼承不僅可以減少我們的代碼量、也是多態的使用前提

2: dart 中的繼承使用extends 關鍵字、子類中使用super來訪問父類;

3: 父類中的所有成員變量和方法都會被繼承、但是構造方法除外.

main(List<String> args) {
 var p = new Person04();
 p.age = 10;
 p.run();
 /// 奔跑ing
 /// 繼承=10
 print('繼承=${p.age}');
}

class Animal {
 int age;
 run() {
  print('奔跑ing');
 }
}

class Person04 extends Animal {

}
9.2.7(1): 子類可以擁有自己的成員變量、并且可以對父類的方法進行重寫
class Person04 extends Animal {
 @override
 run() {
  // TODO: implement run
   1: 如果子類重寫了父類的方法,打印父類的方法
   //我是Person04類的重寫run方法
   //繼承=10
   print('我是Person04類的重寫run方法');
 }
}
9.2.7(2): 子類中可以調用父類的構造方法、對某些屬性進行初始化
  • 子類的構造方法在執行前, 將隱式調用父類的默認的無參數的構造方法(沒有參數且類同名的構造方法)
  • 如果父類沒有無參默認構造方法、則子類的構造方法必須在初始化列表中通過super顯式調用父類的某個構造方法。
class Animal {
 int age;
 run() {
   print('奔跑ing');
 }

 // 只要這樣寫、該類就沒有了無參默認構造函數
 Animal(this.age);
}

class Person04 extends Animal {

 String name;

 Person04(String name, int age) : name=name, super(age);

 @override
 run() {
   // TODO: implement run
   // 1: 如果子類重寫了父類的方法,打印父類的方法
   // 我是Person04類的重寫run方法
   // 繼承=10
   print('我是Person04類的重寫run方法');
 }

 @override
 String toString() {
     print('toString-');
  }
}

9.2.8: 抽象類

我們知道、繼承是多態使用的前提
所以在定義很多通用的通用接口時我們通常會讓調用者傳入父類、通過多態類實現更加靈活的調用方式。

但是、父類本身可能并不需要對某些方法進行具體的實現、所以父類中定義的方法、我們可以定義為抽象類。

什么是抽象方法?在Dart中沒有具體實現的方法(沒有方法體)、就是抽象方法。

  • 抽象方法、必須存在于抽象類中。
  • 抽象類是使用abstract 聲明的類。
    下面這個Shape類就是一個抽象類、其中包含了一個抽象方法
abstract class Shape {
  getArea();
}

class Circle extends Shape {

  double r;
  Circle(this.r);

  @override
  getArea() {
    // TODO: implement getArea
    return r * r * 3.14;
  }
}

class Reactangle extends Shape {

  double w;
  double h;
  Reactangle(this.w, this.h);

  @override
  getArea() {
    // TODO: implement getArea
    return w * h;
  }
}
注意事項:
  • 注意一:** 抽象類中不能實例化;
  • 注意二:** 抽象類中的抽象方法必須被子類實現、抽象類中的已經被實現方法、可以不被子類重寫;

9.2.9: 隱式接口

Dart中的接口比較特殊、沒有一個專門的關鍵字來聲明接口
默認情況下、定義的每一個類都相當于默認也聲明了一個接口、可以由其他的類來實現(因為Dart不支持多繼承)

在開發中、我們通常將用于給別人來實現的類聲明為抽象類:

abstract class Runner {
  run();
}

abstract class Flyer {
  fly();
}

class SuperMan implements Runner, Flyer {
  @override
  run() {
    // TODO: implement run
    print('超人在跑');
  }

  @override
  fly() {
    // TODO: implement fly
    print('超人在飛');
  }
}

9.2.10: Mixin混入

在通過implements實現某個類時、類中所有的方法都必須被重載實現(無論這個類原來是否已經實現過該方法)

但是在某些情況下、一個類可能希望直接復用之前類的原有實現方案、怎么做呢?

  • 使用繼承嗎?但是Dart只支持單繼承、那么意味著你只能復用一個類的實現

Dart提供了另外一種方案:Mixin混入的方式

  • 除了可以class 定義類之外、也可以通過mixin關鍵字來定義一個類
  • 只是通過mixin定義的類用于被其他類混入使用、通過with關鍵字來進行混入。
main(List<String> args) {
  var superMan = SuperMain();
  superMan.run();
  superMan.fly();
  /*
  在奔跑...
  在飛翔... 
  */
}

mixin Runner1 {
  run() {
    print('在奔跑...');
  }
}

mixin Flyer1 {
  fly() {
    print('在飛翔...');
  }
}

/// implements的方式必須要求對其中的方法進行重新實現
class SuperMain0 implements Runner1, Flyer1 {

  @override
  run() {
    // TODO: implement run
    return null;
  }

  @override
  fly() {
    // TODO: implement fly
    return null;
  }
}

class SuperMain with Runner1, Flyer1 {

}

9.2.11: 類成員和方法

前面我們在類中定義的成員和方法都屬于對象級別的、在開發中、我們有時候也需要定義類級別的成員和方法

在Dart中我們使用static關鍵字類定義:

main(List<String> args) {
  var stu = Student();
  stu.name = 'lishengbing';
  stu.age = 66;
  stu.study();

  Student.time = '早上10點';
  Student.attendClass();
  // lishengbing在學習
  // 去上課
}

class Student {
  String name;
  int age;

  static String time;

  study() {
    print('$name在學習');
  }

  static attendClass() {
    print('去上課');
  }
}

9.2.12: 枚舉的類型

枚舉在開發中也非常常見、枚舉也是一種特殊的類、通常用于表示數量的常量值。

1: 枚舉的定義

枚舉使用enum關鍵字來進行定義

// 1: 枚舉的定義

main(List<String> args) {
  // Colors.red
  print(Colors.red);
  // values, [Colors.red, Colors.green, Colors.blue]
  print(Colors.values);
  // index, 0
  print(Colors.red.index);
}

enum Colors {
  red,
  green,
  blue
}
2: 枚舉的屬性

枚舉類型中有兩個比較常見的屬性

  • index: 用于表示每個枚舉常量的索引、從0開始
    如:print(Colors.red.index);
  • values:包含每個枚舉值的List
    如:print(Colors.values);
3: 枚舉的注意事項
  • 注意1: 您不能子類化、混合或者實現枚舉。
  • 注意2: 不能顯式實例化一個枚舉。

9.3: 泛型

1: 為什么使用范型?

......

2: List和Map的范型
main(List<String> args) {
// 1: 創建List的方式
var name1 = ['lishengbing', 'zhangsan', 'wangxiao', 111];
// List<String>
// List<Object>
print(name1.runtimeType);

// 限制類型
var name2 = <String>['1', '2', '3'];
// List<String>
print(name2.runtimeType);



// 2: Map使用泛型的寫法
var info1 = {'name': 'li', 1: 'one', 'age': 20};
// _InternalLinkedHashMap<Object, Object>
print(info1.runtimeType);


// 2: 對類型進行限制
Map<String, String> info2 = {'name': 'li', 'age': '20'};
// 限制寫法1
// info2=_InternalLinkedHashMap<String, String>
print('info2=${info2.runtimeType}');

var info3 = <String, String>{'name': 'li', 'age': '20'};
// 限制寫法2
// info3=_InternalLinkedHashMap<String, String>
print('info3=${info3.runtimeType}');

}
3: 類定義的泛型

如果我們需要定義一個類、用于存儲位置信息Location、但是并不確定使用者使用的是int類型、還是double類型、甚至是一個字符串類型、這個時候該怎么定義呢?

  • 一種方案是使用Object類型、但是在之后使用時候非常不方便
  • 另一種方案就是使用泛型
    Location類的定義:Object類 || 泛型方式
main13(List<String> args) {
  Location l = Location(10, 20);
  /// int
  print(l.x.runtimeType);
}

class Location {
  Object x;
  Object y;

  Location(this.x, this.y);
}


main(List<String> args) {
  Location2 l1 = Location2<int>(10, 30);
  // Location2<int>
  print('int-l1=${l1.runtimeType}');

  Location2 l2 = Location2<String>('30', '40');
  // Location2<String>
  print('String-l2=${l2.runtimeType}');
}

// 泛型寫法
class Location2<T> {
  T x;
  T y;

  Location2(this.x, this.y);
}

  • 如果我們希望類型只能是num類型、怎么做呢?
// 如果希望類型只能是num類型


main(List<String> args) {
  Location3 l3 = Location3(10, 20);
  // Location3<num>
  print(l3.runtimeType);
}


class Location3<T extends num> {
  T x;
  T y;

  Location3(this.x, this.y);
}
4: 泛型方法的定義

最初、Dart僅僅在類中支持泛型、后來一種稱之為泛型方法的新語法允許在方法和函數中使用類型參數。

main(List<String> args) {
  var names = ['1', '2'];
  var first = getFirst(names);
  /// first = 1, type=String
  print('first = ${first}, type=${first.runtimeType}');
}

T getFirst<T>(List<T> ts) {
    return ts[0];
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容