Understanding GoLang interface

  sonic0002        2021-05-15 04:16:42       7,725        1    

If goroutine and channel are considered as the foundation for GoLang concurrency, interface would be the key for data types in GoLang. In practical Go programming, almost all data types are built/used around interfaces, interface is the core of GoLang data structures.

Go is not a typical OOP language, it has no class and inheritance concept syntactically. But it doesn't mean that there cannot be polymorphism in GoLang. Because of interface, it achieves the same polymorphism effect as in C++, though syntactically different.

Although Go doesn't have concept of class, it allows different data types to have its own methods. Indeed the so-called methods are just functions, in contrast to normal functions, this kind of functions are applied on specific data types. In the signature of these functions, there would be a receiver which defines that the function would be applied on the receiver.

Struct can be considered as a light-weight class and it can have methods. Syntactically, interface is a kind of data type which defines one or some methods. These methods only have method signature but no actual implementation. If a data type implements all methods declared in the interface, this data type is considered a type of the interface. One simple example below

package main
import "fmt"
type MyInterface interface {
 Print()
}
func TestFunc(x MyInterface) {
 x.Print()
}
type MyStruct struct{}
func (me MyStruct) Print() {
 fmt.Println("hello world")
}
func main() {
 var me MyStruct
 TestFunc(me)
}

Why interface is needed? There might be three main reasons.

1. Writing generic algorithm

Strictly speaking, Go doesn't support generic programming, in other languages like C++ and Java, generic programming is relative easy because they have native support. But with interface, generic programming is doable.

 package sort
 // A type, typically a collection, that satisfies sort.Interface can be
 // sorted by the routines in this package. The methods require that the
 // elements of the collection be enumerated by an integer index.
 type Interface interface {
     // Len is the number of elements in the collection.
     Len() int
     // Less reports whether the element with
     // index i should sort before the element with index j.
     Less(i, j int) bool
     // Swap swaps the elements with indexes i and j.
     Swap(i, j int)
 }
 
 ...
 
 // Sort sorts data.
 // It makes one call to data.Len to determine n, and O(n*log(n)) calls to
 // data.Less and data.Swap. The sort is not guaranteed to be stable.
 func Sort(data Interface) {
     // Switch to heapsort if depth of 2*ceil(lg(n+1)) is reached.
     n := data.Len()
     maxDepth := 0
     for i := n; i > 0; i >>= 1 {
         maxDepth++
     }
     maxDepth *= 2
     quickSort(data, 0, n, maxDepth)
 }

2. hiding implementation detail

This is easy to understand. If a function is defined to return an interface, you could do things only based on the method defined in the interface, you have no information about how these methods are actually implemented.

The context package is one of this kind. context was first developed by Google and it was included in the standard library and some other implemented are added including cancelCtx, timerCtx and valueCtx.

Let's take a look.

 func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
     c := newCancelCtx(parent)
     propagateCancel(parent, &c)
     return &c, func() { c.cancel(true, Canceled) }
 }

Although the return value is a Context interface, the actual implementation is a cancelCtx struct.

 // newCancelCtx returns an initialized cancelCtx.
 func newCancelCtx(parent Context) cancelCtx {
     return cancelCtx{
       Context: parent,
       done: make(chan struct{}),
     }
 }
 
 // A cancelCtx can be canceled. When canceled, it also cancels any children
 // that implement canceler.
 type cancelCtx struct {
     Context 
     done chan struct{} // closed by the first cancel call.
     mu sync.Mutex
     children map[canceler]struct{} // set to nil by the first cancel call
     err error // set to non-nil by the first cancel call
 }
 
 func (c *cancelCtx) Done() <-chan struct{} {
     return c.done
 }
 
 func (c *cancelCtx) Err() error {
     c.mu.Lock()
     defer c.mu.Unlock()
     return c.err
 }
 
 func (c *cancelCtx) String() string {
     return fmt.Sprintf("%v.WithCancel", c.Context)
 }

Though different Context interfaces are returned, the caller doesn't know about it.

 func WithCancel(parent Context) (Context, CancelFunc) 
 func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) 
 func WithValue(parent Context, key, val interface{}) Context 

3. Providing interception points

Since any struct which defines all the methods in an interface is considered as a type of the interface, the struct can be used to intercept method calls. For example a struct defines String() method is considered as a Stringer interface and whenever an object of this struct is printed, the String() method would be called to print the data.

There are two types of interfaces in GoLang: empty interface and non-empty one. They will be introduced in future post.

Reference

https://www.toutiao.com/i6620648091592688135/

INTERFACE  GOLANG 

       

  RELATED


  1 COMMENT


Anonymous [Reply]@ 2019-04-15 23:27:28

RGUKT Basar



  RANDOM FUN

The night before product release