This is a creation in Article, where the information may have evolved or changed.
About this series
decided to open a new hole.
This series is first about the go language practice. The actual use of the go language in the project also has a period of time, an experience is whether it is official documents, books or web materials, about the go language idioms (idiom) Introduction are relatively small, basic can only rely on the standard library source code to ponder their own, so I particularly want to have some collection and summary.
Then the series is about design patterns. Although the go language is not an object-oriented programming language, many object-oriented design patterns need to solve the problem that exists in the programming. No matter what language, always have to face and solve these problems, but the solution of the ideas and ways will be different. So I want to use the classic design pattern as a starting point to expand this series, after all, we are familiar with the design patterns, you can avoid out of nowhere to come up with some crappy application scenarios.
The specific topics in this series will be more flexible, including the topics of the program:
- Go language idiomatic method.
- The implementation of the design pattern. In particular, the introduction of closures, Ducktype, and other language features brought about by the changes.
- The discussion of design pattern thought. There will be some spit slots.
Scenarios that do not use iterators
the first thing to point out is that in most cases go programs don't need iterators. because the built-in slice and map two containers can be traversed by range, both containers are sufficiently optimized for performance. As long as there is no special requirement, the two containers are usually used directly to solve the problem. Even if you have to write a custom container, we can almost always implement a function that copies all the elements (references) back to a slice, so that the caller can iterate directly with range.
Of course, some special occasions iterators still have a place to play. The next () iterator, for example, is a time-consuming operation that can not copy all elements in one breath, or, for example, interrupt traversal in some conditions.
Classic implementations
The classical implementation completely adopts object-oriented thinking. To simplify the problem, the container in the following example is simple []int , we main use iterators in the function to traverse the operation and print the values that are taken, the interface design of the iterator reference Java.
package mainimport "fmt"type Ints []intfunc (i Ints) Iterator() *Iterator {return &Iterator{data: i,index: 0,}}type Iterator struct {data Intsindex 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())}}
Closure implementation
The Go language supports first class functions, higher order functions, closures, and multiple return value functions. Using these features, you can implement iterators in a different way.
At first glance, the closure implementation is completely different from the classical implementation, in fact, in essence, the two differ little. In the classic implementation, the data required by the iterator exists in the struct, HasNext() Next() and two functions are defined as a method to bind to the Iterator data; The iterator in the closure implementation is an anonymous function that requires data and a i Ints index closure up The form of value is bound, and the two values returned by an anonymous function correspond exactly to those in the classic implementation Next() HasNext() .
package mainimport "fmt"type Ints []intfunc (i Ints) Iterator() func() (int, bool) {index := 0return func() (val int, ok bool) {if index >= len(i) {return}val, ok = i[index], trueindex++return}}func main() {ints := Ints{1, 2, 3}it := ints.Iterator()for {val, ok := it()if !ok {break}fmt.Println(val)}}
Channel implementation
This implementation is the most go-to, using a channel in the new Goroutine the container elements in turn output. The advantage is that the channel can be received with range, so the caller code is very concise, the disadvantage is that the goroutine context switch will have overhead, this implementation is undoubtedly the least efficient, in addition to the caller must receive all the data, if only half of the interrupt sender will be blocked forever.
Vaguely remember in the mailing list to see that the standard library has the use of this example, just went over the next did not find the original posts:-)
By the way,"Create a channel in the function to return, while creating a goroutine to channel the data" This is an important idiom (Channel Factory pattern, see the way to go 18.8), can be used to do sequence generators, fan-out, fan-in and so on.
package mainimport "fmt"type Ints []intfunc (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 implementation
This iterator implementation is the most concise, the code is very straightforward, needless to say. If you want to add the ability to break iterations, you can func(int) change to func(int)bool do, depending on the return value, to decide whether to exit the iteration.
Examples of the container/ring use of Do () in the standard library.
package mainimport "fmt"type Ints []intfunc (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)})}
Summarize
The
- go language does not have class and inheritance, does not have the ability to fully express object-oriented, and is not an object-oriented language in the usual sense. But this does not prevent the go language to implement object-oriented thinking, using its language features, to achieve encapsulation, composition, polymorphism are no problem. The essence of the
- design pattern is the idea, not the class diagram. Programming language is in continuous progress, the class diagram has been a few decades ago that one, put aside the class diagram to re-examine the problem, reasonable use of new language features can be more concise design pattern implementation.