Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Embedded types and polymorphism with structs

I'm creating an API, and I have two different structs for JSON responses; one for single records and one for collection of records:

type Model struct {
  Id uint
}

type Collection struct {
  Records []Model
}

A Model is just a struct representation of database data (for say, a User), and a Collection is a collection of models.

The issue is that there will be separate structs that embed the Model type, like so:

type User struct {
    *Model
    Name string
}

And since a User doesn't satisfy the Model type, I can't append it to the Collection struct like this:

user := User{&Model{1}, "Jon"}
uc := &Collection{[]User{user}}

How can I make the Records struct accept struct types that implement Model?

like image 953
Jon Avatar asked Oct 06 '15 23:10

Jon


People also ask

What is type embedding?

From the article structs in Go, we know that a struct type can have many fields. Each field is composed of one field name and one field type. In fact, sometimes, a struct field can be composed of a field type only. The way to declare struct fields is called type embedding.

What is struct embedding Golang?

Go by Example: Struct EmbeddingGo supports embedding of structs and interfaces to express a more seamless composition of types. This is not to be confused with //go:embed which is a go directive introduced in Go version 1.16+ to embed files and folders into the application binary.

What is polymorphism in Golang?

Polymorphism is the ability of a message to be displayed in more than one form. Polymorphism is considered as one of the important features of Object-Oriented Programming and can be achieved during either at runtime or compile time.

What is Golang composition?

Composition is a method employed to write re-usable segments of code. It is achieved when objects are made up of other smaller objects with particular behaviors, in other words, Larger objects with a wider functionality are embedded with smaller objects with specific behaviors.


1 Answers

You have two options:

  • Make another collection type
  • Use interfaces instead

Implementing separate collections like UserCollection, BarCollection, FooCollection is an option if you do not have too many foos and bars.

The other way is to use either the generic empty interface type interface{} which will hold values of any type (since every instance of every type satisfies the empty interface):

type Collection struct {
    Records []interface{}
}

This has a number of drawbacks such as that you have to do a type assertion every time you access the Records field, that you can put everything in there by mistake and that you have to use reflection to do most manipulations.

The better way to use interfaces would be to implement a model interface and let every type that is a model implement the interface:

type Model interface {
    ImplementsModel()
}

type BaseModel struct {
    Id uint
}

func (b *BaseModel) ImplementsModel() {}

type User struct {
    *BaseModel
    Name string
}

type Collection struct {
    Records []Model
}

Of course you can remove the ImplementsModel dummy method once you have meaningful methods. This dummy method is just there to distinguish the interface from the empty interface so that you cannot put integer values or strings in place of actual models into the collection.

like image 57
nemo Avatar answered Nov 15 '22 11:11

nemo