Go 語言面向對象教程 —— 接口篇:接口定義與實現

接口在 Go 語言中有着相當重要的地位,若是說 goroutine 和 channel 是支撐起 Go 語言併發模型的基石,那麼接口就是 Go 語言整個類型系統的基石。Go 語言的接口不僅僅只是接口,下面就讓咱們一步步來探索 Go 語言的接口特性。php

PHP 的接口實現

和類的實現類似,Go 語言的接口和其餘語言中提供的接口概念徹底不一樣。以 PHP 爲例,接口主要做爲不一樣類之間的契約存在,好比 Laravel 框架就直接將接口稱做契約(Contract),對契約的實現是強制的,體如今具體的細節上就是若是一個類實現了某個接口,就必須實現該接口聲明的全部方法,這個叫「履行契約」:編程

// 聲明一個'iTemplate'接口interface iTemplate{ public function setVariable($name, $var); public function getHtml($template);}
// 實現接口// 下面的寫法是正確的class Template implements iTemplate{ private $vars = array();
    public function setVariable($name, $var)    { $this->vars[$name] = $var; }
    public function getHtml($template)    { foreach($this->vars as $name => $value) { $template = str_replace('{' . $name . '}', $value, $template); }
return $template; }}

這個時候,若是有另外有一個接口 iTemplate2 聲明瞭與 iTemplate 徹底同樣的接口方法,甚至名字也叫 iTemplate 只不過位於不一樣的命名空間下,編譯器也會認爲上面的類 Template 只實現了 iTemplate 而沒有實現 iTemplate2 接口。微信

這在咱們以前的認知中是理所固然的,不管是類與類之間的繼承,仍是類與接口之間的實現,在 PHP 這種單繼承語言中,存在着嚴格的層級關係,一個類只能直接繼承自一個父類,一個類也只能實現指定的接口,若是沒有顯式聲明繼承自某個父類或者實現某個接口,那麼這個類就與該父類或者該接口沒有任何關係。session

咱們把這種接口稱爲侵入式接口,所謂「侵入式」指的是實現類必須明確聲明本身實現了某個接口。這種實現方式雖然足夠明確和簡單明瞭,但也存在一些問題,尤爲是在設計標準庫的時候,由於標準庫必然涉及到接口設計,接口的需求方是業務實現類,只有具體編寫業務實現類的時候才知道須要定義哪些方法,而在此以前,標準庫的接口就已經設計好了,咱們要麼按照約定好的接口進行實現,若是沒有合適的接口須要本身去設計,這裏的問題就是接口的設計和業務的實現是分離的,接口的設計者並不能老是預判到業務方要實現哪些功能,這就形成了設計與實現的脫節。併發

接口的過度設計會致使某些聲明的方法實現類徹底不須要,若是設計的太簡單又會致使沒法知足業務的需求,這確實是一個問題,並且脫離了用戶使用場景討論這些並無意義,以 PHP 自帶的 SessionHandlerInterface 接口爲例,該接口聲明的接口方法以下:框架

SessionHandlerInterface { /* 方法 */ abstract public close ( void ) : bool abstract public destroy ( string $session_id ) : bool abstract public gc ( int $maxlifetime ) : int abstract public open ( string $save_path , string $session_name ) : bool abstract public read ( string $session_id ) : string abstract public write ( string $session_id , string $session_data ) : bool}

用戶自定義的 Session 管理器須要實現該接口,也就是要實現該接口聲明的全部方法,可是實際在作業務開發的時候,某些方法其實並不須要實現,好比若是咱們基於 Redis 或 Memcached 做爲 Session 存儲器的話,它們自身就包含了垃圾回收機制,因此 gc 方法根本不須要實現,又好比 close 方法對於大部分驅動來講,也是沒有什麼意義的。this

正是由於這種不合理的設計,因此在編寫 PHP 類庫中的每一個接口時都須要糾結如下兩個問題(Java 也相似):spa

  • 一個接口須要聲明哪些接口方法?.net

  • 若是多個類實現了相同的接口方法,應該如何設計接口?好比上面這個 SessionHandlerInterface,有沒有必要拆分紅多個更細分的接口,以適應不一樣實現類的須要。設計

接下咱們來看看 Go 語言的接口是如何避免這些問題的。

Go 語言的接口實現

在 Go 語言中,接口實現和類的繼承同樣,並無經過關鍵字顯示聲明實現了哪一個接口,一個類只要實現了某個接口要求的全部方法,咱們就說這個類實現了該接口,例如:

type File struct {  // ...}
func (f *File) Read(buf []byte) (n int, err error) func (f *File) Write(buf []byte) (n int, err error) func (f *File) Seek(off int64, whence int) (pos int64, err error) func (f *File) Close() error

這裏咱們定義了一個 File 類,並實現有 Read()Write()Seek()Close() 等方法。假設咱們有以下接口(Go 語言經過關鍵字 type 和 interface 來聲明接口,花括號內包含都是待實現的方法集合):

type IFile interface {  Read(buf []byte) (n int, err error)  Write(buf []byte) (n int, err error)  Seek(off int64, whence int) (pos int64, err error)  Close() error }
type IReader interface { Read(buf []byte) (n int, err error) }
type IWriter interface { Write(buf []byte) (n int, err error) }
type ICloser interface { Close() error }

儘管 File 類並無顯式實現這些接口,甚至根本不知道這些接口的存在,可是咱們說 File 類實現了這些接口,由於 File 類實現了上述全部接口聲明的方法,當一個類的成員方法集合包含了某個接口聲明的全部方法,換句話說,若是一個接口的方法集合是某個類方法集合的子集,咱們就認爲該類實現了這個接口。

與 PHP 相對,咱們把 Go 語言的這種接口稱做非侵入式接口,由於類與接口的實現關係不是經過顯式聲明,而是系統根據二者的方法集合進行判斷。這樣作有兩個好處:

  • 其一,Go 語言的標準庫不須要繪製類庫的繼承/實現樹圖,在 Go 語言中,類的繼承樹並沒有意義,你只須要知道這個類實現了哪些方法,每一個方法是幹什麼的就足夠了。

  • 其二,實現類的時候,只須要關心本身應該提供哪些方法便可,不用再糾結接口須要拆得多細才合理,也不須要爲了實現某個接口而引入接口所在的包,接口由使用方按需定義,不用事先設計,也不用考慮以前是否有其餘模塊定義過相似接口。

這樣一來,就完美的避免了傳統面向對象編程中的接口設計問題。

本文分享自微信公衆號 - xueyuanjun(geekacademy)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索