Go 程式設計教學:介面 (Interface)

PUBLISHED ON OCT 8, 2017 — PROGRAMMING

在前一章中,我們介紹 Go 的物件系統。然而,我們在前文的最後面提到,Go 缺乏繼承的機制,我們無法透過繼承來達到多型的效果。為了處理這個議題,Go 引入介面的機制,也就是本文的主題。

介面

介面 (interface) 是只有方法宣告,但缺乏方法實作的型別。以 Point 類別為例,其介面如下:

在這個介面中,我們宣告了四個方法 (method)。使用 IPoint 的開頭 I 只是為了要和原來的 Point 型別區別,不是強制性規定。宣告介面後,要再自行實作類別以滿足此介面。我們來看如何用介面解決我們於前文中所碰到的議題:

在本例中,我們建立一個以 IPoint 為型別的切片 points,並分別加入兩個相異的變數 p1p2,由於這兩個變數都符合我們宣告的介面,不會引發程式的錯誤。最後,我們分別呼叫兩個變數的 XY 方法,印出點所在的位置。

嵌入介面

如同類別,我們也可以在介面中嵌入另一個介面,如下例:

在本例中,IPoint3D 除了繼承 IPoint 所有的方法宣告外,另外加入了自己特有的 ZSetZ 方法。

介面的應用實例

初學物件導向程式的程式設計者,往往無法馬上體會介面的用處。實際上,介面相當重要;對於 Go 或其他靜態型別語言來說,一方面要滿足型別檢查,一方面希望程式更加靈活;透過介面,可使得靜態語言程式某種程度上像動態語言程式般,像是要在同一個陣列中加入不同型別的物件時,就可以透過介面來處理。因為介面是在沒有繼承卻能實作子類型 (subtyping) 的手法。如果讀者去讀一些使用 Java 或 C# 等語言撰寫物件導向程式和設計模式的書,會發現這些書大量地使用介面來撰寫更易於維護的程式碼,反而較少使用繼承。

在這裡我們舉一個設計模式 (design pattern) 的例子,設計模式是一套讓物件導向程式更易於維護的方法。在本例中,我們實作 Builder 模式,此模式的用意在於建立一群相關的物件,將建立的過程集中在同一個 Builder 函式中,使程式碼易於維護。如下例:

運算子重載

運算子重載是指物件可以用內建運算子來操作,如同使用內建型別般。Go 的設計是避免過多的語法魔術,也不支援運算子重載。少數的例外是 String 函式,滿足 String 函式即可用 fmt 套件內的函式將物件印出。以下為實例:

空介面

除了先前所介紹的介面外,還有另外一種沒有任何方法宣告的空介面,寫做 interface{},空介面也是一種特殊型別,該型別可放入任何值;基本上,空介面和介面使用的時機不同,要將其視為兩種不同的概念。以下是一個合法的 Go 程式:

然而,空介面並不代表我們可以不理會原本的型別系統,例如,以下程式會引發錯誤:

我們必需要透過型別判定 (type assertion),告訴 Go 程式該變數實際的型別後,才能使用該變數的值:

在進行型別判定時,若不確定型別是否正確,則要進行額外的檢查。以下為實例:

如果無法預先知道變數的型別,可用 switch 敘述進行動態判定。實例如下:

其實,Go 標準函式庫中也有使用空介面,例如,在 fmt 套件的 PrintfSprintf 函式中,以下是實例:

空介面的設計,某種程度上可使 Go 程式更靈活;然而,空介面會略去 Go 的型別檢查,使程式喪失靜態型別所帶來的益處。筆者的建議是,謹慎地使用空介面,只有在自己明確知道為什麼要用空介面時才使用。

comments powered by Disqus