Using method values to get rid of global variables

Have you ever had a piece of Go code like this:

// TODO: Get rid of this global variable.
var foo service

func fooHandler(w http.ResponseWriter, req *http.Request) {
	// code that uses foo
}

func main() {
	foo = initFoo()

	http.HandleFunc("/foo", fooHandler)
}

One way to get rid of that global variable is to use method values:

type fooHandler struct {
	foo service
}

func (h fooHandler) Handle(w http.ResponseWriter, req *http.Request) {
	// code that uses h.foo
}

func main() {
	foo := initFoo()

	http.HandleFunc("/foo", fooHandler{foo: foo}.Handle)
}

Of course, net/http also has http.Handle so in this case you can simply change fooHandler to implement http.Handler interface:

type fooHandler struct {
	foo service
}

func (h fooHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	// code that uses h.foo
}

func main() {
	foo := initFoo()

	http.Handle("/foo", fooHandler{foo: foo})
}

But method values are great when you need to provide a func, for example in https://godoc.org/path/filepath#Walk or https://godoc.org/net/http#Server.ConnState.

Comments

megaserg commented 11 months ago

If you have two or more global dependencies, would you put them in the same struct? I guess at this point it becomes a "context"...

Also, does this closure approach look simpler to you?

func bind(foo service) func(http.ResponseWriter, *http.Request) {
    return func(w http.ResponseWriter, req *http.Request) {
        // code that uses foo
    }
}

func main() {
    foo := initFoo()

    http.HandleFunc("/foo", bind(foo))
}
Write Preview Markdown
shurcooL commented 11 months ago

Sergey, those are valid points.

If you have two or more global dependencies, would you put them in the same struct? I guess at this point it becomes a "context"...

Yep, I pretty much see it as a context. Similar to http.Client or build.Context.

I used this to clean up package scoped variables that were only needed by that specific handler, they weren't really global dependencies.

Also, does this closure approach look simpler to you?

This is yet another viable way to accomplish the same goal. I don't think I prefer it though, because the handler code ends up indented an extra level, which looks slightly messy. But in some cases it could work well. So thanks for sharing the idea!

Write Preview Markdown
to comment.