- Learning Functional Programming in Go
- Lex Sheehan
- 443字
- 2025-02-27 05:14:36
Strategy pattern
The strategy pattern uses composition rather than inheritance to choose which behavior is executed. The behavior in our example implements a load balancing algorithm. Production implementations of the strategy pattern often have an administrative application that is used to choose which strategy it selected during runtime:

Rather than using the context of the request or configuration instructions from an administrative application to selecting our load balancing strategy, we hardcode our example to use the RoundRobin behavior.
Here's the call:
LoadBalancing(RoundRobin(0, "web01:3000", "web02:3000", "web03:3000")),
The first parameter, RoundRobin, is the selected strategy. We pass the RoundRobin function We pass the iterating RoundRobin function in order over the backend server's host addresses. They are passed over the variadic parameter, namely backends.
Instead of using a request to gather context to determine the strategy to employ, we define a Director function type that takes the request. We select the RoundRobin strategy and modify the request's embedded URL member to specify the server to connect to:

The following is the RoundRobin function where we make the r.URL.Host assignment:
func RoundRobin(robin int64, backends ...string) Director {
return func(r *http.Request) {
if len(backends) > 0 {
r.URL.Host = backends[atomic.AddInt64(&robin, 1) % int64(len(backends))]
}
}
}
Alternatively, if we had defined other load balancing strategies, such as Least Loaded or Random, we'd only need to implement that function and pass it to our LoadBalancing function as its director.
The LoadBalancing function returns a decorator that spreads client requests across multiple backend servers, based on the given director, that is, RoundRobin in our example:
func LoadBalancing(dir Director) Decorator {
return func(c Client) Client {
return ClientFunc(func(r *http.Request)(*http.Response, error) {
dir(r)
return c.Do(r)
})
}
}
The Director modifies each HTTP request to follow the chosen load balancing strategy:
type Director func(*http.Request)
Finally, we have our FaultTolerance decorator that extends a client with fault tolerance, based on the given attempts and backoff time duration:
func FaultTolerance(attempts int, backoff time.Duration) Decorator {
return func(c Client) Client {
return ClientFunc(func(r *http.Request) (*http.Response, error) {
var res *http.Response
var err error
for i := 0; i <= attempts; i++ {
if res, err = c.Do(r); err == nil {
Info.Println("SUCCESS!")
break
}
Debug.Println("backing off...")
time.Sleep(backoff * time.Duration(i))
}
if err != nil { Info.Println("FAILURE!") }
return res, err
})
}
}
We only want the backing off information output to our trace file, so we use our Debug.Println function.
Notice what each decorator has in common? They provide additional functionality and eventually call c.Do(r). Some provide the additional functionality before calling c.Do(r); some could do it before and after the call.