I recently learned about Go Embedding structs
and interfaces
and I was quite impressed by how powerful it can be. So in the process of learning and exploring, I thought it might be good to share what I have learned with a small and hopefully easy example.
I have divided this example into 3 simple projects which will explain the use case and how embedding can be a really powerful programming paradigm.
The first project is where we define an interface with two methods:
package types
import "context"
type Client interface {
MethodA(ctx context.Context, message string)
MethodB(ctx context.Context, message string)
}
And then we also define a service which is used to initialize a client that will implement these methods
package types
import (
"context"
)
type Service struct {
client Client
}
func NewService(client Client) *Service {
return &Service{client: client}
}
func(s *Service) MethodA(ctx context.Context, message string) {
s.client.MethodA(ctx, message)
}
func(s *Service) MethodB(ctx context.Context, message string) {
s.client.MethodB(ctx, message)
}
This means that when we initialize using NewService
using a client then it will use the client implemented methods.
Now the third project (pardon my ordering, second will come in a bit) is a library which has an implementation of first.Client
package third
import (
"context"
"fmt"
"github.com/shrimalmadhur/playground/interfaces/first/types"
)
type Client struct {
client types.Client
}
func (c Client) MethodA(ctx context.Context, message string) {
fmt.Println(fmt.Sprintf("client impl message MethodA: %s", message))
}
func (c Client) MethodB(ctx context.Context, message string) {
fmt.Println(fmt.Sprintf("client impl message MethodB: %s", message))
}
func NewClient() Client {
return Client{}
}
Here is the final second project which brings it all together and where we will see actual power of embedding.
It has a file called client.go which contains
import (
"context"
"fmt"
cl "github.com/shrimalmadhur/playground/interfaces/third"
)
type Client struct {
cl.Client
}
func (s Client) MethodA(ctx context.Context, message string) {
fmt.Println(fmt.Sprintf("Override MethodA message: %s", message))
}
func NewClient() Client {
sdkClient := cl.NewClient()
return Client {
sdkClient,
}
}
Now what you see here is
type Client struct {
cl.Client
}
This above syntax is called embedding where you import and override the methods of the actual client if you want.
In the NewClient
function we initialize the the sdkClient
with third.NewClient
which will import the two method using Go embedding. But here we have an option to override any of the MethodA
or MethodB
, so we override MethodA
to see what happens.
To run all of this here is our main.go file
package main
import (
"context"
"github.com/shrimalmadhur/playground/interfaces/first/types"
"github.com/shrimalmadhur/playground/interfaces/second/client"
)
func main() {
ctx := context.Background()
myClient := client.NewClient()
service := types.NewService(myClient)
service.MethodA(ctx, "hello")
service.MethodB(ctx, "hello")
}
So now here is what it prints
Override MethodA message: hello
client impl message MethodB: hello
We can see in the above print messages that the first line has been printed using the overridden MethodA
in the main
package and the second line is printed using the client
library in the second
package. This way you can import a client
package and then override the interface methods
as needed.
I hope this makes it easy to understand Go embedding. Any feedback is welcome.
You can see the full code here.