【Flutter 1-11】Flutter手把手教程Dart語言——類、類的的成員變量和方法、類的構

做者 | 弗拉德
來源 | 弗拉德(公衆號:fulade_me)編程

Dart是一種面向對象的語言,全部對象都是一個類的實例,而全部的類都繼承自Object類。每一個除了Object類以外的類都只有一個超類,一個類的代碼能夠在其它多個類繼承中重複使用。json

類的實例變量

下面是聲明實例變量的示例:api

class Point {
  double x; // 聲明 double 變量 x 並初始化爲 null。
  double y; // 聲明 double 變量 y 並初始化爲 null
  double z = 0; // 聲明 double 變量 z 並初始化爲 0。
}

全部未初始化的實例變量其值均爲null緩存

全部實例變量均會隱式地聲明一個Getter 方法,非final類型的實例變量還會隱式地聲明一個Setter方法編程語言

class Point {
  double x;
  double y;
}

void main() {
  var point = Point();
  point.x = 4; // 使用 x 的 Setter 方法。
  assert(point.x == 4); // 使用 x 的 Getter 方法。
  assert(point.y == null); // 默認值爲 null。
}

若是你在聲明一個實例變量的時候就將其初始化(而不是在構造函數或其它方法中),那麼該實例變量的值就會在對象實例建立的時候被設置,該過程會在構造函數以及它的初始化器列表執行前。ide

訪問類的成員

對象的成員由函數和數據(即方法和實例變量)組成。方法的調用要經過對象來完成,這種方式能夠訪問對象的函數和數據。
使用.來訪問對象的實例變量或方法:函數

var p = Point(2, 2);
// 獲取 y 值
assert(p.y == 2);
// 調用變量 p 的 distanceTo() 方法。
double distance = p.distanceTo(Point(4, 4));

使用 ?. 代替.能夠避免由於左邊表達式爲null 而致使的問題:post

// If p is non-null, set a variable equal to its y value.
var a = p?.y;
方法

方法是對象提供行爲的函數。this

對象的實例方法能夠訪問實例變量和this。下面的distanceTo()方法就是一個實例方法的例子:code

import 'dart:math';

class Point {
  double x, y;

  Point(this.x, this.y);

  double distanceTo(Point other) {
    var dx = x - other.x;
    var dy = y - other.y;
    return sqrt(dx * dx + dy * dy);
  }
}
重寫類成員

子類能夠重寫父類的實例方法(包括操做符)、 Getter 以及 Setter 方法。你可使用 @override註解來表示你重寫了一個成員:

class SmartTelevision extends Television {
  @override
  void turnOn() {...}
  // ···
}
noSuchMethod 方法

若是調用了對象中不存在的方法或實例變量將會觸發noSuchMethod 方法,你能夠重寫noSuchMethod方法來追蹤和記錄這一行爲:

class A {
  // 除非你重寫 noSuchMethod,不然調用一個不存在的成員會致使 NoSuchMethodError。
  @override
  void noSuchMethod(Invocation invocation) {
    print('你嘗試使用一個不存在的成員:' + '${invocation.memberName}');
  }
}

你不能調用一個未實現的方法除非知足下面其中的一個條件:

  • 接收方是靜態的dynamic 類型。
  • 接收方具備靜態類型,定義了未實現的方法,而且接收方的動態類型實現了noSuchMethod方法且具體的實現與Object中的不一樣。

類的靜態變量和靜態方法

使用關鍵字static能夠聲明類變量或類方法。

靜態變量

靜態變量(即類變量)經常使用於聲明類範圍內所屬的狀態變量和常量:

class Queue {
  static const initialCapacity = 16;
  // ···
}
void main() {
  assert(Queue.initialCapacity == 16);
}

靜態變量在其首次被使用的時候才被初始化。

靜態方法

靜態方法(即類方法)不能被一個類的實例訪問,一樣地,靜態方法內也不可使用關鍵字this

import 'dart:math';
class Point {
  double x, y;
  Point(this.x, this.y);
  static double distanceBetween(Point a, Point b) {
    var dx = a.x - b.x;
    var dy = a.y - b.y;
    return sqrt(dx * dx + dy * dy);
  }
}

void main() {
  var a = Point(2, 2);
  var b = Point(4, 4);
  var distance = Point.distanceBetween(a, b);
  assert(2.8 < distance && distance < 2.9);
  print(distance);
}

使用構造函數構建對象

可使用構造函數來建立一個對象。構造函數的命名方式能夠爲類名或類名.標識符的形式。例以下述代碼分別使用Point()Point.fromJson()兩種構造器來建立Point對象:

var p1 = Point(2, 2);
var p2 = Point.fromJson({'x': 1, 'y': 2});

如下代碼具備相同的效果,構造函數名前面的的new關鍵字是可選的:

var p1 = new Point(2, 2);
var p2 = new Point.fromJson({'x': 1, 'y': 2});

一些類提供了常量構造函數。使用常量構造函數,在構造函數名以前加 const關鍵字,來建立編譯時常量時:

var p = const ImmutablePoint(2, 2);

兩個使用相同構造函數相同參數值構造的編譯時常量是同一個對象:

var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);

assert(identical(a, b)); // 它們是同一個實例 (They are the same instance!)

根據使用常量上下文的場景,你能夠省略掉構造函數或字面量前的const 關鍵字。例以下面的例子中咱們建立了一個常量Map

// 這裏有不少 const 關鍵字
const pointAndLine = const {
  'point': const [const ImmutablePoint(0, 0)],
  'line': const [const ImmutablePoint(1, 10), const ImmutablePoint(-2, 11)],
};

根據上下文,你能夠只保留第一個const關鍵字,其他的所有省略:

// 只須要一個 const 關鍵字,其它的則會隱式地根據上下文進行關聯。
const pointAndLine = {
  'point': [ImmutablePoint(0, 0)],
  'line': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)],
};

可是若是沒法根據上下文判斷是否能夠省略const,則不能省略掉 const關鍵字,不然將會建立一個常量對象 例如:

var a = const ImmutablePoint(1, 1); // 建立一個常量 (Creates a constant)
var b = ImmutablePoint(1, 1); // 不會建立一個常量
assert(!identical(a, b)); // 這兩變量並不相同

獲取對象的類型

可使用Object對象的runtimeType屬性在運行時獲取一個對象的類型,該對象類型是Type的實例。

var = Point(2, 2);
print('The type of a is ${a.runtimeType}');

構造函數

聲明一個與類名同樣的函數便可聲明一個構造函數(對於命名式構造函數 還能夠添加額外的標識符)。大部分的構造函數形式是生成式構造函數,其用於建立一個類的實例:

class Point {
  double x, y;
  Point(double x, double y) {
    // 還會有更好的方式來實現此邏輯,敬請期待。
    this.x = x;
    this.y = y;
  }
}

使用 this 關鍵字引用當前實例。
對於大多數編程語言來講在構造函數中爲實例變量賦值的過程都是相似的,而Dart則提供了一種特殊的語法糖來簡化該步驟:

class Point {
  double x, y;
  // 在構造函數體執行前用於設置 x 和 y 的語法糖。
  Point(this.x, this.y);
}
默認構造函數

若是你沒有聲明構造函數,那麼Dart會自動生成一個無參數的構造函數而且該構造函數會調用其父類的無參數構造方法。

構造函數不被繼承

子類不會繼承父類的構造函數,若是子類沒有聲明構造函數,那麼只會有一個默認無參數的構造函數。

命名式構造函數

能夠爲一個類聲明多個命名式構造函數來表達更明確的意圖:

class Point {
  double x, y;

  Point(this.x, this.y);

  // 命名式構造函數
  Point.origin() {
    x = 0;
    y = 0;
  }
}

構造函數是不能被繼承的,這將意味着子類不能繼承父類的命名式構造函數,若是你想在子類中提供一個與父類命名構造函數名字同樣的命名構造函數,則須要在子類中顯式地聲明。

調用父類非默認構造函數

默認狀況下,子類的構造函數會調用父類的匿名無參數構造方法,而且該調用會在子類構造函數的函數體代碼執行前,若是子類構造函數還有一個初始化列表,那麼該初始化列表會在調用父類的該構造函數以前被執行,總的來講,這三者的調用順序以下:

  1. 初始化列表
  2. 父類的無參數構造函數
  3. 當前類的構造函數

若是父類沒有匿名無參數構造函數,那麼子類必須調用父類的其中一個構造函數,爲子類的構造函數指定一個父類的構造函數只需在構造函數體前使用:指定。

由於參數會在子類構造函數被執行前傳遞給父類的構造函數,所以該參數也能夠是一個表達式,好比一個函數:

class Employee extends Person {
  Employee() : super.fromJson(defaultData);
  // ···
}
初始化列表

除了調用父類構造函數以外,還能夠在構造函數體執行以前初始化實例變量。每一個實例變量之間使用逗號分隔。

// 使用初始化列表在構造函數體執行前設置實例變量。
Point.fromJson(Map<String, double> json)
    : x = json['x'],
      y = json['y'] {
  print('In Point.fromJson(): ($x, $y)');
}

在開發模式下,你能夠在初始化列表中使用assert來驗證輸入數據:

Point.withAssert(this.x, this.y) : assert(x >= 0) {
  print('In Point.withAssert(): ($x, $y)');
}
重定向構造函數

有時候類中的構造函數會調用類中其它的構造函數,該重定向構造函數沒有函數體,只需在函數簽名後使用:指定須要重定向到的其它構造函數便可:

class Point {
  double x, y;

  // 該類的主構造函數。
  Point(this.x, this.y);

  // 委託實現給主構造函數。
  Point.alongXAxis(double x) : this(x, 0);
}
常量構造函數

若是類生成的對象都是不會變的,那麼能夠在生成這些對象時就將其變爲編譯時常量。你能夠在類的構造函數前加上const關鍵字並確保全部實例變量均爲final來實現該功能。

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

  final double x, y;

  const ImmutablePoint(this.x, this.y);
}

常量構造函數建立的實例並不老是常量。

工廠構造函數

使用factory關鍵字標識類的構造函數將會令該構造函數變爲工廠構造函數,這將意味着使用該構造函數構造類的實例時並不是老是會返回新的實例對象。例如,工廠構造函數可能會從緩存中返回一個實例,或者返回一個子類型的實例。

在以下的示例中,Logger的工廠構造函數從緩存中返回對象,和 Logger.fromJson工廠構造函數從JSON對象中初始化一個最終變量。

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

  // _cache 變量是庫私有的,由於在其名字前面有下劃線。
  static final Map<String, Logger> _cache =
      <String, Logger>{};

  factory Logger(String name) {
    return _cache.putIfAbsent(
        name, () => Logger._internal(name));
  }

  factory Logger.fromJson(Map<String, Object> json) {
    return Logger(json['name'].toString());
  }

  Logger._internal(this.name);

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

工廠構造函的調用方式與其餘構造函數同樣:

var logger = Logger('UI');
logger.log('Button clicked');

var logMap = {'name': 'UI'};
var loggerJson = Logger.fromJson(logMap);

抽象類

使用關鍵字abstract 標識類可讓該類成爲抽象類,抽象類將沒法被實例化。抽象類經常使用於聲明接口方法、有時也會有具體的方法實現。若是想讓抽象類同時可被實例化,能夠爲其定義工廠構造函數。

抽象類經常會包含抽象方法。下面是一個聲明具備抽象方法的抽象類示例:

// 該類被聲明爲抽象的,所以它不能被實例化。
abstract class AbstractContainer {
  // 定義構造函數、字段、方法等……
  void updateChildren(); // 抽象方法。
}

隱式接口

每個類都隱式地定義了一個接口並實現了該接口,這個接口包含全部這個類的實例成員以及這個類所實現的其它接口。若是想要建立一個A類支持調用B類的API且不想繼承B類,則能夠實現B類的接口。

一個類能夠經過關鍵字implements來實現一個或多個接口並實現每一個接口定義的 API:

// Person 類的隱式接口中包含 greet() 方法。
class Person {
  // _name 變量一樣包含在接口中,但它只是庫內可見的。
  final _name;

  // 構造函數不在接口中。
  Person(this._name);

  // greet() 方法在接口中。
  String greet(String who) => '你好,$who。我是$_name。';
}

// Person 接口的一個實現。
class Impostor implements Person {
  get _name => '';

  String greet(String who) => '你好$who。你知道我是誰嗎?';
}

String greetBob(Person person) => person.greet('小芳');

void main() {
  print(greetBob(Person('小芸')));
  print(greetBob(Impostor()));
}

若是須要實現多個類接口,可使用逗號分割每一個接口類:

class Point implements Comparable, Location {

}

擴展一個類

使用extends關鍵字來建立一個子類,並可以使用super關鍵字引用一個父類:

class Television {
  void turnOn() {
    _illuminateDisplay();
    _activateIrSensor();
  }
}
class SmartTelevision extends Television {
  void turnOn() {
    super.turnOn();
    _bootNetworkInterface();
    _initializeMemory();
    _upgradeApps();
  }
}

使用 Mixin 爲類添加功能

Mixin是一種在多重繼承中複用某個類中代碼的方法模式。
使用with關鍵字並在其後跟上Mixin類的名字來使用Mixin模式:

class Musician extends Performer with Musical {
  // ···
}

class Maestro extends Person
    with Musical, Aggressive, Demented {
  Maestro(String maestroName) {
    name = maestroName;
    canConduct = true;
  }
}

定義一個類繼承自Object而且不爲該類定義構造函數,這個類就是 Mixin類,除非你想讓該類與普通的類同樣能夠被正常地使用,不然可使用關鍵字mixin替代class讓其成爲一個單純的Mixin類:

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類,好比有 MixinA,可是A只能被B類使用,則能夠這樣定義 `A:

class Musician {
  // ...
}
mixin MusicalPerformer on Musician {
  // ...
}
class SingerDancer extends Musician with MusicalPerformer {
  // ...
}

Extension 方法

Dart 2.7 中引入的Extension方法是向現有庫添加功能的一種方式。
這裏是一個在 String 中使用 extension方法的樣例,咱們取名爲 parseInt(),它在 string_apis.dart 中定義:

extension NumberParsing on String {
  int parseInt() {
    return int.parse(this);
  }

  double parseDouble() {
    return double.parse(this);
  }
}

而後使用string_apis.dart裏面的parseInt()方法

import 'string_apis.dart';
print('42'.padLeft(5)); // Use a String method.
print('42'.parseInt()); // Use an extension method.

公衆號

相關文章
相關標籤/搜索