繼承(extends)是面向?qū)ο箝_(kāi)發(fā)方法中非常重要的一個(gè)特征务甥,繼承體現(xiàn)著現(xiàn)實(shí)世界中“一般”與“特殊”的關(guān)系达椰。對(duì)于擁有“一般”性質(zhì)的類我們稱之為“父類”或者“超類”耕蝉,擁有“特殊”性質(zhì)的類我們稱之為“子類”铐刘。比如“動(dòng)物”和“鳥(niǎo)”歧杏,動(dòng)物是一般的概念镰惦,鳥(niǎo)是特殊的概念,可以通過(guò)“鳥(niǎo)是一種特殊的動(dòng)物”這句話邏輯是否成立來(lái)判斷繼承關(guān)系是否成立犬绒。
1. Dart中的繼承
與Java語(yǔ)言類似旺入,Dart語(yǔ)言標(biāo)榜自己為“單繼承”,也就是一個(gè)類只能有一個(gè)直接的父類凯力。如果一個(gè)類沒(méi)有顯式地聲明父類茵瘾,那么它會(huì)默認(rèn)繼承Object
類。此外Dart語(yǔ)言又提供了混入(Mixin)的語(yǔ)法咐鹤,允許子類在繼承父類時(shí)混入其他類拗秘。關(guān)于混入(Mixin)的理解,請(qǐng)參照本章第7節(jié)的內(nèi)容理解祈惶。
Dart語(yǔ)言中使用extends
作為繼承關(guān)鍵字雕旨,子類會(huì)繼承父類的數(shù)據(jù)和函數(shù)。
示例代碼:貓類繼承動(dòng)物類
main() {
var a = new Animal();
var b = new Cat();
a.name = "動(dòng)物";
b.name = "貓";
b.color = "黑色";
a.eat();
b.eat();
b..climb();
}
class Animal{
String name;
void eat(){
print("${name}:進(jìn)食");
}
}
class Cat extends Animal{
String color;
void climb(){
print("${color}的${name}:爬樹(shù)");
}
}
運(yùn)行結(jié)果
2. 函數(shù)重寫
重寫在面向?qū)ο笾畜w現(xiàn)的現(xiàn)實(shí)意義是“子類與父類在同一行為上有不同的表現(xiàn)形式”捧请。同Java語(yǔ)言類似凡涩,Dart語(yǔ)言也支持函數(shù)的重寫,子類重寫父類的函數(shù)后疹蛉,對(duì)象調(diào)用的即為子類的同名函數(shù)活箕。
演示示例:在第1節(jié)的例子的Cat類中重寫Animal類的eat函數(shù)。
main() {
var a = new Animal();
var b = new Cat();
a.name = "動(dòng)物";
b.name = "貓";
b.color = "黑色";
a.eat();
b.eat();
b..climb();
}
class Animal{
String name;
void eat(){
print("${name}:進(jìn)食");
}
}
class Cat extends Animal{
String color;
@override
void eat(){//子類重寫父類的eat方法
print("${color}的${name}:吃魚(yú)");
}
void climb(){
print("${color}的${name}:爬樹(shù)");
}
}
運(yùn)行結(jié)果
3. 操作符重寫
同C++語(yǔ)言類似,Dart語(yǔ)言支持操作符的重寫,常規(guī)的四則運(yùn)算和比較運(yùn)算符都可以進(jìn)行重寫堕绩,其中只有!=不可重寫,a!=b相當(dāng)于!(a==b)的語(yǔ)法糖巡通。
示例代碼:
重寫了==和+的Rectangle類,當(dāng)兩個(gè)對(duì)象的width和height一致認(rèn)為它們是“相等”的,兩個(gè)Rectangle相加則將兩者的width和height進(jìn)行相加后得到新的Rectangle對(duì)象
main() {
var a = new Rectangle(10,10);
var b = new Rectangle(5, 5);
var c = new Rectangle(10, 10);
print(a == b);
print(a == c);
var d = a + b;
print("${a.width}:${a.height}");
print("${d.width}:${d.height}");
print(a == d);
}
class Rectangle{
int width;
int height;
Rectangle(this.width,this.height){}
@override
bool operator ==(dynamic other) {
if(other is! Rectangle){
return false;
}
Rectangle temp = other;
return (temp.width == width && temp.height == height);
}
@override
Rectangle operator +(dynamic other){
if(other is! Rectangle){
return this;
}
Rectangle temp = other;
return new Rectangle( this.width + temp.width, this.height + temp.height);
}
}
運(yùn)行結(jié)果
4. 抽象類
抽象abstract是面向?qū)ο笾械囊粋€(gè)非常重要的概念,通常用于描述父類擁有一種行為但無(wú)法給出細(xì)節(jié)實(shí)現(xiàn)版仔,而需要通過(guò)子類來(lái)實(shí)現(xiàn)抽象的細(xì)節(jié)。這種情況下父類被定義為抽象類误墓,子類繼承父類后實(shí)現(xiàn)其中的抽象方法。
同Java語(yǔ)言類似益缎,Dart中的抽象類也使用abstract來(lái)實(shí)現(xiàn)谜慌,不過(guò)抽象函數(shù)無(wú)需使用abstract,直接給出定義不給出方法體實(shí)現(xiàn)即可
抽象類中可以有數(shù)據(jù)莺奔,可以有常規(guī)函數(shù)欣范,可以有抽象函數(shù)变泄,但抽象類不能實(shí)例化。子類繼承抽象類后必須實(shí)現(xiàn)其中的抽象函數(shù)恼琼。
演示示例:Dog類繼承Animal抽象類
main(){
var d = new Dog();
d.name = "dog";
d.eat();
d.display();
}
abstract class Animal{
String name; //數(shù)據(jù)
void display(){ //普通函數(shù)
print("名字是:${name}");
}
void eat(); //抽象函數(shù)
}
class Dog extends Animal{
@override
void eat() { //實(shí)現(xiàn)抽象函數(shù)
print("eat");
}
}
運(yùn)行結(jié)果
5. 接口
Dart語(yǔ)言中沒(méi)有接口(interface)的關(guān)鍵字妨蛹,但是有實(shí)現(xiàn)(implements)關(guān)鍵字,Dart中可以將類(是否為抽象無(wú)關(guān))當(dāng)做隱式接口
直接使用晴竞,當(dāng)需要使用接口時(shí)蛙卤,可以聲明類來(lái)代替。(個(gè)人認(rèn)為抽象類更適合對(duì)接口的理解)
演示示例:
在第4節(jié)的示例基礎(chǔ)上噩死,添加swimable類和walkable類作為接口使用颤难,Dog類繼承Animal類并實(shí)現(xiàn)swimable和walkable接口
main(){
var d = new Dog();
d.name = "dog";
d.eat();
d.display();
d.swim();
d.walk();
}
abstract class Animal{
String name;
void display(){
print("名字是:${name}");
}
void eat(); //抽象方法
}
abstract class swimable{ //抽象類作為接口
void swim();
}
class walkable{ //普通類作為接口
void walk(){}
}
class Dog extends Animal implements swimable, walkable{
@override
void eat() {
print("eat");
}
@override
void swim() {
print("swim");
}
@override
void walk() {
print("walk");
}
}
運(yùn)行結(jié)果
6. 泛型繼承
上一章中,提到了泛型編程中的繼承問(wèn)題已维,在使用泛型編程時(shí)也可以使用extends
關(guān)鍵字約束輸入泛型的類型行嗤。
演示示例
被泛型約束的函數(shù),當(dāng)傳入泛型int時(shí)垛耳,只能計(jì)算整數(shù)加法栅屏,且在聲明對(duì)象x時(shí)泛型只能傳入num類型及其子類
main(){
var a = 3;
var b = 3.14;
var c = 4;
var x = DataUtil<int>();
var f = x.addition(a, c);
print(f);
}
class DataUtil<T extends num>{
T addition(T a, T b){
return a+b;
}
}
運(yùn)行結(jié)果:7
7. 混入(mixin)
本章節(jié)內(nèi)容借鑒了簡(jiǎn)書(shū)用戶Vadaski的文章【譯】Dart | 什么是Mixin,在此對(duì)其表示感謝堂鲜。
問(wèn)題
我們來(lái)看下面這張關(guān)于動(dòng)物(Animal)既琴,哺乳動(dòng)物(Mammal),鳥(niǎo)(Bird)和魚(yú)(Fish)的繼承關(guān)系圖
這里有一個(gè)名為Animal的超類泡嘴,它有三個(gè)子類(Mammal甫恩,Bird和Fish)。在底部酌予,我們有具體的一些子類磺箕。
小方塊代表行為。例如抛虫,
- 黃色方塊表示具有此行為的類的實(shí)例可以步行(walk)松靡。
- 藍(lán)色方塊表示具有此行為的類的實(shí)例可以游泳(swim)。
- 灰色方塊表示具有此行為的類的實(shí)例可以飛行(fly)建椰。
有些動(dòng)物有共同的行為:貓(Cat)和鴿子(Dove)都可以行走雕欺,但是貓不能飛。
這些行為與此分類正交棉姐,因此我們無(wú)法在超類中實(shí)現(xiàn)這些行為屠列。
在Java語(yǔ)言中我們可以借助接口(Interface)來(lái)實(shí)現(xiàn)相關(guān)的設(shè)計(jì),Dart中也可以利用隱式接口來(lái)完成相應(yīng)的設(shè)計(jì)伞矩。
如果不同的子類在某種行為上表現(xiàn)的都不相同笛洛,那么使用接口來(lái)實(shí)現(xiàn)設(shè)計(jì)是一種良好的設(shè)計(jì)。
但如果不同的子類在實(shí)現(xiàn)某種行為上有著同樣的表現(xiàn)乃坤,那么使用接口來(lái)實(shí)現(xiàn)設(shè)計(jì)可能會(huì)造成代碼的冗余苛让。(接口實(shí)現(xiàn)強(qiáng)制重寫函數(shù))
所以除了上述兩種方式沟蔑,我們也可以利用混入方式(Mixin)來(lái)完成相應(yīng)的設(shè)計(jì)
實(shí)現(xiàn)
對(duì)三種行為分別定義三個(gè)類描述它們,分別是Walker狱杰,Swimmer和Flyer
class Walker {
void walk() {
print("I'm walking");
}
}
class Swimmer {
void swim() {
print("I'm swimming");
}
}
class Flyer{
void fly() {
print("I'm flying");
}
}
如果不想這三個(gè)類被實(shí)例化瘦材,可以使用抽象類+工廠方式定義
abstract class Walker {
factory Walker._() => null;
void walk() {
print("I'm walking");
}
}
使用混入的關(guān)鍵字是with
,它的后面可以跟隨一個(gè)或多個(gè)類名
class Cat extends Mammal with Walker {}
class Dove extends Bird with Walker, Flyer {}
使用時(shí)允許Cat和Dove調(diào)用Mixin的walk函數(shù)仿畸,不允許Cat調(diào)用未Mixin的Fly函數(shù)
main(){
Cat cat = Cat();
Dove dove = Dove();
cat.walk();
dove.walk();
dove.fly();
}
理解
如果Mixin的類和繼承類食棕,或者混入的類之間有相同的方法,在調(diào)用時(shí)會(huì)產(chǎn)生什么樣的情況颁湖,看下面的例子宣蠕。
演示示例
AB和BA類都使用A和B Mixin繼承至P類,但順序不同甥捺。A抢蚀,B和P類都有一個(gè)名為getMessage的方法。
class A {
String getMessage() => 'A';
}
class B {
String getMessage() => 'B';
}
class P {
String getMessage() => 'P';
}
class AB extends P with A, B {}
class BA extends P with B, A {}
void main() {
String result = '';
AB ab = AB();
result += ab.getMessage();
BA ba = BA();
result += ba.getMessage();
print(result);
}
運(yùn)行結(jié)果: BA
為什么會(huì)產(chǎn)生這個(gè)結(jié)果镰禾?
Dart中的Mixins通過(guò)創(chuàng)建一個(gè)新類來(lái)實(shí)現(xiàn)皿曲,該類將mixin的實(shí)現(xiàn)層疊在一個(gè)超類之上以創(chuàng)建一個(gè)新類 ,它不是“在超類中”吴侦,而是在超類的“頂部”屋休,因此如何解決查找問(wèn)題不會(huì)產(chǎn)生歧義。
— Lasse R. H. Nielsen on StackOverflow.'
實(shí)際上备韧,這段代碼
class AB extends P with A, B {}
class BA extends P with B, A {}
在語(yǔ)義上等同于
class PA = P with A;
class PAB = PA with B;
class AB extends PAB {}
class PB = P with B;
class PBA = PB with A;
class BA extends PBA {}
最終的繼承關(guān)系如下圖所示
很顯然劫樟,最后被繼承的類重寫了上面所有的getMessage方法,可以理解為處于Mixin結(jié)尾的類將前面的getMessage方法都覆蓋(override)掉了
mixin應(yīng)用程序?qū)嵗念愋褪鞘裁矗?/p>
通常织堂,它是其超類的子類型叠艳,也是mixin名稱本身表示的類的子類型,即原始類的類型易阳。
— dartlang.org
所以這意味著這個(gè)程序的運(yùn)行結(jié)果全部為true
class A {
String getMessage() => 'A';
}
class B {
String getMessage() => 'B';
}
class P {
String getMessage() => 'P';
}
class AB extends P with A, B {}
class BA extends P with B, A {}
void main() {
AB ab = AB();
print(ab is P); //true
print(ab is A); //true
print(ab is B); //true
BA ba = BA();
print(ba is P); //true
print(ba is A); //true
print(ba is B); //true
}
最后
完整的實(shí)現(xiàn)示例
abstract class Animal {}
abstract class Mammal extends Animal {}
abstract class Bird extends Animal {}
abstract class Fish extends Animal {}
abstract class Walker {
factory Walker._() => null;
void walk() {
print("I'm walking");
}
}
abstract class Swimmer {
factory Swimmer._() => null;
void swim() {
print("I'm swimming");
}
}
abstract class Flyer {
factory Flyer._() => null;
void fly() {
print("I'm flying");
}
}
class Dolphin extends Mammal with Swimmer {}
class Bat extends Mammal with Walker, Flyer {}
class Cat extends Mammal with Walker {}
class Dove extends Bird with Walker, Flyer {}
class Duck extends Bird with Walker, Swimmer, Flyer {}
class Shark extends Fish with Swimmer {}
class FlyingFish extends Fish with Swimmer, Flyer {}
Mixin類圖