物件導向( Object Oriented Programming ),目的為了處理資料,設計一個有共同規則的物件,還有方法來方便對資料重複使用或做複雜的處理,可以想像是去跟資料庫做互動,所設計的模式,資料庫裡的每筆資料就像一個物件,而物件(Object),是從類別(Class) 實體化後而來,可以使用此類別定義的屬性和方法。
新增物件
需先分析要處理的每一筆資料細目,Attribute屬性(fileld欄位,datatype),再根據此規則建立類別,之後就能將每一筆資料都建立成此類別的物件
語法:
類別 物件名稱 = new 類別名 ( [ 引數值 ] ) ;
Call by reference
等號左右兩邊的類別(class)要一樣或有繼承關係才可以,[引數值] : 可為空,與建構有關,之後會再詳談
Parameters (引數、參數),傳入的值
類別 物件名稱 = 類別名 ( [ 引數值 ] ) ;
new 在Dart 2後可以選擇不用加
//個人資料表,有姓名,年齡,電話,住址
class Person {
//找出共同規則來建立類別
String name;
int age;
String phoneNumber;
String address;
}
void main() {
Person a = new Person(); // 建立一個Person類別的物件,a 即為Person類別的 instance
Person b = Person(); // 一樣為建立一個Person類別的物件,b 一樣為Person類別的 instance
print(a); // 印出 Instance of 'Person'
print(b); // 印出 Instance of 'Person'
//取得物件的型態
print(a.runtimeType); // 印出 Person
print(b.runtimeType); // 印出 Person
//是否為同一個物件
print(identical(a, b)); // 印出 false
//還未對此物件設值,Person 的 name 也未給初始值
print("a's name = ${a.name}"); // a's name = null
point.a = "ryder"; // 使用預設setter 方法對變數name 賦值
print("a's name = ${a.name}"); // a's name = ryder
}
建構式 ( constructors )
新增一個物件時最後會執行的步驟
故物件初始化時,想傳值、對資料做處理或額外的判斷,可做在建構式裡面
名字必須與類別名稱一樣
全域( Global )/區域( Local )變數生命週期
以變數宣告的位置分成全域變數或區域變數,其變數的生命週期只在該區域內 { }
,若在 { }
內呼叫變數會先從區域找起,再去全域,若在區域內想用同名稱的全域變數,需要用 this. 來呼叫
this._ → 表示其物件本身的變數,即class的field區
class Person {
String name;
int age;
Person(String name, int age){
this.name = name;
this.age = age;
}
}
void main() {
Person a = new Person("Ryder",26);
print("a.name = ${a.name}, a.age = ${a.age}"); //印出 a.name = Ryder, a.age = 26
}
Default copnstructors(預設建構式)
如果一個都沒有定義建構式,類別會自動建立一個預設建構式,預設建構式沒有任何引數
Named constructors(命名建構式)
一個類別只能有一個主建構式 Unnamed constructors,不像在Java 擁有多載(Overloading) 的特性,是用引數來區分,但是Dart 用另一種方法來滿足這個要求,並且能更清楚表達程式,增加易讀性,即使用 Named constructors(命名建構式)
class Person {
String name = "匿名";
int age;
//主建構式 Unnamed constructors
//由於建構式引數賦值給物件屬性變數的情況太常見了,Dart提供了一個語法來簡化這個操作
Person(this.name, this.age);
//Named constructors
Person.secretAge(this.name);
//Named constructors
Person.anonymous(this.age);
}
void main() {
Person a = Person("Ryder",26);
print("a.name = ${a.name}, a.age = ${a.age}"); //印出 a.name = Ryder, a.age = 26
Person b = Person.secretAge("Judy");
print("b.name = ${b.name}, b.age = ${b.age}"); //印出 b.name = Judy, b.age = null
Person c = Person.anonymous(65);
print("c.name = ${c.name}, c.age = ${c.age}"); //印出 c.name = 匿名, c.age = 65
}
Redirecting constructors(重定向建構式)
有時候一個建構式會調動類別中的其他建構式,一個重定向建構式是沒有程式碼的,在建構式聲明後,使用冒號呼叫其他建構式
class Point {
num x;
num y;
// 主建構式
Point(this.x, this.y);
// 呼叫主建構式
Point.alongXAxis(num x) : this(x, 0);
}
void main() {
Point a = Point(6, 6);
print("a.x = ${a.x}, a.y = ${a.y}"); //印出 a.x = 6, a.y = 6
Point b = Point.alongXAxis(8);
print("b.x = ${b.x}, b.y = ${b.y}"); //印出 b.x = 6, b.y = 0
}
main
方法,同時還支援在類別中定義函式(靜態函式和例項函式),還可以在方法中定義方法,Dart支援top-level 變數,也支援類別變數(static,後面會介紹) 和物件變數public
、protected
、private
封裝關鍵字。如果某個變數名以底線_
開頭,代表這個變數為private
函式是類別中定義的方法,是物件可執行的行為,方法也是物件具有的一種型別,可以賦值給變數,也可以當做其他方法的引數,甚至把類別的instance (例項)當作方法來呼叫
語法:
類型 方法名(引數) {
欲執行的步驟
}
=> expr
是語法{return expr;}
形式的縮寫
類型:
Effective Dart推薦用明確的型態類型定義方法,但一樣可以選擇忽略型別來定義方法
class Student {
String _name;
int _chi;
int _eng;
int _sum;
double _ave;
Student(this._name, this._chi, this._eng) {
_sum = _chi + _eng;
_ave = (_chi + _eng) / 2;
}
void show1() {
print(
"name = $_name, chinese = $_chi, english = $_eng, sum = $_sum, avg = $_ave");
}
show2() {
print(
"name = $_name, chinese = $_chi, english = $_eng, sum = $_sum, avg = $_ave");
}
void show3() => print(
"name = $_name, chinese = $_chi, english = $_eng, sum = $_sum, avg = $_ave");
}
main() {
Student a = Student("Ryder", 70, 80);
a.show1(); //印出 name = Ryder, chinese = 70, english = 80, sum = 150, avg = 75
a.show2(); //印出 name = Ryder, chinese = 70, english = 80, sum = 150, avg = 75
a.show3(); //印出 name = Ryder, chinese = 70, english = 80, sum = 150, avg = 75
}
函數可以有兩種類型的參數 → 必須 required 和可選 optional,若一個函式同時有兩種,required parameter 需放在首位,後面再放optional parameter
optional parameter 可選參數
可選參數可以是命名參數,也可以是位置參數,但不能同時為兩種
Optional Named parameters 可選命名參數:可以增強程式的可讀性
當定義一個函數時,使用
{param1, param2, … }
的形式来制定命名参数void enableFlags({bool bold, bool hidden}) {...}
當使用一個方法時,可以使用
paramName:value
的形式來指定命名參數enableFlags(bold: true, hidden: false);
可以對命名參數註釋
@required
,來表明該命名參數是必須 required 的。 ( depend on the meta package and importpackage:meta/meta.dart
)const Scrollbar({Key key, @required Widget child})
當使用建構 Scrollbar時,如果沒傳參數 child,就會報錯
Optional positional parameters 可選位置參數:
將一些方法的引數放到
[ ]
中就變成可選位置參數了main() { //定義一個方法 [String device]是可選位置參數 也就是呼叫這個方法可以不傳這個引數 String say(String from, String msg, [String device]) { //required parameter 放前面,可選參數需要放在後面 var result = '$from says $msg'; if (device != null) { result = '$result with a $device'; } return result; } //不使用可選引數呼叫方法 print(say('Ryder', 'hello world')); //印出 Ryder says hello world //使用可選數呼叫方法 print(say('Ryder', 'hello world', 'Dart')); //印出 Ryder says hello world with a Dart }
Default parameter value (預設引數值):
在定義方法的時候,可以使用=
來定義可選引數的預設值,預設值只能是編譯時常量( const
),如果沒有提供預設值,則預設值為null
main() {
//定義一個返回型態為空的方法,方法中可選命名參數bold、hidden 預設值為false
void enableFlags({bool bold = false, bool hidden = false}) {
print("bold = $bold, hidden = $hidden");
}
//呼叫方法 沒有傳hidden的值,那預設值就是false
enableFlags(bold: true); //印出 bold = true, hidden = false
//定義一個方法 有一個可選位置參數device 的預設參數是carrier pigon
String say(String from, String msg,
[String device = 'carrier pigeon', String mood]) {
var result = '$from says $msg';
if (device != null) {
result = '$result with a $device';
}
if (mood != null) {
result = '$result (in a $mood mood)';
}
return result;
}
//呼叫上面的方法,沒有傳device的值,那預設值就是carrier pigeon
print(say('Ryder', 'hello world')); //印出 Ryder says hello world with a carrier pigeon
}
還可以使用list
或者map
作為預設值
main(){
doStuff(); //印出 list: [1, 2, 3]
//gifts: {first: paper, second: cotton, third: leather}
}
//List和Map都取了預設值
void doStuff(
{List<int> list = const [1, 2, 3],
Map<String, String> gifts = const {
'first': 'paper',
'second': 'cotton',
'third': 'leather'
}}) {
print('list: $list');
print('gifts: $gifts');
}
The main() function(入口函式)
每個app 都有一個top-level 的main()
入口方法,有它app 才能執行。main()
方法的返回值為void
並且有個可選的List<String>
引數
建立一個 .dart 檔
void main(List<String> arguments) {
print(arguments);
}
在終端機 terminal 下指令
dart main.dart test1 test2
印出 [test1, test2]
Functions as first-class objects
函數本身可以作為一個參數傳遞給另一個函數
main() {
void printElement(int element) {
print(element);
}
var list = [1, 2, 3];
// 傳遞printElement方法 給List 類別的forEach 方法當參數用
list.forEach(printElement); //印出 1
// 2
// 3
}
方法也可以賦值給一個變數:
main() {
var loudify = (msg) => '!!! ${msg.toUpperCase()} !!!'; // (msg) 是匿名方法,等等會介紹
print(loudify("hello")); //印出 !!! HELLO !!!
}
匿名函數:
大部分方法都會命名,例如main()
或者printElement()
。但是也可以建立沒有名字的方法,稱為匿名方法,有時候也被稱為lambda
或者clourse
閉包。可以把匿名方法賦值給一個變數,並可以使用這個方法,比如新增集合或者從集合中刪除。匿名函式和命名函式類似,在括號之間可以定義一些參數,並使用逗號分割,其中參數也可以是可選參數等
語法:
([[Type] param1[, …]]) {
codeBlock;
};
main() {
//toUpperCase 即為匿名方法
Function toUpperCase = (String item){
return item.toUpperCase();
};
print("hello 轉大寫 = ${("hello")}"); //印出 Hello
//可以不用先宣告方法,直接使用
var list = ['apples', 'oranges', 'grapes', 'bananas', 'plums'];
list.forEach((i) {
print(list.indexOf(i).toString() + ': ' + i);
});
/*印出:
0: apples
1: oranges
2: grapes
3: bananas
4: plums
*/
// => 的寫法
list.forEach((i) => print(list.indexOf(i).toString() + ': ' + i));
/*印出:
0: apples
1: oranges
2: grapes
3: bananas
4: plums
*/
}
Lexical scope
同前面介紹過的,變數、方法的生命週期只在該區域內 { }
//top-level scope start
var topLevel = true;
main() {//main scope start
var insideMain = true;
myFunction() {//myFunction scope start
var insideFunction = true;
nestedFunction() {//nestedFunction scope start
var insideNestedFunction = true;
//在nestedFunction()內,所有變數都可以使用
assert(topLevel);
assert(insideMain);
assert(insideFunction);
assert(insideNestedFunction);
}//nestedFunction scope end
}//myFunction scope end
}//main scope end
//top-level scope end
Lexical closures(詞法閉包)
Closure
閉包,是一個方法物件,不管該物件在何處被呼叫,該物件都可以使用其作用域內的變數,方法可以封閉定義到其作用域內的變數。
Function makeAdder(num addBy) {
return (num i) => addBy + i;
}
main() {
var add2 = makeAdder(2);
// add2 為一個匿名方法物件 (num i) => 2 + i;
var add4 = makeAdder(4);
// add4 為一個匿名方法物件 (num i) => 4 + i;
print(add2(3)); //印出 5
print(add4(3)); //印出 7
}
foo() {} //top-level方法
class A {
static void bar() {} // 靜態方法,又稱類別類的方法。 static 後面會介紹
void baz() {} // instance例項方法,又為物件類的方法
}
void main() {
var x;
// 比較top-level函式
x = foo;
print(foo == x); //印出:true
// 比較靜態方法
x = A.bar;
print(A.bar == x); //輸出:true
// 比較例項方法
var v = new A(); // Instance #1 of A
var w = new A(); // Instance #2 of A
var y = w;
x = w.baz;
//這些閉包引用相同的例項 w
//所以相等
print(y.baz == x); //印出:true
// 閉包引用不同的例項,所以不相等
print(v.baz != w.baz); //印出:true
}
外部函式庫
使用方式:
Instance methods(例項函式)
//要用到外部函式庫的sqrt 方法,故需要import 此方法的函式庫
import 'dart:math';
class Point {
num x;
num y;
Point(this.x, this.y);
num distanceTo(Point other) {
var dx = x - other.x;
var dy = y - other.y;
return sqrt(dx * dx + dy * dy);
}
}
void main() {
Point a = Point(3, 4);
Point origin = Point(0, 0);
print("a.distanceTo(origin) = ${a.distanceTo(origin)}"); //印出 a.distanceTo(origin) = 5
}
Getters
和setters
是用來設定和使用物件屬性的特殊函式,每個例項變數都預設的具有一個getter
, 如果變數不是final
的則還有一個setter
,可以通過實行getter
和setter
來建立新的屬性,使用get
和set
關鍵字定義getter
和setter
:
class Rectangle {
num left;
num top;
num width;
num height;
Rectangle(this.left, this.top, this.width, this.height);
// 定義兩個計算屬性:右和下
num get right => left + width;
set right(num value) => left = value - width; //對right 設值會執行 left = value(對right 設的值) - width;
num get bottom => top + height;
set bottom(num value) => top = value - height;
}
main() {
var rect = new Rectangle(3, 4, 20, 15);
print(rect.left); //印出 3
rect.right = 12; // left = value - width; 所以 left = 12 - 20;
print(rect.left); //印出 -8
}
Return values (返回值)
除了類型void
函式,其他所有的函数都有返回值,如果没有指定返回值,则返回null
main() {
foo() {}
print(foo()); //印出null
}
今天就先介紹類別的觀念以及方法的應用,下一篇將講解 static的觀念,以及寫程式時,發生Exception 異常時,該如何去做例外處理