You can use generics with structs and interface so you can work with different concrete types.
In the example below, we defined Repo interface that can handle different concrete types like Model1 and Model2. The idea behind this is to create repository that can retrieve data using the model of our choosing.
To implement a generic repo interface, we created a generic struct that can handle different concrete types as well.
type Repo[T any] interface {
GetOne(id string) (T, error)
GetAll() ([]T, error)
}
type repo[T any] struct{}
func (repo[T]) GetOne(id string) (T, error) {
var t T
return t, nil
}
func (repo[T]) GetAll() ([]T, error) {
return nil, nil
}
type Model1 struct{}
type Model2 struct{}
func main() {
model1Repo := repo[Model1]{}
model2Repo := repo[Model2]{}
// As a result, each instantiated type has methods where T is substituted with the concrete type
// model1Repo.GetAll() => GetAll() ([]Model1, error)
// model2Repo.GetOne() => GetOne(id string) (Model2, error)
}