最近時間太忙,每天加班加點,可是咱們的學習的行爲不能落下。編程
小夥伴們繼續跟着我一塊兒來學習40分鐘快速入門Dart基礎(中)緩存
鎮樓目錄: bash
在本章咱們主要跟你們聊聊方法和類。這兩大章看似很少,其實也很多,不太小夥伴們不用擔憂,只要你們按照我下面的代碼敲一遍,保證能掌握大部分知識點!網絡
好廢話很少說,直接開幹。閉包
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 中方法能夠有兩種類型的參數:必需的和可選的。 必需的參數須要在參數列表前面, 後面再定義可選參數。ide
什麼叫可選命名函數,其實說白了就是把方法的參數放到 {} 中就變成了可選命名參數。函數
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 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())); //輸出:你好黃藥師。你知道我是誰嗎?
}
複製代碼
這時疑問來了,接口跟繼承有什麼區別,不就是多繼承嗎? 接口的實現則意味着,子類獲取到的僅僅是接口的成員變量符號和方法符號,須要從新實現成員變量,以及方法的聲明和初始化,不然編譯器會報錯。而繼承能夠選擇不從新實現,這是最大的區別。
可能小夥伴以爲有點繞:那咱們總結一下。其實就兩句話:
若是 Dart 類實現了 call() 函數則 能夠當作方法來調用。
class User {
call(String name, int age) => '$name $age!';
}
main() {
var c = new User();
var out = c("黃藥師",50);
print(out); //輸出:黃藥師 50!
}
複製代碼
在面向對象的世界中,咱們最熟悉的莫過於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實現的一些源碼,從裏面吸收一些正確的經驗。
下一章也是咱們Dart最後一章咱們繼續講解Dart:異步、泛型、異常
參考:上面動物例子轉載了該博主的文章: 深刻理解Dart之Mixins
最後附上:本人本身收集的工具庫(待完成) 工具庫-待完成