In some case when we need to process some data not all at once, this is when the pipeline pattern comes in.
The pipeline pattern contains two elements in all: node and pipeline, node is used to denote the process logic, and pipeline connects all the nodes together to finish the overall processing logic.
Here is my implementation of pipeline with golang, first comes in the node,
type Node interface {
GetNext() Node
SetNext(valve Node)
Invoke(s string) string
}
type BaseNode struct {
Next Node
}
func (v *BaseNode) GetNext() Node {
return v.Next
}
func (v *BaseNode) SetNext(valve Node) {
v.Next = valve
}
type FirstNode struct {
BaseNode
}
func (v *FirstNode) Invoke(s string) string {
s = strings.Replace(s, "11", "first", -1)
fmt.Println("After the first valve, s: " + s)
return s
}
type SecondNode struct {
BaseNode
}
func (v *SecondNode) Invoke(s string) string {
s = strings.Replace(s, "22", "second", -1)
fmt.Println("After the second valve, s: " + s)
return s
}
type ThirdNode struct {
BaseNode
}
func (v *ThirdNode) Invoke(s string) string {
s = strings.Replace(s, "33", "third", -1)
fmt.Println("After the third valve, s: " + s)
return s
}
then the pipeline interface and its implementation,
type Pipeline interface {
GetHead() Node
AddNode(valve Node)
Invoke(s string)
}
type MyPipeline struct {
Head Node
}
func (p *MyPipeline) GetHead() Node {
return p.Head
}
func (p *MyPipeline) AddNode(valve Node) {
if p.Head == nil {
p.Head = valve
p.Head.SetNext(nil)
} else {
current := p.Head
for current.GetNext() != nil {
current = current.GetNext()
}
current.SetNext(valve)
current.GetNext().SetNext(nil)
}
}
func (p *MyPipeline) Invoke(s string) {
current := p.Head
for current != nil {
s = current.Invoke(s)
current = current.GetNext()
}
}
and last the main func,
func main() {
firstValve := FirstNode{}
secondValve := SecondNode{}
ThirdValve := ThirdNode{}
pipeline := MyPipeline{}
pipeline.AddNode(&firstValve)
pipeline.AddNode(&secondValve)
pipeline.AddNode(&ThirdValve)
pipeline.Invoke("11 22 33")
}
ok, that's all.