本文首發於個人部落格
類和物件
Dart是一個物件導向的語言,物件導向中非常重要的概念就是類,類產生了物件。
類的定義
在Dart中,定義類用class關鍵字。 類通常有兩部分組成:成員(member)和方法(method)。 定義類的虛擬碼如下:
class 類名 {
型別 成員名;
返回值型別 方法名(引數列表) {
方法體
}
}
複製程式碼
編寫一個簡單的Person類:
- 這裡有一個注意點: 我們在方法中使用屬性(成員/例項變數)時,並沒有加this;
- Dart的開發風格中,在方法中通常使用屬性時,會省略this,但是有命名衝突時,this不能省略;
class Person {
String name;
eat() {
print('$name在吃東西');
}
}
複製程式碼
我們來使用這個類,建立對應的物件:
- 注意:從Dart2開始,new關鍵字可以省略。
main(List<String> args) {
// 1.建立類的物件
var p = new Person(); // 直接使用Person()也可以建立
// 2.給物件的屬性賦值
p.name = 'eagle';
// 3.呼叫物件的方法
p.eat();
}
複製程式碼
構造方法
普通構造方法
我們知道, 當通過類建立一個物件時,會呼叫這個類的構造方法。
- 當類中沒有明確指定構造方法時,將預設擁有一個無參的構造方法。
- 前面的Person中我們就是在呼叫這個構造方法.
我們也可以根據自己的需求,定義自己的構造方法:
**注意一:**當有了自己的構造方法時,預設的構造方法將會失效,不能使用
- 當然,你可能希望明確的寫一個預設的構造方法,但是會和我們自定義的構造方法衝突;
- 這是因為Dart本身不支援函式的過載(名稱相同, 引數不同的方式)。
**注意二:**這裡我還實現了toString方法
class Person {
String name;
int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
@override
String toString() {
return 'name=$name age=$age';
}
}
複製程式碼
另外,在實現構造方法時,通常做的事情就是通過引數給屬性賦值 為了簡化這一過程, Dart提供了一種更加簡潔的語法糖形式. 上面的構造方法可以優化成下面的寫法:
Person(String name, int age) {
this.name = name;
this.age = age;
}
// 等同於
Person(this.name, this.age);
複製程式碼
命名構造方法
但是在開發中, 我們確實希望實現更多的構造方法,怎麼辦呢?
因為不支援方法(函式)的過載,所以我們沒辦法建立相同名稱的構造方法。
我們需要使用命名構造方法:
class Person {
String name;
int age;
Person() {
name = '';
age = 0;
}
// 命名構造方法
Person.withArgments(String name, int age) {
this.name = name;
this.age = age;
}
@override
String toString() {
return 'name=$name age=$age';
}
}
// 建立物件
var p1 = new Person();
print(p1);
var p2 = new Person.withArgments('eagle', 18);
print(p2);
複製程式碼
在之後的開發中, 我們也可以利用命名構造方法,提供更加便捷的建立物件方式:
比如開發中,我們需要經常將一個Map轉成物件,可以提供如下的構造方法
// 新的構造方法
Person.fromMap(Map<String, Object> map) {
this.name = map['name'];
this.age = map['age'];
}
// 通過上面的構造方法建立物件
var p3 = new Person.fromMap({'name': 'kobe', 'age': 30});
print(p3);
複製程式碼
初始化列表
我們來重新定義一個類Point, 傳入x/y,可以得到它們的距離distance:
class Point {
final num x;
final num y;
final num 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)
重定向構造方法
在某些情況下, 我們希望在一個構造方法中去呼叫另外一個構造方法, 這個時候可以使用重定向構造方法:
在一個建構函式中,去呼叫另外一個建構函式(注意:是在冒號後面使用this呼叫)
class Person {
String name;
int age;
Person(this.name, this.age);
Person.fromName(String name) : this(name, 0);
}
複製程式碼
常量構造方法
在某些情況下,傳入相同值時,我們希望返回同一個物件,這個時候,可以使用常量構造方法. 預設情況下,建立物件時,即使傳入相同的引數,建立出來的也不是同一個物件,看下面程式碼:
這裡我們使用identical(物件1, 物件2)函式來判斷兩個物件是否是同一個物件:
main(List<String> args) {
var p1 = Person('eagle');
var p2 = Person('eagle');
print(identical(p1, p2)); // false
}
class Person {
String name;
Person(this.name);
}
複製程式碼
但是, 如果將構造方法前加const進行修飾,那麼可以保證同一個引數,建立出來的物件是相同的
這樣的構造方法就稱之為常量構造方法。
main(List<String> args) {
var p1 = const Person('eagle');
var p2 = const Person('eagle');
print(identical(p1, p2)); // true
}
class Person {
final String name;
const Person(this.name);
}
複製程式碼
常量構造方法有一些注意點:
- 注意一:擁有常量構造方法的類中,所有的成員變數必須是final修飾的.
- 注意二: 為了可以通過常量構造方法,建立出相同的物件,不再使用 new關鍵字,而是使用const關鍵字
如果是將結果賦值給const修飾的識別符號時,const可以省略.
工廠構造方法
Dart提供了factory關鍵字, 用於通過工廠去獲取物件
main(List<String> args) {
var p1 = Person('eagle');
var p2 = Person('eagle');
print(identical(p1, p2)); // true
}
class Person {
String name;
static final Map<String, Person> _cache = <String, Person>{};
factory Person(String name) {
if (_cache.containsKey(name)) {
return _cache[name];
} else {
final p = Person._internal(name);
_cache[name] = p;
return p;
}
}
Person._internal(this.name);
}
複製程式碼
setter和getter
預設情況下,Dart中類定義的屬性是可以直接被外界訪問的。 但是某些情況下,我們希望監控這個類的屬性被訪問的過程,這個時候就可以使用setter和getter了
main(List<String> args) {
final d = Dog("黃色");
d.setColor = "黑色";
print(d.getColor);
}
class Dog {
String color;
String get getColor {
return color;
}
set setColor(String color) {
this.color = color;
}
Dog(this.color);
}
複製程式碼
類的繼承
物件導向的其中一大特性就是繼承,繼承不僅僅可以減少我們的程式碼量,也是多型的使用前提。 Dart中的繼承使用extends關鍵字,子類中使用super來訪問父類。 父類中的所有成員變數和方法都會被繼承,,但是構造方法除外。
main(List<String> args) {
var p = new Person();
p.age = 18;
p.run();
print(p.age);
}
class Animal {
int age;
run() {
print('在奔跑ing');
}
}
class Person extends Animal {
}
複製程式碼
子類可以擁有自己的成員變數, 並且可以對父類的方法進行重寫:
class Person extends Animal {
String name;
@override
run() {
print('$name在奔跑ing');
}
}
複製程式碼
子類中可以呼叫父類的構造方法,對某些屬性進行初始化:
子類的構造方法在執行前,將隱含呼叫父類的無參預設構造方法(沒有引數且與類同名的構造方法)。 如果父類沒有無參預設構造方法,則子類的構造方法必須在初始化列表中通過super顯式呼叫父類的某個構造方法。
class Animal {
int age;
Animal(this.age);
run() {
print('在奔跑ing');
}
}
class Person extends Animal {
String name;
Person(String name, int age) : name=name, super(age);
@override
run() {
print('$name在奔跑ing');
}
@override
String toString() {
return 'name=$name, age=$age';
}
}
複製程式碼
抽象類
我們知道,繼承是多型使用的前提。 所以在定義很多通用的呼叫介面時, 我們通常會讓呼叫者傳入父類,通過多型來實現更加靈活的呼叫方式。 但是,父類本身可能並不需要對某些方法進行具體的實現,所以父類中定義的方法,,我們可以定義為抽象方法。 什麼是 抽象方法? 在Dart中沒有具體實現的方法(沒有方法體),就是抽象方法。
抽象方法,必須存在於抽象類中。 抽象類是使用abstract宣告的類。
下面的程式碼中, Shape類就是一個抽象類, 其中包含一個抽象方法.
abstract class Shape {
getArea();
}
class Circle extends Shape {
double r;
Circle(this.r);
@override
getArea() {
return r * r * 3.14;
}
}
class Reactangle extends Shape {
double w;
double h;
Reactangle(this.w, this.h);
@override
getArea() {
return w * h;
}
}
複製程式碼
注意事項:
**注意一:**抽象類不能例項化. **注意二:**抽象類中的抽象方法必須被子類實現, 抽象類中的已經被實現方法, 可以不被子類重寫.
隱式介面
Dart中的介面比較特殊, 沒有一個專門的關鍵字來宣告介面. 預設情況下,定義的每個類都相當於預設也宣告瞭一個介面,可以由其他的類來實現(因為Dart不支援多繼承) 在開發中,我們通常將用於給別人實現的類宣告為抽象類:
abstract class Runner {
run();
}
abstract class Flyer {
fly();
}
class SuperMan implements Runner, Flyer {
@override
run() {
print('超人在奔跑');
}
@override
fly() {
print('超人在飛');
}
}
複製程式碼
Mixin混入
在通過implements實現某個類時,類中所有的方法都必須被重新實現(無論這個類原來是否已經實現過該方法)。 但是某些情況下,一個類可能希望直接複用之前類的原有實現方案,怎麼做呢?
使用繼承嗎?但是Dart只支援單繼承,那麼意味著你只能複用一個類的實現。
Dart提供了另外一種方案: Mixin混入的方式
除了可以通過class定義類之外,也可以通過mixin關鍵字來定義一個類。 只是通過mixin定義的類用於被其他類混入使用,通過with關鍵字來進行混入。
main(List<String> args) {
var superMan = SuperMain();
superMan.run();
superMan.fly();
}
mixin Runner {
run() {
print('在奔跑');
}
}
mixin Flyer {
fly() {
print('在飛翔');
}
}
// implements的方式要求必須對其中的方法進行重新實現
// class SuperMan implements Runner, Flyer {}
class SuperMain with Runner, Flyer {
}
複製程式碼
類成員和方法
前面我們在類中定義的成員和方法都屬於物件級別的, 在開發中, 我們有時候也需要定義類級別的成員和方法 在Dart中我們使用static關鍵字來定義:
main(List<String> args) {
var stu = Student();
stu.name = 'eagle';
stu.sno = 110;
stu.study();
Student.time = '早上8點';
// stu.time = '早上9點'; 錯誤做法, 例項物件不能訪問類成員
Student.attendClass();
// stu.attendClass(); 錯誤做法, 實現物件補鞥呢訪問類方法
}
class Student {
String name;
int sno;
static String time;
study() {
print('$name在學習');
}
static attendClass() {
print('去上課');
}
}
複製程式碼
列舉型別
列舉在開發中也非常常見, 列舉也是一種特殊的類, 通常用於表示固定數量的常量值。
列舉的定義
列舉使用enum關鍵字來進行定義:
main(List<String> args) {
print(Colors.red);
}
enum Colors {
red,
green,
blue
}
複製程式碼
列舉的屬性
列舉型別中有兩個比較常見的屬性:
index: 用於表示每個列舉常量的索引, 從0開始. values: 包含每個列舉值的List.
main(List<String> args) {
print(Colors.red.index);
print(Colors.green.index);
print(Colors.blue.index);
print(Colors.values);
}
enum Colors {
red,
green,
blue
}
複製程式碼
列舉型別的注意事項:
- 注意一: 您不能子類化、混合或實現列舉。
- 注意二: 不能顯式例項化一個列舉