設計模式 in Go: Iterator
行爲模式旨在解決對象間通信和交互相關的問題,專注於定義那些複雜到無法靜態設計的協作策略,這些協作策略使得程序可以在運行時動態地進行職責派遣以實現更好的擴展。
今天我們開始第 2 個行爲模式的學習 —— Iterator(迭代器模式)。
問題背景:
設想我們定義了一個新的數據類型,它包含了一系列數據元素,可能是以 list、array、set 或其他據結構進行存儲,重要的是我們後續會出於性能、功能方面的考慮對維護這些數據的邏輯、存儲結構進行優化。所以並不想將維護這個數據元素集合的內部表示暴露給使用方,但是我們確實又需要允許使用方對其進行遍歷。
迭代器模式用於按順序訪問集合中的元素,同時不暴露其內部結構。當我們需要遍歷諸如數組或列表等對象集合並對每個元素進行操作時,通常會用到這一模式。
解決方案:
迭代器模式提供了一種按順序訪問數據集合的方式,而不暴露數據集合的內部結構。它將遍歷邏輯與數據集合本身的存儲結構分離,使得數據集合可以更改其結構而不會影響使用它的代碼。
該模式引入了兩個主要組件:迭代器(Iterator)和聚合(Aggregate)。迭代器定義了一種訪問集合中元素的接口,而聚合定義了創建迭代器的接口。集合類實現聚合接口,並提供一個知道如何遍歷集合的迭代器實現。
通過使用迭代器模式,我們可以遍歷集合而無需擔心其具體的實現細節。它提供了一種標準化的方式來訪問元素,無論集合是數組、鏈表還是其他任何數據結構。
以下是一個如何訪問團隊成員的示例演示:
示例代碼:
package iterator
import "fmt"
// Iterator iterator to traverse collection elements.
//
// use it like this:
//
// for it.HasMore() {
// el := it.Next()
// // do something with `el`
// // ...
// }
type Iterator interface {
HasMore() bool
Next() *Student
}
// we'll traverse a list of students, so implement a sliceIterator
type sliceIterator struct {
offset int
data []*Student
}
func (si *sliceIterator) HasMore() bool {
return si.offset < len(si.data)
}
func (si *sliceIterator) Next() *Student {
if !si.HasMore() {
return nil
}
v := si.data[si.offset]
si.offset++
return v
}
type Student struct {
Name string
Age int
}
func (s *Student) String() string {
return fmt.Sprintf("student %s is %d-years-old", s.Name, s.Age)
}
// Team the so-called Aggregate Component, which creates an Iterator.
type Team struct {
Students []*Student
}
// Here we return an sliceIterator which implements Iterator.
// Maybe after some time, we want to change []*Student to map[ID]*Student,
// then we define a mapIterator on it and return the mapIterator is enough.
// The client code don't have to be modified because Iterator Pattern hides
// the details of datastructure.
func (t *Team) Iterator() Iterator {
return &sliceIterator{
offset: 0,
data: t.Students,
}
}
下面通過測試用例進行演示:
package iterator_test
import (
"fmt"
"iterator"
"testing"
)
func TestIterator(t *testing.T) {
// create the Aggregator
students := []*iterator.Student{
&iterator.Student{Name: "zhang", Age: 100},
&iterator.Student{Name: "wang", Age: 99},
}
team := &iterator.Team{
Students: students,
}
// create the Iterator
it := team.Iterator()
// traverse the collection via Iterator
for it.HasMore() {
student := it.Next()
fmt.Println(student)
}
}
ps:此處我們優先考慮可讀性,不會太關注編碼標準,如註釋、camelCase 類型名等。我們將多個文件的代碼組織到一個 codeblock 中僅僅是爲了方便閱讀,如果您想測試可以通過 git 下載源碼 github.com/hitzhangjie/go-patterns。
迭代器模式變體:
有一些與迭代器模式結合使用的相關模式,可以解決類似的問題。這些模式包括:
-
組合模式: 組合模式允許你將一組對象當作單一對象進行處理。它可以與迭代器模式結合使用,遍歷層次結構,在這種結構中每個元素既可以是單個對象也可以是一組對象。
-
工廠模式: 工廠方法模式可以用於根據遍歷的集合類型創建不同類型的迭代器。它提供了一種封裝迭代器創建邏輯的方式,並且允許輕鬆擴展。
-
訪問者模式: 訪問者模式可以用於在不修改元素類的情況下對集合中的元素執行操作。它可以在與迭代器模式結合使用時,遍歷集合並對每個元素應用不同的操作。
在迭代器模式的上下文中,“Aggregate”(聚合)指的是表示或包含一組對象的接口或類。它定義了創建可以遍歷集合元素的迭代器對象的操作。在中國語境中,術語 “Aggregate” 可以翻譯爲 “聚合類” 或“容器類”。
與其他模式的關係:
迭代器模式常常與其他模式結合使用,以提供更爲高級的功能。與迭代器模式相關的一些模式包括:
-
觀察者模式: 可以用於在集合的元素髮生變化時通知觀察者。迭代器模式則可以用來遍歷集合,並通知觀察者關於變化的信息。
-
代理模式: 可以用來提供一個控制對集合元素訪問的替代對象。迭代器模式可以通過代理對象遍歷集合,從而允許實現額外的功能或安全檢查。
本文總結:
總體而言,迭代器模式提供了一種順序訪問集合元素的方式,而不暴露其內部結構。它將遍歷邏輯與集合的具體實現相分離,從而增加了靈活性和可擴展性。可以與其他模式結合使用,以解決更復雜的問題並提供額外的功能。
鑽牛角尖:
QA:如果是用 go,可能我們會在自定義類型中添加方法 func (t *foo) ForEach(fn func(el *Element))
,實現簡單也實現了迭代時逐一處理的功能,大多數情景下可以和標準迭代器模式實現相同的功能 … 這種寫法是迭代器模式嗎?推薦嗎?
如果非要揪住問這是不是迭代器模式,我只能說:1)形式上,標準迭代器模式作爲一種 “範本”,對大多數編程語言具有更普遍性的指導意義,2)並不是所有語言都支持閉包,此時 ForEach(fn) 中 fn 就得傳遞多個參數,實現並不比通過 iterator.Next()遍歷後處理更簡單、更具普適性,3)總的來說,標準迭代器模式可能會更靈活,4) 這種寫法沒有實現 “遍歷” 和 “處理” 的徹底切割,嗯也沒什麼,不過建議瞭解下 C# foreach 關鍵字,看看 C# 是怎麼設計的。
OK,還記得我們提到過的模式的層次嗎?架構模式,設計模式,Idioms,即使是同一個思想,不同語言也可能有不同的推薦寫法。凡事都有兩面性,當我們引入一種解決方案的時候,我們同時也引入了麻煩,寫一個標準的迭代器模式,問題是有 Foreach(fn) 簡單、有效嗎?它一定程度上也解決了迭代器模式要解決的問題。
我們學習設計模式,是爲了降低解決問題的複雜度,並不是爲了讓問題變複雜。關鍵問題決定了採用的方案,其他問題驗證我們的方案 …… so 先明確現在要解決什麼樣的問題,以及將來可能演變成什麼樣的問題,是不是、行不行、推不推薦,不就都想明白了嗎。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/aLvG84vpwBX5_mOHDo6JAw