#Most Idiomatic Way of Writing a Generic Constructor?

12 messages · Page 1 of 1 (latest)

proper mulch
#

Hey there!

I'm currently trying to do a bit of web development, and have a bunch of classes (FooPage, BarPage) that are almost exactly the same, save for the interface for their dependencies and methods specific to how they serve their content.

It became onerous to write basically the same constructor for each page, so I am trying to split things out and make my code more DRY. This has resulted in the following code:

type Page[D any] interface {
    Register(m *http.ServeMux)
    SetDeps(d D)
    SetTemplate(t *template.Template)
}

type PageBase[D any] struct {
    Deps D
    Tmpl *template.Template
}

func (p *PageBase[D]) SetDeps(d D) {
    p.Deps = d
}

func (p *PageBase[D]) SetTemplate(t *template.Template) {
    p.Tmpl = t
}

func NewPage[P Page[D], D any](p P, m *http.ServeMux, d D, t *template.Template) P {
    p.SetDeps(d)
    p.SetTemplate(t)
    p.Register(m)
    return p
}```

which allows us to then define whole pages like so:

```go
type MockPageDeps interface {
  // Blank for testing
}

type MockPage struct {
    dmn.PageBase[MockPageDeps]
}

func (p *MockPage) Register(m *http.ServeMux) {
  // Typically calls m.HandleFunc("/whatever", pageFunction)
}

and construct them like so:

    dmn.NewPage[*mckpg.MockPage, mckpg.MockPageDeps](&mckpg.MockPage{}, mux, deps, &tmpl)

This works, but feels very ugly to me. I'm wondering if anyone more experienced has a better (still DRY) way of doing what I'm trying to do?

boreal elk
#

Either way, I think generic functions automatically detect the type. i.e.

dmn.NewPage(&mckpg.MockPage{}, mux, deps, &tmpl)
proper mulch
#

Potentially multiple different services

#

For example, the login page might need access to GetUserByUsername() from LoginStorage, or whatever

#

So my typical pattern is to throw all of the dependencies something might need into an anonymous struct that satisfies the PageDependency interface that the page defines

#

i.e.

...

mock_deps := struct{
  LoginService
  PostService
} {
  // these services would be initialized earlier
  LoginService: login_svc
  PostService: post_svc
}

dmn.NewPage(&mckpg.MockPage{}, mux, mock_deps, &tmpl)
boreal elk
#

You could always just export all the services in one struct to remove the dependencies and pass it as a pointer so it doesn't have that much memory overhead

proper mulch
#

This is true but gets a bit weird in the case that a service depends on another service

boreal elk
#

Like a depends on b, so init b then init a