Dart語(yǔ)言學(xué)習(xí)筆記二-對(duì)象逆甜,接口教藻,類篡悟,mixin

此篇內(nèi)容均來(lái)源于《Dart編程語(yǔ)言》--[美]Gilad Bracha 著 戴虬 譯

值得再次強(qiáng)調(diào)的是:在Dart中谜叹,一切皆對(duì)象,這甚至包括了最簡(jiǎn)單的數(shù)據(jù)如數(shù)字或布爾值等搬葬。一個(gè)對(duì)象由(可能為空的)一組字段提供狀態(tài)荷腊,由一族方法提供行為。對(duì)象的狀態(tài)可以是可變或不變的急凰。對(duì)象的方法永不為空女仰,因?yàn)樗械腄art對(duì)象都具備一定的行為。對(duì)象從它們的類中獲得行為抡锈。每個(gè)對(duì)象都有一個(gè)類疾忍,我們將之表述為對(duì)象是類的一個(gè)實(shí)例。因?yàn)槊總€(gè)對(duì)象都有一個(gè)決定其行為的類床三,所以Dart是一門(mén)基于類的語(yǔ)言一罩。一個(gè)類沒(méi)有列出父類,那么其父類就是Object撇簿。

1.accessor

accessor(存饶粼ā)是為了方便訪問(wèn)值所提供的特殊方法。
Dart提供的getter方法四瘫,是一個(gè)不帶參數(shù)的特殊方法歧沪,可以在不提供參數(shù)列表的情況下直接調(diào)用。getter方法的引入是通過(guò)在方法名前添加前綴get莲组。getter方法不需要參數(shù)列表诊胞, 甚至是空的參數(shù)列表。
setter方法名前面要添加前綴set锹杈,并只接收一個(gè)參數(shù)撵孤。setter的調(diào)用語(yǔ)法與傳統(tǒng)的變量賦值一樣的。如果一個(gè)實(shí)例變量是可變的竭望,則一個(gè)setter將自動(dòng)為他定義邪码,所有實(shí)例變量的賦值實(shí)際上都是對(duì)setter的調(diào)用。

class Point {
  var rho, theta;
  Point(this.rho, this.theta);
  get x => rho * cos(theta);
  set x(newX) {
    rho = sqrt(newX*newX + y*y);
    theta = acos(newX/rho);
  }
  set y(newY){
    rho = sqrt(x *x + newY*newY);
    theta = asin(newY/rho);
  }
  get y =>rho * sin(theta);
  scale(factor) => new Point(rho * factor, theta * factor);
  operator +(p) => new Point(rho + p.rho, theta + p.theta);
  static distance(p1, p2) {
    var dx = p1.rho - p2.rho;
    var dy = p1.theta - p2.theta;
    return sqrt(dx * dx + dy * dy);
  }
} 

2. 實(shí)例變量

當(dāng)一個(gè)類聲明一個(gè)實(shí)例變量時(shí)咬清,會(huì)確保每個(gè)實(shí)例都有自己的唯一變量復(fù)制闭专。對(duì)象的實(shí)例變量需要占用內(nèi)存奴潘,這塊內(nèi)存是在對(duì)象創(chuàng)建時(shí)分配的。重要的是影钉, 此內(nèi)存在被訪問(wèn)之前画髓, 應(yīng)該被設(shè)置為某些合理的值。在低級(jí)語(yǔ)言如c語(yǔ)言中則并不如此平委,新分配的存儲(chǔ)空間的內(nèi)容可能是不明確的奈虾,通常就是內(nèi)存在重新分配之前的值。這將會(huì)導(dǎo)致可靠性廉赔,安全性方面的問(wèn)題肉微。

Dart會(huì)將每個(gè)新分配的變量(不只是實(shí)例變量,還包括局部變量蜡塌,類變量和頂層變量)初始化為null碉纳。在Dart中, 與其他對(duì)象一樣馏艾,null也是一個(gè)對(duì)象村象。我們不能把null與其他對(duì)象混淆,如0或false攒至。null對(duì)象只是在Dart核心庫(kù)中定義的Null類的唯一實(shí)例。

聲明實(shí)例或靜態(tài)變量會(huì)自動(dòng)引入一個(gè)getter躁劣。如果變量是可變的迫吐,則一個(gè)setter也會(huì)被自動(dòng)定義。事實(shí)上账忘,Dart中的字段都不是直接訪問(wèn)的志膀,所有對(duì)字段的引用都是對(duì)accessor方法的調(diào)用。只有對(duì)象的accessor才能直接訪問(wèn)他的狀態(tài)鳖擒。

3. 類變量

除了實(shí)例變量溉浙,類也可以定義類變量。一個(gè)類只有一份類變量的副本蒋荚,無(wú)論它有多少個(gè)實(shí)例戳稽。即使類沒(méi)有實(shí)例,類變量也存在期升。

類變量的聲明是在變量名前放置單詞static惊奇。我們可以添加一個(gè)類變量來(lái)跟蹤有多少個(gè)實(shí)例被創(chuàng)建。

class Box {
  static var numberOfInstance = 0;
  Box() {
    numberOfInstance++;
  }
}

像實(shí)例變量播赁,類變量從不直接引用容为。所有對(duì)他們的訪問(wèn)都是通過(guò)accessor替劈。在它的聲明類中抬纸, 類變量可通過(guò)名稱直接引用湿故。在類的外部坛猪,只能通過(guò)在變量名前加上類名來(lái)訪問(wèn)墅茉。

類變量通常也被稱為靜態(tài)變量就斤,當(dāng)‘靜態(tài)變量’這個(gè)術(shù)語(yǔ)也包括了類變量與頂層變量洋机。為了避免混淆,我們將堅(jiān)持使用‘類變量’這個(gè)術(shù)語(yǔ)喜鼓。我們也經(jīng)常會(huì)用“字段”這個(gè)術(shù)語(yǔ)來(lái)通常實(shí)例和類變量庄岖。

類變量是延遲初始化的角骤, 在getter第一次被調(diào)用時(shí)類變量才執(zhí)行初始化邦尊,即第一次嘗試讀取它時(shí)胳赌。未經(jīng)初始化熏版,默認(rèn)值為null撼短。

class Cat{}
class DeadCat extends Cat{}
class LiveCat extends Cat{
  LiveCat(){print("I am alive!");}
}
var schrodingers = new LiveCat() as Cat;
void main() {
  schrodingers = new DeadCat();
}
// 結(jié)果:什么都沒(méi)有輸出

此處schrodingers的初始化永遠(yuǎn)不會(huì)被執(zhí)行曲横,并且對(duì)print()的調(diào)用也永遠(yuǎn)不會(huì)執(zhí)行灾杰。

4. final變量

Dart的變量可以用單詞final作為前綴艳吠,表明他們?cè)诔跏蓟蟛荒茉傩薷摹1仨氃诼暶鲿r(shí)就進(jìn)行初始化孽椰。

final origin1 = new Point(0, 0);
class Point1 {
  final x,y;
  Point1(this.x, this.y);
}

5. 相同與相等

所有對(duì)象都支持相等操作符==。這個(gè)操作符是在Object中定義的栏渺,用于檢測(cè)參數(shù)與接收者是否相同。

var aPoint = new Point(3,4);
var anotherPoint = new Point(3,4);
aPoint == anotherPoint; // 值為false

因?yàn)槊總€(gè)對(duì)象都有唯一標(biāo)識(shí)锐涯,一個(gè)對(duì)象只與它自己相同。我們可以重新 ==

operator == (p)=>x==p.x&&y==p.y;

dart:core庫(kù)中定義了一個(gè)identical()方法全庸,開(kāi)發(fā)者可以使用它來(lái)檢查兩個(gè)對(duì)象是否相同。

print(identical(origin,origin));
print(identical(aPoint,aPoint));
print(identical(aPoint, anotherPoint));
結(jié)果:
true
true
false

我們現(xiàn)在有足夠的知識(shí)來(lái)定義Object類的相等方法:

bool operator ==(other)=>identical(this, other);
···
  print(origin == origin);
  print(aPoint == aPoint);
  print(aPoint == aPoint1);
  print(aPoint == anotherPoint);
結(jié)果:
true
true
false
false

6. 類與父類

每個(gè)類都聲明了一組實(shí)例成員融痛,包括實(shí)例變量和各種實(shí)例方法。每個(gè)類(Object除外)繼承了父類的實(shí)例成員。除了Object類外所有的類都只有一個(gè)父類沛励,Object沒(méi)有父類,所以Dart類層次結(jié)構(gòu)形成了一個(gè)以O(shè)bject類為根的樹(shù)企蹭。這種結(jié)構(gòu)叫做單繼承徒河。

7. 抽象方法與抽象類

簡(jiǎn)單的聲明一個(gè)方法而不提供它的實(shí)現(xiàn)是有用的系馆,這種方法被稱為抽象方法。任何種類的實(shí)例方法都可以是抽象的顽照,不管是getter由蘑,setter,操作符或普通方法代兵。

有一個(gè)抽象方法的類本身就是一個(gè)抽象類尼酿,抽象類的聲明是通過(guò)在類名前加上前綴abstract。

8. 接口

每個(gè)類都隱含的定義了一個(gè)接口奢人,此接口描述了類的實(shí)例擁有哪些方法谓媒。很多編程語(yǔ)言都有正式的接口聲明,但在Dart中沒(méi)有何乎。這是不必要的句惯, 因?yàn)槲覀兪冀K可以定義一個(gè)抽象類來(lái)描述所需的接口。

abstract class CartesianPoint{
  get x;
  get y;
}
abstract class PolarPoint{
  get rho;
  get theta;
}
class Point implements CartesianPoint, PolarPoint{
  
}

在運(yùn)行時(shí)檢查對(duì)象時(shí)否符合某個(gè)接口是可行的

print(5 is int);
  print('x' is String);
  print(aPoint is CartesianPoint);
  print(new Point2() is CartesianPoint);
  print(new Point2() is PolarPoint);
結(jié)果:
true
true
false
true
true

請(qǐng)注意支救,is不檢查對(duì)象是否為某個(gè)類或子類的實(shí)例抢野。相反,is檢查對(duì)象的類是否明確的實(shí)現(xiàn)了某個(gè)接口(直接或間接)各墨。換句話說(shuō)指孤,我們并不關(guān)心對(duì)象是如何實(shí)現(xiàn)的,我們只在意它支持哪些接口贬堵。這是與其他有類似構(gòu)造的語(yǔ)言的關(guān)鍵區(qū)別恃轩。如果一個(gè)類希望模擬另一個(gè)類的接口黎做,則它并不局限于已有的實(shí)現(xiàn)蒸殿。

接口的繼承類似于類。類的隱含接口會(huì)繼承父類的隱含接口酥艳,同時(shí)會(huì)繼承父類實(shí)現(xiàn)的接口爬骤。同類已有充石, 接口可以重寫(xiě)父接口的實(shí)例方法;另外霞玄,某些重寫(xiě)可能是非法的赫冬,例如重寫(xiě)方法與被重寫(xiě)方法的參數(shù)不一致浓镜,或者試圖用普通方法重寫(xiě)getter或setter,反之亦然劲厌。

另外一個(gè)接口有多個(gè)父接口膛薛,不同的父接口之間可能會(huì)產(chǎn)生沖突。假設(shè)一個(gè)同名的方法在多個(gè)父接口中出現(xiàn)补鼻,而且它們的參數(shù)不一致哄啄,則在這種情況下,互相沖突的方法沒(méi)有一個(gè)會(huì)被繼承风范,如果一個(gè)父類定義了一個(gè)getter咨跌, 而另一個(gè)父類也定義了同名的普通方法,那么結(jié)果也是一樣的硼婿。

9. 對(duì)象的創(chuàng)建

Dart中的計(jì)算都是圍繞對(duì)象展開(kāi)的锌半。因?yàn)镈art是純面向?qū)ο蟮恼Z(yǔ)言,所以即使是最微不足道的Dart程序也會(huì)涉及對(duì)象的創(chuàng)建寇漫。

一個(gè)構(gòu)造函數(shù)的函數(shù)體在開(kāi)始前總是隱含的運(yùn)行父類構(gòu)造函數(shù)的函數(shù)體州胳。傳遞給父構(gòu)造函數(shù)的參數(shù)跟初始化列表中的調(diào)用父構(gòu)造函數(shù)的參數(shù)相同遍膜,他們不會(huì)重新計(jì)算。

9.1 重定向構(gòu)造函數(shù)

重定向構(gòu)造函數(shù)的目的是把執(zhí)行重定向到另一個(gè)構(gòu)造函數(shù)。在重定向構(gòu)造函數(shù)中巾兆,參數(shù)列表跟在一個(gè)冒號(hào)后面蔫磨, 并以this.id(蒲列。侥猩。。)的形式指定重定向到哪個(gè)構(gòu)造函數(shù)。這里是Point.polar()

Point.polar(this.rho, this.theta);
Point(a, b):this.polar(sqrt(a*a+b*b), atan(a/b));

9.2 工廠構(gòu)造函數(shù)

假設(shè)我們想要避免分配過(guò)多的點(diǎn)鹏往。我們想保留點(diǎn)的一份緩存,而不是每次請(qǐng)求都生成一個(gè)新的點(diǎn)。當(dāng)有人嘗試分配一個(gè)點(diǎn)時(shí)介褥,我們就檢查緩存中管是否存在相等的點(diǎn),如果有睛廊, 就返回那一個(gè)點(diǎn)邓馒。

一般來(lái)說(shuō)疏遏,構(gòu)造函數(shù)使上訴設(shè)想比較難實(shí)現(xiàn)。如我們前面描述的呈驶,在大多數(shù)編程語(yǔ)言中,構(gòu)造函數(shù)總是會(huì)分配一份新的實(shí)例虏辫。如果想使用緩存,那么必須提前考慮好娄昆,并確保你的點(diǎn)是通過(guò)一個(gè)方法來(lái)調(diào)用分配的谷浅,而這個(gè)方法通常叫做工廠方法撼玄。在Dart中掌猛, 任意構(gòu)造函數(shù)都可以被替換工廠方法竹海,并且對(duì)客戶是完全透明的坊萝。我們通過(guò)工廠構(gòu)造函數(shù)來(lái)做到這一點(diǎn)。

工廠構(gòu)造函數(shù)由factory前綴開(kāi)頭园细。他們看起來(lái)像普通的構(gòu)造函數(shù),但可能沒(méi)有初始化列表或初始化形式參數(shù)睦柴。相反,他們必須有一個(gè)返回一個(gè)對(duì)象的函數(shù)體狱窘。工廠構(gòu)造函數(shù)可以從緩存中返回對(duì)象,或選擇分配一個(gè)新的實(shí)例。它甚至可以創(chuàng)建一個(gè)不同類的實(shí)例(或者從緩存或其他數(shù)據(jù)結(jié)構(gòu)中查找他們)仗嗦。只要生成的對(duì)象符合當(dāng)前類的接口,則一切都會(huì)按預(yù)期執(zhí)行。

10. noSuchMethod()

Dart中的計(jì)算都是圍繞對(duì)象方法的調(diào)用蜓洪。如調(diào)用了一個(gè)不存在的方法, 則默認(rèn)的行為是拋出NoSuchMethodError錯(cuò)誤泉坐。但腕让,并非總?cè)绱恕?/p>

11. 常量對(duì)象與字段

有些對(duì)象是在編譯時(shí)就可以計(jì)算的常量。Dart還支持用戶定義的常量對(duì)象。常量對(duì)象的創(chuàng)建時(shí)使用const而不是new滑凉。const也是調(diào)用構(gòu)造函數(shù),但該構(gòu)造函數(shù)必須是常量構(gòu)造函數(shù)若未,且它的參數(shù)必須是常量。實(shí)際上,Dart要求常量構(gòu)造函數(shù)的參數(shù)必須是數(shù)字供屉,布爾量或者字符串疯特。

 const origin = const Point(0, 0);

12. 類方法

類方法是不依賴于個(gè)體實(shí)例的方法录别。通過(guò)類變量而引入的accessor都是類方法葫男,我們可以稱他們?yōu)轭恎etter和setter徘层。

13. 實(shí)例及其類與元類

每個(gè)對(duì)象都是一個(gè)類的實(shí)例。既然一切都是對(duì)象讯私,那么類也是對(duì)象拥褂;既然類是對(duì)象莫秆,那么它們本身也是某個(gè)類的實(shí)例缝驳。類的類通常被稱為元類霜医。Dart語(yǔ)言指定類的類型為T(mén)ype吗购,但沒(méi)有指明他們屬于哪個(gè)類踱启。

反射是唯一可靠的發(fā)現(xiàn)對(duì)象所屬類的方式研底。對(duì)象都支持一個(gè)名為runtimeType的getter埠偿,它默認(rèn)返回對(duì)象的所屬類。但是子類可隨意重寫(xiě)runtimeType

14. Object與其方法

class Object {
  const Object();
  external bool operator ==(other);
  external int get hashCode;

  external String toString();
  external dynamic noSuchMethod(Invocation invocation);
  external Type get runtimeType;
}

被標(biāo)記為external榜晦,表明他們的實(shí)現(xiàn)是在其他地方冠蒋。external機(jī)制用于聲明代碼的實(shí)現(xiàn)來(lái)自于外部。這些外部代碼可以有多種提供方式, 通過(guò)作為底層實(shí)現(xiàn)基礎(chǔ)的外部函數(shù)接口孽拷,或者甚至可能動(dòng)態(tài)的生成實(shí)現(xiàn)乃秀。

15. mixin

單繼承有很大的局限性愈污。Dart使用基于mixin的繼承系奉。
為了理解基于mixin的繼承,我們將考察一個(gè)簡(jiǎn)化版的集合類:

abstract class Collection{
  forEach(f);
  where(f);
  map(f);
}

我們真正想要的是Collection的主體,即大括號(hào)之間的部分程剥。此主體包含了類聲明本身提供的功能懒熙。它是Collection和父類的差異沙廉,我們把這個(gè)差異叫做mixin。

我們可以把mixin看作一個(gè)函數(shù),它接收一個(gè)父類s并返回一個(gè)新的擁有特定主體的S子類。我們把M與父類S的mixin操作寫(xiě)成S with M。顯然,S必須指定一個(gè)類。

但是我們?nèi)绾沃付╩ixin恨憎?通過(guò)指定一個(gè)類挺份,每個(gè)類都通過(guò)它的主體隱含定義了一個(gè)mixin腺逛,我們就是用它來(lái)對(duì)S執(zhí)行mixin操作。我們現(xiàn)在知道如何在不復(fù)制代碼的情況下定義CompoundWidget埋泵。

class CompoundWidget extends Widget with Collection {
  // ...CompoundWidget的實(shí)現(xiàn)
}

CompoundWidget的父類是Widget與Collection磺浙,即類Collection與父類Widget在mixin之后產(chǎn)生的一個(gè)新的匿名類。

15.1 mixin例子:表達(dá)式問(wèn)題

class Expression {}

class Addition extends Expression {
  var operand1, operand2;
  get eval => operand1.eval + operand2.eval;
}

class Subtraction extends Expression {
  var operand1, operand2;
  get eval => operand1.eval - operand2.eval;
}

class Number extends Expression {
  int val;
  get eval => val;
}

以上實(shí)現(xiàn)方式是有問(wèn)題的浮入。當(dāng)你想把這些表達(dá)式轉(zhuǎn)換為字符串時(shí)袋哼,你就需要添加另一個(gè)方法到原先的層次結(jié)構(gòu)中。類似功能的函數(shù)可能有無(wú)數(shù)個(gè)闸衫,你的類很快就會(huì)變得難以維護(hù)和使用涛贯。還有一個(gè)問(wèn)題就是不是所有想添加新功能的人都可以訪問(wèn)到原始源代碼。

使用mixin可以很好的解決這個(gè)問(wèn)題蔚出,下面的Dart代碼將展示如何實(shí)現(xiàn)弟翘。我們從三個(gè)初始數(shù)據(jù)類型開(kāi)始,只是我們不把它們定義為抽象類骄酗,因?yàn)樗麄儾⒉皇俏覀冏罱K要實(shí)例化的數(shù)據(jù)類型稀余。他們被用來(lái)定義類型的結(jié)構(gòu)及對(duì)應(yīng)的構(gòu)造函數(shù)。

library abstract_expressions;
abstract class AbstractExpression{}
abstract class AbstractAddition {
  var operand1, operand2;
  AbstractAddition(this.operand1, this.operand2);

}

abstract class AbstractSubtraction {
  var operand1, operand2;
  AbstractSubtraction(this.operand1, this.operand2);

}

abstract class AbstractNumber {
  var val;
  AbstractNumber(this.val);
}

現(xiàn)在趋翻,我們定義第一個(gè)功能:求職器滚躯。我們將通過(guò)一組mixin類來(lái)做到這一點(diǎn)。

library evaluator;
abstract class ExpressionWithEval{
  get eval;
}
abstract class AdditionWithEval {
  get operand1;
  get operand2;
  get eval => operand1.eval + operand2.eval;

}

abstract class SubtractionWithEval {
  get operand1;
  get operand2;
  get eval => operand1.eval - operand2.eval;
}

abstract class NumberWithEval {
  get val;
  get eval => val;
}

以上求值器完全獨(dú)立于類型層次結(jié)構(gòu)嘿歌,注意我們沒(méi)有導(dǎo)入哪怕一個(gè)依賴掸掏。我們的客戶端應(yīng)該使用的時(shí)機(jī)類型是單獨(dú)定義的:

library expressions;
import 'abstract_expressions.dart';
import 'evaluator.dart';
abstract class Expression = AbstractExpression with ExpressionWithEval;
class Addition = AbstractAddition with AdditionWithEval implements Expression;
class Subtraction = AbstractSubtraction with SubtractionWithEval implements Expression;
class Number = AbstractNumber with NumberWithEval implements Expression;

每個(gè)具體的AST(抽象語(yǔ)法樹(shù))類型都被定義成一個(gè)mixin應(yīng)用,即用相應(yīng)的求職器mixin來(lái)擴(kuò)展對(duì)應(yīng)的抽象數(shù)據(jù)類宙帝。我們可以給expressions庫(kù)提那家一個(gè)main函數(shù)丧凤,用來(lái)構(gòu)建一個(gè)簡(jiǎn)單的表達(dá)式樹(shù)。這是可能的步脓,因?yàn)楦鱾€(gè)AST節(jié)點(diǎn)類的父類構(gòu)造函數(shù)都隱含的為他們定義了合成的構(gòu)造函數(shù)愿待。

main() {
  var e = new Addition(new Addition(new Number(4), new Number(2)), 
  new Subtraction(new Number(10), new Number(7)));
print('$e = ${e.eval}');
}
結(jié)果:
Instance of 'Addition' = 9

為什么將抽象類型和實(shí)際類型分離?expressions庫(kù)即實(shí)際類型的作用是通過(guò)mixin應(yīng)用連接各個(gè)組件來(lái)定義我們整個(gè)系統(tǒng)靴患。abstractExpressions庫(kù)即抽象類型的作用是定義我們的AST節(jié)點(diǎn)的形式仍侥。保持他們的獨(dú)立,使我們?cè)贁U(kuò)展系統(tǒng)時(shí)只需對(duì)expressions庫(kù)做修改鸳君,無(wú)需觸碰我們的數(shù)據(jù)類型的表現(xiàn)形式农渊。

一般的模式是,每個(gè)具體類都基于擴(kuò)展一個(gè)定義其數(shù)據(jù)表示的抽象類或颊,同時(shí)用一系列的mixin來(lái)代表該數(shù)據(jù)類型所具備的功能砸紊。這種方法之所以有效, 是因?yàn)槲覀優(yōu)槊總€(gè)類型和功能的組合單獨(dú)定義了一個(gè)mixin囱挑。例如醉顽,上面的每個(gè)eval方法都是在各自的mixin類中定義的。如果我們想增加一種類型平挑,那么我們可以獨(dú)立添加游添。下面我們將增加乘法的AST節(jié)點(diǎn):

library multiplication;
abstract class AbstractMultiplication {
  var operand1, operand2;
  AbstractMultiplication(this.operand1, this.operand2); 
}

這個(gè)添加操作同樣完全獨(dú)立于原先的類層次結(jié)構(gòu)和已有功能∠挡荩現(xiàn)在我們還需要定義乘法是如何求值得驻啤。我們可以單獨(dú)定義一個(gè)庫(kù):

library multiplication_evaluator;
abstract class MultiplicationWithEval {
  get operand1;
  get operand2;
  get eval => operand1.eval * operand2.eval;

}

再次栖榨,以上代碼是獨(dú)立的,我們還需要在expressions庫(kù)中創(chuàng)建相應(yīng)的具體類母廷。

import 'abstract_expressions.dart';
import 'evaluator.dart';
class Multiplication = AbstractMultiplication with MultiplicationWithEval implements Expression;

然后在main方法中計(jì)算:

main() {
  var e = new Multiplication(new Addition(new Number(4), new Number(2)), 
  new Subtraction(new Number(10), new Number(7)));
  print('$e = ${e.eval}');
}
結(jié)果:
Instance of 'Multiplication' = 18

所打印的內(nèi)容信息量比我們想象的藥少石抡,因?yàn)閑的打印使用的是從Object繼承來(lái)的默認(rèn)toString實(shí)現(xiàn)檐嚣。為了解決這個(gè)問(wèn)題,我們可以把一個(gè)專門(mén)的toString實(shí)現(xiàn)添加到我們的類層次結(jié)構(gòu)中啰扛。

library string_converter;

abstract class ExpressionWithStringConversion {
  toString();
}

abstract class AdditionWithStringConversion {
  get operand1;
  get operand2;
  toString() => '($operand1 + $operand2)';
}

abstract class SubtractionWithStringConversion {
  get operand1;
  get operand2;
  toString() => '($operand1 - $operand2)';
}

abstract class NumberWithStringConversion {
  get val;
  toString() => '$val';
}

abstract class MultiplicationWithStringConversion {
  get operand1;
  get operand2;
  get eval => '($operand1 * $operand2)';
}

再次嚎京,我們按照每一個(gè)功能,類型的組合定義一種mixin的方式隐解。這一次鞍帝,我們知道類層次結(jié)構(gòu)涉及了乘法,我們把與它對(duì)應(yīng)的實(shí)現(xiàn)與其他實(shí)現(xiàn)都放到同一個(gè)庫(kù)中煞茫。同樣帕涌,我們要改進(jìn)expressions庫(kù)來(lái)整合新的功能。

library expressions;

import 'abstract_expressions.dart';
import 'evaluator.dart';
import 'multiplication.dart';
import 'multiplication_evaluator.dart';
import 'string_converter.dart';
abstract class Expression = AbstractExpression with ExpressionWithEval, ExpressionWithStringConversion;
class Addition = AbstractAddition with AdditionWithEval, AdditionWithStringConversion implements Expression;
class Subtraction = AbstractSubtraction with SubtractionWithEval, SubtractionWithStringConversion implements Expression;
class Number = AbstractNumber with NumberWithEval, NumberWithStringConversion implements Expression;
class Multiplication = AbstractMultiplication with MultiplicationWithEval, MultiplicationWithStringConversion implements Expression;
// main函數(shù)不變续徽,但它會(huì)打印一個(gè)描述更好的樹(shù)
Instance of 'Multiplication' = ((4 + 2) * (10 - 7))

我們可以根據(jù)需要把擴(kuò)展過(guò)程繼續(xù)下去蚓曼。只要你想,你就可以添加多種類型钦扭、功能纫版,只要像上面一樣, 把要使用的類型的最終形式定義成mixin應(yīng)用客情。每個(gè)類型所對(duì)應(yīng)的每個(gè)功能其弊,都通過(guò)一個(gè)獨(dú)立的mixin類來(lái)定義。添加新功能確實(shí)需要修改這些mixin應(yīng)用膀斋,但是這看起來(lái)更像調(diào)整你的make文件以包括新加的功能或類型(make是c語(yǔ)言開(kāi)發(fā)中常用的一種構(gòu)建工具)梭伐。如果新類型和功能都是獨(dú)立定義的,那么我們始終可以定義一個(gè)mixin吧新功能獨(dú)立地添加到新類型上仰担,讓他們良好的混合在一起糊识。

mixin使類的代碼以模塊化方式重用,而不依賴于它在類層次結(jié)構(gòu)中的位置惰匙。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末技掏,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子项鬼,更是在濱河造成了極大的恐慌,老刑警劉巖劲阎,帶你破解...
    沈念sama閱讀 219,539評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件绘盟,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)龄毡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門(mén)吠卷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人沦零,你說(shuō)我怎么就攤上這事祭隔。” “怎么了路操?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,871評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵疾渴,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我屯仗,道長(zhǎng)搞坝,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,963評(píng)論 1 295
  • 正文 為了忘掉前任魁袜,我火速辦了婚禮桩撮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘峰弹。我一直安慰自己店量,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,984評(píng)論 6 393
  • 文/花漫 我一把揭開(kāi)白布鞠呈。 她就那樣靜靜地躺著垫桂,像睡著了一般。 火紅的嫁衣襯著肌膚如雪粟按。 梳的紋絲不亂的頭發(fā)上诬滩,一...
    開(kāi)封第一講書(shū)人閱讀 51,763評(píng)論 1 307
  • 那天,我揣著相機(jī)與錄音灭将,去河邊找鬼疼鸟。 笑死,一個(gè)胖子當(dāng)著我的面吹牛庙曙,可吹牛的內(nèi)容都是我干的空镜。 我是一名探鬼主播,決...
    沈念sama閱讀 40,468評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼捌朴,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼吴攒!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起砂蔽,我...
    開(kāi)封第一講書(shū)人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤洼怔,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后左驾,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體镣隶,經(jīng)...
    沈念sama閱讀 45,850評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡极谊,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,002評(píng)論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了安岂。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片轻猖。...
    茶點(diǎn)故事閱讀 40,144評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖域那,靈堂內(nèi)的尸體忽然破棺而出咙边,到底是詐尸還是另有隱情,我是刑警寧澤次员,帶...
    沈念sama閱讀 35,823評(píng)論 5 346
  • 正文 年R本政府宣布败许,位于F島的核電站,受9級(jí)特大地震影響翠肘,放射性物質(zhì)發(fā)生泄漏檐束。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,483評(píng)論 3 331
  • 文/蒙蒙 一束倍、第九天 我趴在偏房一處隱蔽的房頂上張望被丧。 院中可真熱鬧,春花似錦绪妹、人聲如沸甥桂。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,026評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)黄选。三九已至,卻和暖如春婶肩,著一層夾襖步出監(jiān)牢的瞬間办陷,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,150評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工律歼, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留民镜,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,415評(píng)論 3 373
  • 正文 我出身青樓险毁,卻偏偏與公主長(zhǎng)得像制圈,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子畔况,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,092評(píng)論 2 355