最近時間太忙,天天加班加點,但是我們的學習的行為不能落下。
小夥伴們繼續跟著我一起來學習40分鐘快速入門Dart基礎(中)
鎮樓目錄:
在本章我們主要跟大家聊聊方法和類。這兩大章看似不多,其實也不少,不過小夥伴們不用擔心,只要大家按照我下面的程式碼敲一遍,保證能掌握大部分知識點!
好廢話不多說,直接開幹。
一、一等方法物件
Dart 是一個真正的面嚮物件語言,方法也是物件並且具有一種 型別 Function。 這意味著,方法可以賦值給變數,也可以當做其他方法的引數,同時也可以把方法當做引數呼叫另外一個方法
import 'dart:core';
void main() {
var list = ["黃藥師", "郭靖", "小龍女"];
void printElement(element) {
print(element);
}
list.forEach(printElement);
}
複製程式碼
在Java中如果需要能夠通知呼叫者或者其他地方方法執行過程的各種情況,可能需要指定一個介面,其實就是我們說的回撥函式。比如View的onClickListener。而在Dart中,我們可以直接指定一個回撥方法給呼叫的方法,由呼叫的方法在合適的時機執行這個回撥。
void setListener(Function listener){
listener("Success");
}
//或者
void setListener(void listener(String result)){
listener("Success");
}
//兩種方式,第一種呼叫者根本不確定 回撥函式的返回值、引數是些什麼
//第二中則需要寫這麼一大段 太麻煩了。
//第三種:型別定義 將返回值為voide,引數為一個String的方法定義為一個型別。
typedef void Listener(String result);
void setListener(Listener listener){
listener("Success");
}
複製程式碼
在Dart 中方法可以有兩種型別的引數:必需的和可選的。 必需的引數需要在引數列表前面, 後面再定義可選引數。
二、可選命名引數
什麼叫可選命名函式,其實說白了就是把方法的引數放到 {} 中就變成了可選命名引數。
import 'dart:core';
void main() {
int add({int i, int j}) {
if (i == null || j == null) {
return 0;
}
return i + j;
}
//全參呼叫
print("--1--${add(i: 10, j: 20)}"); //輸出:30
//不傳引數(呼叫也是正確的)
print("--2--${add()}"); //輸出:0
//選擇傳遞引數
print("--3--${add(j: 20)}"); //輸出:0
//與位置無關
print("--4--${add(j: 20, i: 10)}"); //輸出:30
}
複製程式碼
三、可選位置引數
什麼叫可選命名函式,其實說白了就是把方法的引數放到 [] 中就變成了可選命名引數。
void main() {
int add([int i, int j]) {
if (i == null || j == null) {
return 0;
}
return i + j;
}
//全參呼叫
print("--1--${add(10, 20)}"); //輸出:30
//不傳引數(呼叫也是正確的)
print("--2--${add()}"); //輸出:0
//選擇傳遞引數
print("--3--${add(10)}"); //輸出:0
}
複製程式碼
四、預設引數
什麼叫做預設引數,其實就是在定義方法的時候,可選引數可以使用 = 來定義可選引數的預設值。
import 'dart:core';
void main() {
int sum([int age1 = 1, int age2 = 2]) {
if (age1 == null || age2 == null) {
return 0;
}
return age1 + age2;
}
//全參呼叫
print("--1--${sum(10, 20)}"); //輸出:30
//不傳引數(呼叫也是正確的)
print("--2--${sum()}"); //輸出:3
//選擇傳遞引數
print("--3--${sum(20)}"); //輸出:22
}
複製程式碼
五、匿名函式
首先什麼叫匿名函式,簡單來說:
大多數方法都是有名字的,比如 main() 或 printElement()。你可以建立一個沒有名字的方法,稱之為 匿名函式,或Lambda表示式 或Closure閉包。你可以將匿名方法賦值給一個變數然後使用它,比如將該變數新增到集合或從中刪除。
([Type] param1, …) {
codeBlock;
};
複製程式碼
匿名方法看起來與命名方法類似,在括號之間可以定義引數,引數之間用逗號分割。 後面大括號中的內容則為函式體:下面程式碼定義了只有一個引數 item 且沒有引數型別的匿名方法。List 中的每個元素都會呼叫這個函式,列印元素位置和值的字串:
import 'dart:core';
void main() {
var list = ['黃藥師', '楊過', '老頑童'];
list.forEach((item) {
print('${list.indexOf(item)}: $item'); //輸出:0: 黃藥師 1: 楊過 2: 老頑童
});
// 如果函式體內只有一行語句,你可以使用箭頭語法:
list.forEach(
(item) => print('${list.indexOf(item)}: $item')); //輸出:0: 黃藥師 1: 楊過 2: 老頑童
}
複製程式碼
至此在開發flutter 過程中常用的方法知識點我們講完了啊
其實方法內容不止這些,接下來的小夥伴可以自行挖掘:比如方法作用域等。
接下來我們進入類講解
六、類
Dart 是一個物件導向程式語言。 每個物件都是一個類的例項,所有的類都繼承於 Object。同時也支援物件導向的特性,比如:類、介面、抽象等。
使用class關鍵字宣告一個dart類,後面跟類名,並且由一對花括號包圍的類體 所有類都有同一個基類,Object,dart的繼承機制使用了Mixin;
//虛擬碼
class class_name {
<fields> //欄位,類中宣告任何變數、常量;
<getters/setters> // 如果物件為final,或const,只有一個getter方法 這個等會我們會有例項產生
<constructors> // 建構函式,為類的物件分配記憶體
<functions> //函式,也叫方法,物件的操作;
}
複製程式碼
上面我們提到 <getters/setters>方法,其實每個例項變數都會自動生成一個 getter 方法(隱含的)。 非final 例項變數還會自動生成一個 setter 方法。
class User {
var name;
var age;
User(this.name, this.age);
}
void main(){
var user =User("黃藥師",50);
var _name = user.name;
var _age = user.age;
print("-----$_name$_age"); //輸出:黃藥師 50
}
複製程式碼
七、類--建構函式
Dart建構函式有種實現方式:
- 預設構造方法
- 命名構造方法Class.name(var param)
- 呼叫父類構造方法
- 不可變物件,定義編譯時常量物件,建構函式前加const
- 工廠建構函式:factory
預設建構函式往往也是我們最常見的也是最簡單的如下
class User {
var name;
var age;
User(this.name, this.age); //預設建構函式
}
複製程式碼
命名建構函式
Dart 並不支援建構函式的過載,而採用了命名建構函式為一個類實現多個建構函式:
class User {
var name;
var age;
User(this.name, this.age); //預設建構函式
//User(this.name); ///錯誤,因為不准許過載
User.age(this.age) {
name = "歐陽鋒";
}
}
void main() {
var user = User.age(50);
print("----${user.name}${user.age}"); //輸出:歐陽鋒 50
}
複製程式碼
重定向建構函式
有時候一個建構函式會調動類中的其他建構函式(在Java中就是 this(...))。 一個重定向建構函式是沒有程式碼的,在建構函式宣告後,使用 冒號呼叫其他建構函式。
class User {
var name;
var age;
User(this.name, this.age); //預設建構函式
User.user(name, age) : this(name, age);
}
void main() {
var user = User.user("黃藥師", 50);
print("----${user.name}${user.age}"); //輸出:黃藥師50
}
複製程式碼
常量建構函式
如果你的類提供一個狀態不變的物件,你可以把這些物件 定義為編譯時常量。要實現這個功能,需要定義一個 const 建構函式, 並且宣告所有類的變數為 final。
class User {
final String name;
final int age;
const User(this.name, this.age); //預設建構函式
}
void main() {
var user = User("黃藥師", 50);
print("----${user.name}${user.age}"); //輸出:黃藥師50
}
複製程式碼
工廠建構函式
當實現一個使用factory 關鍵詞修飾的建構函式時,這個建構函式不必建立類的新例項。例如,一個工廠建構函式 可能從快取中獲取一個例項並返回,或者 返回一個子型別的例項。(工廠建構函式無法訪問 this)
//工廠構造方法 如果一個構造方法並不總是返回一個新的物件,這個時候可以使用factory來定義這個構造方法。
class Logger {
final String name;
bool mute = false;
static final Map<String, Logger> _cache = <String, Logger>{};
factory Logger(String name) {
if (_cache.containsKey(name)) {
return _cache[name];
} else {
final logger = new Logger._internal(name);
_cache[name] = logger;
return logger;
}
}
Logger._internal(this.name);
void log(String msg) {
if (!mute) {
print(msg);
}
}
}
//呼叫
void main() {
//工廠
var logger = new Logger('UI');
logger.log('Button clicked');
}
複製程式碼
要注意的是工廠構造方法時沒法訪問this關鍵字的,所以上面就有了在類的內部這麼呼叫構造方法的程式碼:final logger = new Logger._internal(name); 在上面工廠構造方法中,如果快取中存在傳入的name的key值,則取出快取中的對應value返回。 如果快取中沒找到,就會通過命名構造方法來新建一個物件,快取起來後返回
補充說明:藉助工廠建構函式能夠實現單例:(實用場景:flutter 網路請求Dio應用)
//使用工廠構造實現單例
class DioUtil {
static final DioUtil _instance = DioUtil._init();
static Dio _dio;
factory DioUtil() {
return _instance;
}
DioUtil._init() {
_dio = new Dio();
}
}
複製程式碼
八、方法
上面是方法:方法是物件提供行為的函式。
Getters 和 Setters
Dart中每個例項變數都隱含的具有一個 getter, 如果變數不是 final 的則還有一個 setter。可以通過實現 getter 和 setter 來建立新的屬性, 使用 get 和 set 關鍵字定義 getter 和 setter:
class Rect {
num left;
num top;
num width;
num height;
Rect(this.left, this.top, this.width, this.height);
//使用 get定義了一個 right 屬性
num get right => left + width;
set right(num value) => left = value - width;
}
void main() {
var rect = Rect(0, 0, 10, 10);
print(rect.right); //10
rect.right = 15;
print(rect.left); //5
}
複製程式碼
使用 Getter 和 Setter 的好處是,你可以先使用你的例項變數,過一段時間過再將它們包裹成方法且不需要改動任何程式碼,即先定義後更改且不影響原有邏輯。
九、抽象類、抽象方法、還有繼承
例項方法、Getter 方法以及 Setter 方法都可以是抽象的,定義一個介面方法而不去做具體的實現讓實現它的類去實現該方法,抽象方法只能存在於抽象類中,抽象類的定義跟Java的抽象類類似,就不單獨介紹了。
abstract class User {
void say(); //定義一個抽象方法
}
class Person extends User{
@override
void say() {
// 提供一個實現,所以在這裡該方法不再是抽象的……
}
}
複製程式碼
說明:抽象類不能被例項化,除非定義工廠方法並返回子類。
abstract class User {
String name;
//預設構造方法
User(this.name);
//工廠方法返回Child例項
factory User.test(String name){
return new Child(name);
}
void printName();
}
// extends 繼承抽象類
class Child extends User{
Child(String name) : super(name);
@override
void printName() {
print(name);
}
}
void main() {
var p = User.test("黃藥師");
print(p.runtimeType); //輸出實際型別 Child
p.printName();//輸出實際型別 黃藥師
}
複製程式碼
十、介面
Dart 沒有像 Java 用單獨的關鍵字 interface 來定義介面,普通用 class 宣告的類就可以是介面,可以通過關鍵字 implements來實現一個或多個介面並實現每個介面定義的 API:
// Person 類的隱式介面中包含 greet() 方法。
class User {
// _name 變數同樣包含在介面中,但它只是庫內可見的。
final _name;
// 建構函式不在介面中。
User(this._name);
// greet() 方法在介面中。
String greet(String who) => '你好,$who。我是$_name。';
}
// Person 介面的一個實現。
class Impostor implements User {
get _name => '';
String greet(String who) => '你好$who。你知道我是誰嗎?';
}
String greetBob(User person) => person.greet('黃藥師');
void main() {
print(greetBob(User('歐陽鋒'))); //輸出:你好,黃藥師。我是歐陽鋒。
print(greetBob(Impostor())); //輸出:你好黃藥師。你知道我是誰嗎?
}
複製程式碼
這時疑問來了,介面跟繼承有什麼區別,不就是多繼承嗎? 介面的實現則意味著,子類獲取到的僅僅是介面的成員變數符號和方法符號,需要重新實現成員變數,以及方法的宣告和初始化,否則編譯器會報錯。而繼承可以選擇不重新實現,這是最大的區別。
可能小夥伴覺得有點繞:那我們總結一下。其實就兩句話:
- 單繼承,多實現。
- 繼承可以有選擇的重寫父類方法並且可以使用super,實現強制重新定義介面所有成員。
十一、可呼叫的類:
如果 Dart 類實現了 call() 函式則 可以當做方法來呼叫。
class User {
call(String name, int age) => '$name $age!';
}
main() {
var c = new User();
var out = c("黃藥師",50);
print(out); //輸出:黃藥師 50!
}
複製程式碼
十二、混合Mixins
在物件導向的世界中,我們最熟悉的莫過於class、 abstract class和interface。Dart作為一門現代物件導向程式設計語音,在原有的特性基礎上,新增了一些新的特性如:Mixins
什麼是Mixins:
簡單的理解,就是用來複用多個類之間的程式碼,減少耦合。我們直接來看一個例子。
我們在沒有使用Mixins的從前:
假設,我們現在正在開發一個動物大全App,我們需要建立一個Duck類。作為一個有豐富物件導向程式設計經驗的開發者,你自然的將所有和Duck有相似特徵的抽取成一個abstract class。
/// Bird
abstract class Bird {
void shout() {
println('shouting');
}
}
/// WaterborneBird
abstract class WaterborneBird extends Bird {
void swim() {
println('swimming');
}
}
/// Duck
class Duck extends WaterborneBird {
void doDuckThings() {
shout();
swim();
println('quack quack quack!')
}
}
複製程式碼
很好,我們清楚的將鴨子歸入水中生活的鳥類,加入其它的鳥類也變得非常容易。但是,現在我們需要加入金魚了,於是我們和上面一樣編寫程式碼。
/// Fish
abstract class Fish {
void swim() {
println("swimming")
}
}
/// GoldFish
class GoldFish extends Fish {
void doGoldFishThings() {
swim();
pringln('zzz...');
}
}
複製程式碼
這是我們發現金魚和鴨子一樣擁有swim的特性,在這個例子中是非常簡單的,但是如果我們有複雜的行為需要賦予給一個新的類,我們就要大量編寫重複的程式碼了。
使用Mixins
我們宣告一個Swimming的mixin:
mixin Swimming {
void swim() {
println('swimming')
}
}
複製程式碼
我們可以使用with關鍵字將mixin加入到class中,其實看到這裡你可能已經回想到我們其實可能已經用過這個with關鍵字了。接下來,我們就可以對上面的程式碼進行改造了:
/// Bird
abstract class Bird {
void shout() {
println('游泳');
}
}
/// Duck
class Duck extends Bird with Swimming {
void doDuckThings() {
shout();
swim();
println('跳躍!')
}
}
複製程式碼
/// Fish
abstract class Fish {
}
/// GoldFish
class GoldFish extends Fish with Swimming {
void doGoldFishThings() {
swim();
pringln('zzz...');
}
}
複製程式碼
mixins彌補了介面和繼承的不足,繼承只能單繼承,而介面無法複用實現,mixins卻可以多混入並且能利用到混入類。
我們在來看一個例子做比較:
abstract class Swimming{
void swimming(){
print("游泳");
}
}
abstract class Jump{
void jump(){
print("跳躍");
}
}
//只能單繼承,如果需要Jump,只能以implements的形式
class HuangYaoShi extends Swimming implements Jump{
//實現介面
void jump(){
print("跳躍");
}
}
//但是實際上,我們經常不需要重新實現Jump方法,複用Jump所實現的jump方法就可以了
//這時使用混合能夠更加方便
class HuangYaoShi with Swimming, Jump {}
複製程式碼
關於Mixins,還有很多需要注意的事情,我們雖然可以使用Mixins對程式碼進行一些簡化,但是要建立在對需求和類之間的關係準確理解的基礎上。建議多去看看Flutter中使用Mixins實現的一些原始碼,從裡面吸取一些正確的經驗。
下一章: 40分鐘快速入門Dart基礎(下)
參考:上面動物例子轉載了該博主的文章: 深入理解Dart之Mixins
最後附上:本人自己收集的工具庫(待完成) 工具庫-待完成