做者 | 弗拉德
來源 | 弗拉德(公衆號: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
方法來追蹤和記錄這一行爲:
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; } }
構造函數是不能被繼承的,這將意味着子類不能繼承父類的命名式構造函數,若是你想在子類中提供一個與父類命名構造函數名字同樣的命名構造函數,則須要在子類中顯式地聲明。
默認狀況下,子類的構造函數會調用父類的匿名無參數構造方法,而且該調用會在子類構造函數的函數體代碼執行前,若是子類構造函數還有一個初始化列表,那麼該初始化列表會在調用父類的該構造函數以前被執行,總的來講,這三者的調用順序以下:
若是父類沒有匿名無參數構造函數,那麼子類必須調用父類的其中一個構造函數,爲子類的構造函數指定一個父類的構造函數只需在構造函數體前使用:
指定。
由於參數會在子類構造函數被執行前傳遞給父類的構造函數,所以該參數也能夠是一個表達式,好比一個函數:
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
是一種在多重繼承中複用某個類中代碼的方法模式。
使用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
類,好比有 Mixin
類 A
,可是A
只能被B
類使用,則能夠這樣定義 `A:
class Musician { // ... } mixin MusicalPerformer on Musician { // ... } class SingerDancer extends Musician with MusicalPerformer { // ... }
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.