Specific use of Go interface

  • 2020-06-12 09:31:23
  • OfStack

After a simple understanding of Go, I found that the design of Go grammar is very simple and easy to understand. This is exactly what Rob Pike, the father of Go, said: "Less is more".

The following is my own experience on the specific features of grammar.

interface

An overview of

Unlike the object-oriented design (OOP) languages, which are usually based on type hierarchy and inheritance (C++, Java), the core idea of Go is composition (composition). Go step 1 decouple objects and operations, implementing a true duck type (Duck typing) : an object can be quacked as a duck, as opposed to C++ or Java, which requires the type system to ensure that an object is quacked first.


type Duck interface {
  Quack()
}

type Animal struct {
  name string
}

func (animal Animal) Quack() {
  fmt.Println(animal.name, ": Quack! Quack! Like a duck!")
}

func main() {
  unknownAnimal := Animal{name: "Unknown"}

  var equivalent Duck
  equivalent = unknownAnimal
  equivalent.Quack()
}

Run the above code output:

[

Unknown : Quack! Quack! Like a duck!

]

This is implemented using the Java language:


interface Duck {
  void Quack();
}

class SomeAnimal implements Duck {
  String name;

  public SomeAnimal(String name) {
    this.name = name;
  }

  public void Quack() {
    System.out.println(name + ": Quack! Quack! I am a duck!");
  }
}

public class Test {
  public static void main(String []args){
    SomeAnimal unknownAnimal = new SomeAnimal("Unknown");
    Duck equivalent = unknownAnimal;
    equivalent.Quack();
  }
}

The comparison shows that Go decouples an object more thoroughly from the operations (methods or functions) on it. Go does not require an object to implement an interface through the type system (is a), but only methods that implement an interface (like a), and type declarations are loosely coupled to method declarations or implementations. If you slightly transform the implementation of the method under 1:


func (animal Animal) Quack() {
  fmt.Println(animal.name, ": Quack! Quack! Like a duck!")
}

To:


func Quack(animal Animal) {
  fmt.Println(animal.name, ": Quack! Quack! Like a duck!")
}

Is it the same as the normal method?

I have analyzed the message invocation process of Objective C in message 1:


Bird * aBird = [[Bird alloc] init];
[aBird fly];

Calls to fly, compiler by inserting code 1, converts the calls to IMP concrete implementation method, the IMP is the method in the class structure of Bird chain table lookup name for fly selector SEL find corresponding specific method, the compiler will call message into the message function objc_msgSend call:


objc_msgSend(aBird, @selector(fly));

Whether Objective C messaging or Qt Signal/Slot of mechanism, can say is trying to the object itself (data) and to the operation of the object (message) decoupling, but Go will this job done more thorough in language level, such not only avoid multiple inheritance problem, also reflect what matters most in object-oriented design: messaging between objects.

implementation

interface is actually a single structure with two members. One member is a pointer to specific data, and the other member contains type information. The empty interface and the interface with method are slightly different. The following are the data structures used by the empty interface and the interface with method respectively:


struct Eface
{
  Type*  type;
  void*  data;
};
struct Iface
{
  Itab*  tab;
  void*  data;
};

struct Itab
{
  InterfaceType*  inter;
  Type*  type;
  Itab*  link;
  int32  bad;
  int32  unused;
  void  (*fun[])(void);
};

struct Type
{
  uintptr size;
  uint32 hash;
  uint8 _unused;
  uint8 align;
  uint8 fieldAlign;
  uint8 kind;
  Alg *alg;
  void *gc;
  String *string;
  UncommonType *x;
  Type *ptrto;
};

Start with Eface, which is the data structure used at the bottom of interface{}. The data field contains 1 void* pointer, and 1 pointer to a type structure. interface{} plays a similar role to void* in C, where any object in Go can be represented as interface{}. The difference is that there is type information in interface{}, which enables reflection.

The type information structure of different types of data is not completely 1. Type is the common part of the type information structure, where size describes the size of the type, and UncommonType is an array of 1 function pointer, collecting all methods of the specific implementation of this type.

In the reflect package, there is an KindOf function that returns 1 ES1010en {}. In fact, this function simply takes the Type field in Eface.

Iface is slightly different from Eface in that it is the data structure used by the underlying interface with methods. The data field also points to the original data. Type not only stores Type information, but also has an additional method table fun[]. Methods implemented in 1 specific type of Iface are copied to the fun array of Itab.

There is a method table in Type's UncommonType where all methods implemented by a particular type are collected. The Method and MethodByName methods in the reflect package are implemented by querying this table. Each item in the table is an Method, and its data structure is as follows:


struct Method
{
  String *name;
  String *pkgPath;
  Type  *mtyp;
  Type *typ;
  void (*ifn)(void);
  void (*tfn)(void);
};

Iface's Itab's InterfaceType also has a table of methods that are declared by the interface. Each item is an IMethod, and the data structure is as follows:


struct IMethod
{
  String *name;
  String *pkgPath;
  Type *type;
};

A comparison of the Method structure above shows that only declarations are not implemented here.

The func field of Itab in Iface is also a table of methods, and each entry in this table is a function pointer, that is, only the implementation is not declared.

The test for type conversion is to see if the method table in Type contains all the methods in InterfaceType's method table and copy the implementation part of the Type method table to the func table of Itab.

Matters needing attention

When an interface is not initialized, the corresponding value is nil. In other words:


var v interface{}

So v is one nil. On the underlying storage, it is a null pointer.

A different situation


interface Duck {
  void Quack();
}

class SomeAnimal implements Duck {
  String name;

  public SomeAnimal(String name) {
    this.name = name;
  }

  public void Quack() {
    System.out.println(name + ": Quack! Quack! I am a duck!");
  }
}

public class Test {
  public static void main(String []args){
    SomeAnimal unknownAnimal = new SomeAnimal("Unknown");
    Duck equivalent = unknownAnimal;
    equivalent.Quack();
  }
}

0

At this point, v is 1 interface, its value is nil, that is, its data field is empty, but it is not nil itself.

Here's an example:
The error type in Go language is actually an error interface that abstracts the Error() method:


interface Duck {
  void Quack();
}

class SomeAnimal implements Duck {
  String name;

  public SomeAnimal(String name) {
    this.name = name;
  }

  public void Quack() {
    System.out.println(name + ": Quack! Quack! I am a duck!");
  }
}

public class Test {
  public static void main(String []args){
    SomeAnimal unknownAnimal = new SomeAnimal("Unknown");
    Duck equivalent = unknownAnimal;
    equivalent.Quack();
  }
}

1

The code is as follows:


interface Duck {
  void Quack();
}

class SomeAnimal implements Duck {
  String name;

  public SomeAnimal(String name) {
    this.name = name;
  }

  public void Quack() {
    System.out.println(name + ": Quack! Quack! I am a duck!");
  }
}

public class Test {
  public static void main(String []args){
    SomeAnimal unknownAnimal = new SomeAnimal("Unknown");
    Duck equivalent = unknownAnimal;
    equivalent.Quack();
  }
}

2

Run test_checkError() output:

[

e is nil
err is not nil

]

Related articles: