Golang Decoupling, Embedding and Exporting
Decoupling
Golang does not have a concept of object-oriented programming (OOP), including the concept of class or a class-based inheritance. Rather, Golang has its own way to interpret the regular object-oriented languages - decoupling.
In traditional OOP design, one class contains member variables, either public or private. It also contains member functions, either public or private. In order to reuse the class member function, another class has to inherit the class. Golang provides an approaches to make design more efficient: Decouple a concrete data (struct) with a set of member functions (interface).
Decoupling separates concrete data and behaviors. The concrete data can be defined as an object data structure such as struct
. The behaviors are defined as a set of member functions in the type of interface
.
The good thing about it is, we are able to implement the same interface methods for different concrete data, either with a value receiver, or with a pointer receiver.
The following example illustrates the polymorphism of Golang.
package main
import (
"fmt"
)
type reader interface {
read() (int, error)
}
type file struct {
name string
fileContent string
}
func (f *file) read() (int, error)
content := "file contents"
f.fileContent = content
return len(content), nil
}
type pipe struct {
name string
pipeMessage string
}
func (p *pipe) read() (int, error) {
msg := `{name: "Henry", title: "developer"}`
p.pipeMessage = msg
return len(msg), nil
}
func main() {
f := file{
name: "data.json",
}
p := pipe{
name: "pipe_message",
}
retrieve(&f)
fmt.Println(f.fileContent)
retrieve(&p)
fmt.Println(p.pipeMessage)
}
func retrieve(r reader) error {
len, _ := r.read()
fmt.Println(fmt.Sprintf("read %d bytes", len))
return nil
}
After retrieve
function passes the concrete data file
struct as a reader
interface, what happens is, the interface r
's type is pointed to a iTable
which contains file
type, and its behavior’s pointer, which points to read
method. The interface r
's value is copied from the real concrete data of file
struct.
When the second time call retrieve
, since the concrete data pipe
is passed as a reader
interface, then, the r
interface type is changed to pipe
and the value is copied from pipe
.
We can think that the Golang interface is a pointer which points to a component with Type
pointer and Value
pointer. Golang provides a way reflect
to extract this type and value. We may have another blog to demonstrate it.
Embedding
We can embed one concrete data struct to another concrete data struct. Or, Embedded a set of member functions (interface) in a concrete data (struct). It actually creates the relationship between two objects. The inner type can be prompted to the outer type object. Then, the outer type concrete data can directly use the inner type behaviors. The following is an example
package main
import (
"fmt"
)
type mailer interface {
notify()
}
type user struct {
name string
email string
}
func (u *user) notify() {
fmt.Println("sending email")
}
type admin struct {
user
level int
}
func main() {
user := user{
name: "Henry Du",
email: "henrydu.com@gmail.com",
}
ad := &admin{
user: user,
level: 8,
}
ad.notify()
}
Exporting
Exporting is more or less like capsulation pieces of Golang. For example, We could define enumeration type which contains an integer. Based on the exported named type, we could implement an interface to have several behaviors to be used. Then, we could import this named type from other package.
As we have already known, only first-letter-capital named type or field type can be exported to other package in Golang.
type RunningMode uint32
const (
LBMode RunningMode = iota
ServerMode
)
const (
LBModeStr = "Load Balancer"
ServerModeStr = "Server"
)
func (r RunningMode) IsServerMode() bool {
switch r {
case LBMode:
return false
case ServerMode:
return true
}
return false
}
func (r RunningMode) String() string {
switch r {
case LBMode:
return LBModeStr
case ServerMode:
return ServerModeStr
}
return "invalid running mode"
}