Let’s first see where we can apply this pattern and what can be the use cases of this pattern:

  1. Small and Independent Tasks: The boring design pattern is well-suited for scenarios where you have several small and independent tasks that can be executed concurrently. Each task can be encapsulated within a separate goroutine.
  2. I/O-Bound Operations: For applications that involve I/O-bound operations (e.g., reading and writing to files, making network requests), the boring design pattern can be beneficial. Concurrently executing I/O-bound tasks using goroutines and channels can improve overall throughput and performance.
  3. Event Handling: When dealing with events or event-driven programming, the boring design pattern can be employed to handle multiple events concurrently. Each event handler can run in its own goroutine, responding to events as they occur.

Now we will see how can we implement this pattern with the help of goroutine and waitgroups

package main

import (
"fmt"
"sync"
)

// Boring function that returns a channel to send boring messages
func boring(msg string) <-chan string {
ch := make(chan string)
go func() {
defer close(ch) // Close the channel when the goroutine exits
for i := 1; i <= 5; i++ {
ch <- fmt.Sprintf("%s: %d", msg, i)
}
}()
return ch
}

func main() {
aliceCh := boring("Alice")
bobCh := boring("Bob")

// Receive messages concurrently
var wg sync.WaitGroup
wg.Add(2)

go func() {
for msg := range aliceCh {
fmt.Println(msg)
}
wg.Done()
}()

go func() {
for msg := range bobCh {
fmt.Println(msg)
}
wg.Done()
}()

wg.Wait()
}

--

--