Why use interfaces?

Let’s look at a simple program. We enter a URL and we output response

import ( "fmt" "io/ioutil" "net/http" ) func retrieve(url string) string{ res, err := http.Get(url) if err ! = nil { panic(err) } defer res.Body.Close() bytes, _ := ioutil.ReadAll(res.Body) return string(bytes) } func main() { fmt.Println(retrieve("https://www.baidu.com")) }Copy the code

There’s no logical problem at all. Is there a better way to write it? After all, the main function is strongly coupled to our url parsing function

Improved version:

package infra import ( "io/ioutil" "net/http" ) type Retriever struct { } func (Retriever) Get(url string) string { res,  err := http.Get(url) if err ! = nil { panic(err) } defer res.Body.Close() bytes, _ := ioutil.ReadAll(res.Body) return string(bytes) }Copy the code
package main

import (
   "fmt"
   "gomodtest/infra"
)

func getRetriever() infra.Retriever{
   return infra.Retriever{}
}



func main() {
   retriever := getRetriever()
   fmt.Println(retriever.Get("https://www.baidu.com"))
}
Copy the code

This looks a lot better than our first version of the program.

So let’s say at this point we’re going to get the test team to do that and they’re going to have a retriever

Since this is the test team, let’s give a fake response string for the time being

package testing

type Retriever struct {

}

func (Retriever) Get(url string) string  {
   return "fake content"
}
Copy the code

And that’s when you find that your main function is really hard to write and you have to change your getRetriever method inside of main, and you have to change the return value of that method even if you change return. This is when you realize that your code is still coupled

Now, at this point, if you’re coming from Java it’s pretty easy to figure out how to use interfaces.

Just a quick look at how interfaces are used in go

func getRetriever() retriever{
   return testing.Retriever{}
}

type retriever interface {
   Get(string) string
}

func main() {
   retriever := getRetriever()
   fmt.Println(retriever.Get("https://www.baidu.com"))
}
Copy the code

It’s very simple, just define an interface and that interface also has a Get method. So for the above requirements

We only need to change the implementation of our return method during testing, but not for external calls

This is the greatest use of the interface, even if it is basically successfully decoupled

duck typing

If you have Java experience in the previous chapter, you may be confused. You define an interface but your implementation does not implement the corresponding interface. This is the difference between the GO interface and Java interface.

Strictly speaking, GO is a structured type system, similar to Duck typing.

Go’s interface describes the external behavior of things rather than the internal structure

Interfaces in the GO language are user-defined

In traditional object-oriented systems such as Java, the interface is generally defined by the provider or the user, which is completely different from THE GO language. In the process of using the GO language, we must pay attention to the change in the way of thinking

Therefore, for the requirements of the previous chapter, we will write the interface of the GO language according to the ideas to achieve one time:

You can see that the Download function uses a retriever. What are the features of this retriever? She has to have a get function

So let’s define one according to this idea:

With this interface, the provider can play around with it, such as a mock service

Caller:

func main() {
   fmt.Println(download(mock.Retriever{
       Contents: "this is mock",
   }))
}
Copy the code

It can be seen that the implementation of interface in GO language is as long as the implementation of the method, so the implementation of interface in GO language is implicit, she does not need to explicitly say which interface I implement, but just implement the method in the interface

This is the biggest interface implementation difference between GO and Java

The value type of the interface

Following the above type we can extend a Website retriever, and then modify our code a little bit to see what the retriver for this interface is

func main() {
   var r Retriever
   r = mock.Retriever{
      Contents: "this is mock",
   }
   fmt.Printf("%T %v\n",r,r)
   fmt.Println(download(r))

   r = website.Retriever{
      Domain: "baidu",
      Protol: "https",
   }
   fmt.Printf("%T %v\n",r,r)
   fmt.Println(download(r))

}
Copy the code

So you can see that r is a value type here, so one might ask, aren’t value types involved in copying can this be a pointer? Of course you can, and it’s easy to modify.

Modify our interface implementation directly:

And then let’s change our interface assignment

Run the program:

You can see that up here

The interface type of go can accept either a value type or a pointer type.

As with Java, we can get the specific type directly:

Here must be careful to write:

func inspect(r Retriever){
   switch r.(type) {
   case mock.Retriever:
      fmt.Println("this is mock retriever")
   case *website.Retriever:
      fmt.Println("this is website retriever")
   }
}
Copy the code

It’s important to remember that interface in GO has two things in it: the implementor’s type and (the implementor’s pointer or value)

** Interface variables come with Pointers **

Interface type {}

This syntax stands for any type

func printAny( r interface{}){
   fmt.Println(r)
}
Copy the code

Of course, you can cast from any type

func addAny( r interface{},q interface{}) int{
   return r.(int)+q.(int)
}
Copy the code

However, this method does not detect errors at compile time, but only at run time.

Combination of interfaces

This is one of the differences between go and the Java language, where interfaces and interfaces cannot be combined. This is going to cause us some problems at some point.

For example, if you write a function that takes an Interface Jump as an argument in Java, if you want the function to take an interface Run as an argument, there’s no way to do that.

But in GO, it’s ok

For instance

type Jump interface {
   JumpIn()
}

type Run interface {
   RunIn()
}

type RunAndJump interface {
   Jump
   Run
   Happy()
}
Copy the code

In addition to defining RunAndJump, we’ve also defined RunAndJump which has RunAndJump properties and a happy function

Let’s define another function

func printInfo(rq RunAndJump){
   rq.RunIn()
   rq.JumpIn()
   rq.Happy()
}
Copy the code

Notice that the argument becomes this interface type

Now let’s define a structure that implements all three of these methods to be of type RunAndJump

package test

import "fmt"

type Person struct {
   Name string
}

func (q Person) JumpIn() {
   fmt.Println(q.Name + " jump")
}

func (q Person) RunIn() {
   fmt.Println(q.Name + " run")
}

func (q Person) Happy() {
   fmt.Println(q.Name + " is happy")
}
Copy the code

Finally, the main function

func main() {
   printInfo(test.Person{
      Name: "wuyue",
   })
}
Copy the code

Finally, go provides many useful interfaces in library functions such as Reader Writer and Stringer

If you are interested in looking at these interfaces and implementing the corresponding methods in your own code, you will be able to adapt many of the basic functions provided by the GO language.