【轉載】Go語言設計模式實踐:迭代器(Iterator)

原文:http://www.cnblogs.com/newgame/p/4061083.htmlhtml

關於本系列

決定開個新坑。java

這個系列首先是關於Go語言實踐的。在項目中實際使用Go語言也有段時間了,一個體會就是不管是官方文檔、圖書仍是網絡資料,關於Go語言慣用法(idiom)的介紹都比較少,基本只能靠看標準庫源代碼本身琢磨,因此我特別想在這方面有一些收集和總結。編程

而後這個系列也是關於設計模式的。雖然Go語言不是一門面向對象編程語言,可是不少面向對象設計模式所要解決的問題是在程序設計中客觀存在的。無論用什麼語言,老是要面對和解決這些問題的,只是解決的思路和途徑會有所不一樣。因此我想就以經典的設計模式做爲切入點來展開這個系列,畢竟你們對設計模式都很熟悉了,能夠避免無中生有想出一些蹩腳的應用場景。設計模式

本系列的具體主題會比較靈活,計劃主要包括這些方面的話題:網絡

Go語言慣用法。
設計模式的實現。特別是引入了閉包,協程,DuckType等語言特性後帶來的變化。
設計模式思想的探討。會有一些吐槽。閉包

不使用迭代器的方案

首先要指出的是,絕大多數狀況下Go程序是不須要用迭代器的。由於內置的slice和map兩種容器均可以經過range進行遍歷,而且這兩種容器在性能方面作了足夠的優化。只要沒有特殊的需求,一般是直接用這兩種容器解決問題。即便不得不寫了一個自定義容器,咱們幾乎老是能夠實現一個函數,把全部元素(的引用)拷貝到一個slice以後返回,這樣調用者又能夠直接用range進行遍歷了。編程語言

固然某些特殊場合迭代器仍是有用武之地。好比迭代器的Next()是個耗時操做,不能一口氣拷貝全部元素;再好比某些條件下須要中斷遍歷。函數

經典實現

經典實現徹底採用面向對象的思路。爲了簡化問題,下面的例子中容器就是簡單的[]int,咱們在main函數中使用迭代器進行遍歷操做並打印取到的值,迭代器的接口設計參考java。性能

package main

import "fmt"

type Ints []int

func (i Ints) Iterator() *Iterator {
    return &Iterator{
        data:  i,
        index: 0,
    }
}

type Iterator struct {
    data  Ints
    index int
}

func (i *Iterator) HasNext() bool {
    return i.index < len(i.data)
}

func (i *Iterator) Next() (v int) {
    v = i.data[i.index]
    i.index++
    return v
}

func main() {
    ints := Ints{1, 2, 3}
    for it := ints.Iterator(); it.HasNext(); {
        fmt.Println(it.Next())
    }
}

閉包實現

Go語言支持first class functions、高階函數、閉包、多返回值函數。用上這些特性能夠換種方式實現迭代器。優化

初看之下閉包實現與經典實現徹底不一樣,其實從本質上來看,兩者區別不大。經典實現中把迭代器須要的數據存在struct中,HasNext() Next()兩個函數定義爲Iterator的方法從而和數據綁定了起來;閉包實現中迭代器是一個匿名函數,它所須要的數據i Ints和index以閉包up value的形式綁定了起來,匿名函數返回的兩個值正好對應經典實現中的Next()和HasNext()。

package main

import "fmt"

type Ints []int

func (i Ints) Iterator() func() (int, bool) {
    index := 0
    return func() (val int, ok bool) {
        if index >= len(i) {
            return
        }

        val, ok = i[index], true
        index++
        return
    }
}

func main() {
    ints := Ints{1, 2, 3}
    it := ints.Iterator()
    for {
        val, ok := it()
        if !ok {
            break
        }
        fmt.Println(val)
    }
}

channel實現

這份實現是最go way的,使用了一個channel在新的goroutine中將容器內的元素依次輸出。優勢是channel是能夠用range接收的,因此調用方代碼很簡潔;缺點是goroutine上下文切換會有開銷,這份實現無疑是最低效的,另外調用方必須接收完全部數據,若是隻接收一半就中斷掉髮送方將永遠阻塞。

依稀記得在郵件列表裏看到說標準庫裏有這個用法的例子,剛纔去翻了下沒找到原帖了:-)

順便說一下,「在函數中建立一個channel返回,同時建立一個goroutine往channel中塞數據」這是一個重要的慣用法(Channel Factory pattern,見the way to go 18.8節),能夠用來作序列發生器、fan-out、fan-in等。

package main

import "fmt"

type Ints []int

func (i Ints) Iterator() <-chan int {
    c := make(chan int)
    go func() {
        for _, v := range i {
            c <- v
        }
        close(c)
    }()
    return c
}

func main() {
    ints := Ints{1, 2, 3}
    for v := range ints.Iterator() {
        fmt.Println(v)
    }
}

Do實現

這份迭代器實現是最簡潔的,代碼也很直白,無須多言。若是想加上中斷迭代的功能,能夠將func(int)改成func(int)bool,Do中根據返回值決定是否退出迭代。

標準庫中的container/ring中有Do()用法的例子。

package main

import "fmt"

type Ints []int

func (i Ints) Do(fn func(int)) {
    for _, v := range i {
        fn(v)
    }
}

func main() {
    ints := Ints{1, 2, 3}
    ints.Do(func(v int) {
        fmt.Println(v)
    })
}

總結

Go語言中沒有class和繼承,不具有完整表達面向對象的能力,不是一門一般意義上的面嚮對象語言。可是這不妨礙Go語言實現面向對象的思想,利用其語言特性,實現封裝、組合、多態都沒有問題。 設計模式的精髓在於思想而不在於類圖。編程語言是在不斷進步的,類圖卻一直用幾十年前那一張,拋開類圖從新審視問題,合理利用語言新特性能夠獲得更簡潔的設計模式實現。

相關文章
相關標籤/搜索