Go: Reflection catch22 reflect package

2019-08-11 21:11发布

问题:

Ok.. I have, or am writing a web framework package, called mao.

I'd like to have my Route defined in the controller.

in mao

type (
    Controller struct {
        Route Route
    }
    Route struct {
        Name, Host, Path, Method string
    }
)

in mao importing package

controller/default.go

type DefaultController struct {
    mao.Controller
}
func (this *DefaultController) Index() Response {
    this.Route = mao.Route{"default_index","localhost","/", "GET"}
}

Now since I'd like to define my route inside the controller, the router, when instanced should read all controllers. That's the problem.

How do I pass the package name to my router so it's able to get all structs and functions in that package? Is it even possible?

回答1:

What you ask isn't possible in Go, since it doesn't have a way to enumerate all types in a package/program.

One alternative would be to follow the lead of the database/sql package, and have a system where other packages can register with it when imported.

For example, to use the PostgreSQL driver with that package, you might do:

import (
    _ "github.com/lib/pq"
    "database/sql"
)

...
db, err := sql.Open("postgres", "dbname=test")

The postgres driver is registered during initialisation of the github.com/lib/pq package. Here is the relevant code from that package (eliding some parts that aren't relevant):

package pq

import (
    "database/sql"
)

type drv struct{}

func (d *drv) Open(name string) (driver.Conn, error) {
    return Open(name)
}

func init() {
    sql.Register("postgres", &drv{})
}

Perhaps you could create a registration API like this to find the various implementations available in the program?



回答2:

Honestly, I think you are doing it the wrong way. "auto registering" is obfuscating what happens and will lead to code that is hard to test and to reason about.

I would suggest to make controller an interface that should be satisfied by the concrete controllers and have a method Add(c Controller) on the router to assign the controller in the calling main project (that imports the router and the controllers). This should make your code understandable and explicit and is more in the spirit of go.

The database/sql driver registration is more of a hack and should not be considered best practice.



回答3:

I think you should have one struct for router (which may well be global, like http.DefaultClient) and then in constructor functions for your controllers you'd be able to inject this router as a dependency so that a relevant route is injected for the router. DI+interfaces make your code nice and testable, not only in Go.

Just an idea.



标签: reflection go