Dart語言慣用語——Dart中特有的代碼味道
對于其它語言,特別是Java和Javascript的開發(fā)者,Dart語言設計得看起來很熟悉。如果你足夠努力,你可以使 Dart 就像是那些語言一樣。如果你非常努力,你甚至可以把Dart變?yōu)镕ortran,但是這樣你將錯過Dart中獨特的、有趣的部分。
51CTO推薦專題:Google Dart新結構化編程語言
本文將幫助您寫出適用于Dart的獨特代碼。因為語言仍在演進中,所以這里的許多慣用語也在變化。語言中的有些地方我們仍不確定用什么***實踐好。(也許你能幫助我們。)但這里是一些要點,有助于把你帶出Java或Javascript的思維慣式,進入Dart。
構造函數(shù)
本文將從每個對象生命周期的起始: 使用構造函數(shù)開始。每個對象都將在某一個時刻被構造出來,定義構造函數(shù)是創(chuàng)建一個類的重要組成部分。這里Dart有些有趣的想法。
自動初始化字段
首先是擺脫一些單調乏味的部分。許多構造函數(shù)僅僅是簡單地把參數(shù)賦值給字段,如:
- class Point {
- num x, y;
- Point(num x, num y) {
- this.x = x;
- this.y = y;
- }
- }
這樣我們這里不得不輸入4次x,僅僅是初始化一個字段。太爛了。我們可以做的更好:
- class Point {
- num x, y;
- Point(this.x, this.y);
- }
在參數(shù)列表中,如果參數(shù)前使用 this. ,那么這個名字的字段將自動使用該參數(shù)值做初始化。這個例子也展示了另外一個小特性:如果一個構造函數(shù)體完全是空的,那么可以只使用一個分號(;)替代 { }。
命名構造函數(shù)
就像大部分動態(tài)類型語言一樣,Dart不支持重載。對于方法而言,這沒有多少限制,因為你總是可以使用不同的名字,但是構造函數(shù)就沒有這么幸運了。為了緩和這種情況,Dart允許你定義命名構造函數(shù):
- class Point {
- num x, y;
- Point(this.x, this.y);
- Point.zero() : x = 0, y = 0;
- Point.polar(num theta, num radius) {
- x = Math.cos(theta) * radius;
- y = Math.sin(theta) * radius;
- }
- }
這里這個 Point 類有三個構造函數(shù),一個標準的和二個命名的。你可以像下面這樣使用它們:
- var a = new Point(1, 2);
- var b = new Point.zero();
- var c = new Point.polar(Math.PI, 4.0);
注意我們這里在調用命名構造函數(shù)的時候仍然使用了 new 。這樣它就不是一個靜態(tài)方法了。
工廠構造函數(shù)
有一些和工廠(factory)相關的設計模式。當你需要一些類的實例,但是想以更靈活一點的方式,而不是直接用硬編碼的方式調用具體類型的構造函數(shù)的話,這時這些工廠模式開始發(fā)揮作用了。如果你已經(jīng)有了一個實例也許會想返回之前已經(jīng)緩存的實例,或者也許你想返回一個不同類型的對象。
Dart支持這種模式,但不要求你改變創(chuàng)建對象時的樣子。而是讓你定義一個工廠構造函數(shù)。當你調用它的時候看起來像一個普通的構造函數(shù)。但是實現(xiàn)可以做任何它想做的事情。例如:
- class Symbol {
- final String name;
- static Map<String, Symbol> _cache;
- factory Symbol(String name) {
- if (_cache == null) {
- _cache = {};
- }
- if (_cache.containsKey(name)) {
- return _cache[name];
- } else {
- final symbol = new Symbol._internal(name);
- _cache[name] = symbol;
- return symbol;
- }
- }
- Symbol._internal(this.name);
- }
這里我們有一個表示符號的類。一個符號就像一個字符串,但是我們保證在任意時間點上一個給定的名字只會有一個符號對象。這讓你能安全地比較兩個對象的相等性僅僅通過測試他們是同一個對象。
這里的默認(未命名)構造函數(shù)前加上了 factory 前綴。它告訴Dart這是一個工廠構造函數(shù)。當它被調用時,它不會創(chuàng)建一個新對象。(工廠構造函數(shù)中沒有 this 對象)。相反,期望你創(chuàng)建一個實例并明確地返回它。這里我們用給定的名字查找之前緩存的對象,如果找到了就重用它。
最酷的是調用者根本看不到這點。它們只需要:
- var a = new Symbol('something');
- var b = new Symbol('something');
第二個 new 將返回之前緩存的對象。這很好,因為這意味著如果我們起初不需要工廠構造函數(shù)但之后又認識到需要時,我們將不必把所有之前使用new的地方都改為使用靜態(tài)方法調用。
函數(shù)
像大部分現(xiàn)代語言一樣,函數(shù)是Dart中的頭等公民(first-class),帶有完整的閉包和輕量型語法支持。函數(shù)就像任何其它對象一樣,你應毫不猶豫地自由使用它們。特別是,在Dart團隊中我們大量使用函數(shù)用作事件處理器(event handler)。
Dart有三種創(chuàng)建函數(shù)的表示法:一個是命名函數(shù),一個是帶函數(shù)體的匿名函數(shù)和一個表達式語句函數(shù)。命名形式看起來像這樣:
- void sayGreeting(String salutation, String name) {
- final greeting = '$salutation $name';
- print(greeting);
- }
這看起來像是一個普通的C語言函數(shù)或者Java、Javascript中的方法。和C、C++不同的是,這些可以嵌入到另一個函數(shù)的中間。如果你不需要給出函數(shù)的名字,也可以使用匿名形式。和上面代碼類似,但沒有名字或返回類型,像這樣:
- window.on.click.add((event) {
- print('You clicked the window.');
- })
這里我們傳遞一個函數(shù)到add()方法注冊一個事件處理器。***,如果你需要一個真正的輕量型函數(shù),僅僅對單一表達式求值并返回,使用 => :
- var items = [1, 2, 3, 4, 5];
- var odd = items.filter((i) => i % 2 == 1);
- print(odd); // [1, 3, 5]
一個括號的參數(shù)列表,跟著一個 => 和一個單一表達式就創(chuàng)建了一個帶參數(shù)并返回表達式結果的函數(shù)。
實際上,只要有可能我們自己更喜歡用這種箭頭函數(shù),因為它簡潔且容易識別,感謝 => 。我們經(jīng)常使用匿名函數(shù)作為事件處理器和回調函數(shù)。命名函數(shù)反而使用很少。
Dart 還有一個技巧,這是我最喜歡的語言特性之一:可以使用 => 定義成員。當然,你可以這樣做:
- class Rectangle {
- num width, height;
- bool contains(num x, num y) {
- return (x < width) && (y < height);
- }
- num area() {
- return width * height;
- }
- }
但是當你僅需要下面這樣做的時候,為什么還需要上面那樣呢:
- class Rectangle {
- num width, height;
- bool contains(num x, num y) => (x < width) && (y < height);
- num area() => width * height;
- }
我們發(fā)現(xiàn)箭頭函數(shù)非常適用于定義簡單的getter和其它單行方法來實現(xiàn)計算或訪問對象屬性。
#p#
字段,getters 和 setters
說到屬性,Dart使用標準的 object.someProperty 語法使用它們。 當屬性是類中一個真實的字段時,大部分語言就是這樣做的。但是Dart 還允許你定義一些方法,它們看起來像是訪問屬性,但實際上可以執(zhí)行任意你想要的代碼。在其它語言中,這些被稱為getters 和 setters??催@個例子:
- class Rectangle {
- num left, top, width, height;
- num get right() => left + width;
- set right(num value) => left = value - width;
- num get bottom() => top + height;
- set bottom(num value) => top = value - height;
- Rectangle(this.left, this.top, this.width, this.height);
- }
這里我們定義了一個Rectangle 類,它有四個真實的字段:left, top, width, 和 height。它還有兩對getters 和 setters方法定義了兩個額外的邏輯屬性:right 和 bottom。在你使用這個類時,真實字段與 getters和setters 沒有明顯的區(qū)別:
- var rect = new Rectangle(3, 4, 20, 15);
- print(rect.left);
- print(rect.bottom);
- rect.top = 6;
- rect.right = 12;
字段和getters/setters間的模糊化是語言的基本原則。看待它最清楚的方式就是認為字段僅僅是魔法實現(xiàn)的getters 和 setters。這意味著你可以做些有趣的事情,比如用字段覆蓋繼承的getter方法,或反之。如果接口定義了一個getter,你可以簡單地用一個同名、同類型的字段實現(xiàn)它。如果字段是可變的(非final),那么它也實現(xiàn)了接口要求的setter。
實際上,這意味著你不必防御性地把字段隱藏到getter、setter樣板方法里來隔離它們,就像你在Java或C#中所做的。如果你有需要暴露的屬性,只需用一個public的字段。如果你不想它們被修改,只需加上final。
稍后,如果你需要做一些驗證或什么其它事情,你隨時可以用getter和setter代替這個字段。比如我們想確保 Rectangle類總是有非負的大小,我們可以把它改為這樣:
- class Rectangle {
- num left, top;
- num _width, _height;
- num get width() => _width;
- set width(num value) {
- if (value < 0) throw 'Width cannot be negative.';
- _width = value;
- }
- num get height() => _height;
- set height(num value) {
- if (value < 0) throw 'Height cannot be negative.';
- _height = value;
- }
- num get right() => left + width;
- set right(num value) => left = value - width;
- num get bottom() => top + height;
- set bottom(num value) => top = value - height;
- Rectangle(this.left, this.top, this._width, this._height);
- }
現(xiàn)在我們修改這個類增加了一些驗證,但根本不影響那些已經(jīng)使用了它的代碼。
頂層定義
Dart是“純”面向對象語言,變量中的任何東西都是一個真正的對象(沒有突變的原始類型),并且每個對象都是某個類的實例。然而它不是教條式的OOP語言。它不要求你把每個東西都定義在類里。相反,如果你愿意,你可以在頂層自由地定義函數(shù)、變量甚至是getters和setters。
- num abs(num value) => value < 0 ? -value : value;
- final TWO_PI = Math.PI * 2.0;
- int get today() {
- final date = new Date.now();
- return date.day;
- }
即使是那些不要求你把所有東西都放在類或對象中的語言,如Javascript,它們一般仍然是用一種命名空間的形式:相同名字的頂層定義會導致不經(jīng)意的沖突。為解決這個問題,Dart使用一種 library 系統(tǒng),允許你用一個前綴導入其它庫中的定義來消除歧義。這意味著你不應該需要防御式地把定義放到類中。
我們仍在探索這實際意味著我們應如何定義庫。我們的大部分代碼是把定義放到類中的,如Math。很難說這僅是我們在其它語言中根深蒂固的習慣還是說對Dart而言這也是一種好的實踐方式。我們期待這方面的反饋。
我們確實有一些使用頂層定義的例子。首先你需要運行的main()函數(shù)就是要在頂層定義的。如果你使用DOM,熟悉的document 和 window “變量”實際上是Dart中頂層定義的getters 。
字符串和插值
Dart有幾種字符串字面值。你可以用單引號或雙引號,也可以用三引號的多行字符串:
- 'I am a "string"'
- "I'm one too"
- '''I'm
- on multiple lines
- '''
- """
- As
- am
- I
- """
為了構造更大的字符串,使用+ 連接它們即可:
- var name = 'Fred';
- var salutation = 'Hi';
- var greeting = salutation + ', ' + name;
但是使用字符串插值會更快更清晰:
- var name = 'Fred';
- var salutation = 'Hi';
- var greeting = '$salutation, $name';
在字符串中,一個美元符號($)跟著一個變量將被擴展為該變量的值。(如果變量不是字符串將調用它的toString()方法)。你也可以在大括號里插入表達式:
- var r = 2;
- print('The area of a circle with radius $r is ${Math.PI * r * r}');
操作符
Dart使用你熟悉的C、Java語言里一樣的操作符和優(yōu)先級。它們會按你期望的方式工作。而在背后,它們有點特殊。在Dart中,使用操作符的表達式如1+2,實際上僅是調用方法的語法糖。對于語言,這個例子看起來更像是1.+(2) 。
這意味著你也可以為你自己的類型重載(多數(shù))操作符。例如這是一個Vector 類:
- class Vector {
- num x, y;
- Vector(this.x, this.y);
- operator +(Vector other) => new Vector(x + other.x, y + other.y);
- }
這樣,我們可以使用熟悉的語法形式做向量加法:
- var position = new Vector(3, 4);
- var velocity = new Vector(1, 2);
- var newPosition = position + velocity;
話雖如此,請不要過度濫用。我們給你汽車的鑰匙,并且相信你不會掉頭把車開到客廳里。
在實踐上,如果你定義的類型在“現(xiàn)實世界”中(在黑板上?)經(jīng)常使用操作符,那么它可能是一個好的操作符重載候選者,如:復數(shù)、向量、矩陣等。另外,也不一定。自定義操作符的類型一般也應該是不可變類型。
注意因為操作符調用實際上僅僅是方法調用,它們具有固有的不對稱性。方法總是在左邊的參數(shù)上獲取。所以當你做a+b的時候,是根據(jù) a 的類型決定其意義的。
相等性
有一類操作符需要特別注意。Dart有兩類相等運算符:== 和 !=, 與 === 和 !== 。Javascript開發(fā)者應該很熟悉,但是這里有點區(qū)別。
== 和 != 做等價測試。它們應該是你99%的時候使用的。和Javascript不同,它們不做任何隱式轉換,所以它們應該像你所期待的那樣的行為。別害怕使用它們。和Java不同,它們適用于任何具有等價關系定義的類型。不再需要 someString.equals("something") 這樣了。
你可以為你自己的類型重載 == ,只要它們有意義。你不必重載 != ,Dart 自動根據(jù)你的 == 定義做推斷。
其它操作符, === 和 !== 用來測試身份。a === b 僅當 a 和 b 是內存中完全相同的對象時才返回 true 。默認情況下,如果類型沒有定義有意義的相等操作符,那么 == 調用將退回到 === 。所以你唯一需要用這個的時候是你明確地想要繞過任何用戶定義的 == 操作符。
原文:http://han.guokai.blog.163.com/blog/static/1367182712011925112734306/
【編輯推薦】