結構型態(Structural typing)


對於物件所共用擁有的行為,如果是同一種物件,可以將共同行為抽離設計至父類別,由子類別實作共同行為,對於非同種物件,可以將之抽離出來設計為特徵(Trait),讓不同的類別之間實作該特徵。

然而有時候,你就是有像這樣的需求:
class Duck {
def quack = "呱...呱..."
}

class Man {
def quack = "呱~~呱~~"
}

也許這個人是穿著鴨子吉祥物服裝要呱呱叫也說不一定,你如何設計一個方法,讓鴨子與人都可以呱呱叫?如果你並不想大費周章,設計一個具有quack方法的特徵的話!

如果是動態語言,如JavaScript、Python、Ruby等,會採用 鴨子型態(Duck typing),也就是「如果它走路像個鴨子,游起來像個鴨子,叫聲也像鴨子,那它就是鴨子!」在Scala中,你可以使用結構型態(Structural typing)來達到類似鴨子型態的目的:
def doQuack(d: {def quack: String}) {
println(d.quack)
}

doQuack(new Duck) // "呱...呱...
doQuack(new Man) // "呱~~~呱~~~

在 上例中,doQuack()方法接受任何型態的物件,只要它具有quack的行為,即使Duck與Man彼此沒有關係,但它們確實都具有quack的行 為,所以可以傳入doQuack()方法之中。正如上例所示,結構型態正是在解決物件間即不是同一種物件,也非實作某個特徵,但又確實擁有共同行為的情 況。

你可以在{ } 之間定義任何的方法外觀,例如:
class Duck {
def quack = "呱...呱..."
def swim = "划...划..."
}

class Man {
def quack = "呱~~~呱~~~"
def swim = "划~~~划~~~"
}

def doQuack(d: {def quack: String; def swim: String}) {
println(d.quack)
println(d.swim)
}

doQuack(new Duck)
doQuack(new Man)

如果要定義的方法較多時,可以使用type為結構型態取個別名:
class Duck {
def quack = "呱...呱..."
def swim = "划...划..."
def walk = "搖...搖..."
}

class Man {
def quack = "呱~~~呱~~~"
def swim = "划~~~划~~~"
def walk = "搖~~~搖~~~"
}

type DuckLike = {
def quack: String
def swim : String
def walk : String
}

def doQuack(d: DuckLike) {
println(d.quack)
println(d.swim)
println(d.walk)
}

doQuack(new Duck)
doQuack(new Man)

你也可以限制必須是某個類別的子類別,例如:
class Animal

class Man extends Animal {
def quack = "呱~~~呱~~~"
}

class ToyDuck {
def quack = "呱***呱***"
}

type DuckLike = Animal {
def quack: String
}

def doQuack(d: DuckLike) {
println(d.quack)
}

doQuack(new Man)
doQuack(new ToyDuck) // 編譯錯誤,type mismatch

在上例中,限制必須是動物才可以丟入doQuack()呱呱叫,玩具鴨不是動物,所以不可以丟入doQuack()中。有趣的是,結構型態也可以進行擴充:
type Quacklike = { def quack: String }
type Ducklike = Quacklike { def swim: String; def walk: String }

在上例中,Ducklike將必須是具有quack、swim、walk的型態。如果已給定結構型態別名,則擴充時所使用的語法為使用with:
type Quacklike = { def quack: String }
type Walklike = { def walk: String }
type Swimlike = { def swim: String }
type Ducklike = Quacklike with Walklike with Swimlike

結構型態就是一種型態,自然也就可以用來對陣列或群集(Collection)作參數化,例如在下面的ducks陣列中,可以放入任何種類的物件,只要它們具有quack的行為就可以:
class Duck {
def quack = "呱...呱..."
}

class Toy {
def quack = "呱...呱..."
}

class Man {
def quack = "呱~~~呱~~~"
}

type DuckLike = {
def quack: String
}

val ducks: Array[DuckLike] = Array[DuckLike](new Duck, new Toy, new Man)
ducks.foreach(d => println(d.quack))