Golang Technology (I) in-depth understanding of interface and golanginterface
Rob Pike, one of the major designers of the Go language, once said that if you can only choose one Go language for feature porting to another language, he will choose an interface. It can be seen that the status of the interface in golang and its vitality to the gloang language. What interfaces are in golang is equivalent to a contract, which specifies a group of operations that an object can provide. To understand the interface concept in golang, we 'd better first look at how other modern languages implement interfaces. C ++ does not provide a keyword such as interface. It implements an interface through a pure virtual base class, while java declares an interface through the interface keyword. They have a common feature that a class must display the Declaration to implement this interface, as follows:
interface IFoo { void Bar();}class Foo implements IFoo { void Bar(){}}
This method must explicitly declare that you have implemented an interface, which is called an intrusive interface. We will not discuss the disadvantages of intrusive interfaces in detail here. Let's look at the huge inheritance system of java and its complicated interface types. Golang adopts a completely different design concept. In the Go language, a class only needs to implement all functions required by the interface, so we can say that this class implements this interface, for example:
type IWriter interface { Write(buf [] byte) (n int, err error)}type File struct { // ...}func (f *File) Write(buf [] byte) (n int, err error) { // ...}
An important benefit of non-intrusive interfaces is that they remove the complicated inheritance system. Let's take a look at the summary of the go language programming book: first, the standard library of the Go language, you no longer need to draw an inheritance tree for the class library. You must have seen many inheritance tree diagrams of C ++, Java, and C # class libraries. Here is a Java inheritance tree: http://docs.oracle.com/javase/1.4.2/docs/api/overview-tree.html in Go, the inheritance tree of the class is meaningless, you only need to know what methods this class implements, each method is what meaning is enough. Second, when implementing the class, you only need to care about the methods you should provide, and do not have to worry about how detailed the interface needs to be split. The interface is defined by the user on demand without prior planning. Third, you do not need to import a package to implement an interface. Because referencing an external package more often means more coupling. The interface is defined by the user according to their own needs. The user does not need to worry about whether other modules have defined similar interfaces. If you carefully study the structure of golang, students studying C ++ may find that the interface Concept in golang is similar to Concept in C ++, if you do not know concept, you can refer to Liu weipeng's C ++ 0x series: Concept and Concept! . C ++ uses a template to achieve this effect. No matter what type you use to instantiate the template, the group of Operations corresponding to the template can be instantiated normally, otherwise, the compilation fails. Unlike C ++ templates, golang can only perform interface queries during compilation. We will explain the details of interface queries later. In addition, if you have known Qt before, you can easily find another advantage of this non-intrusive interface. One important feature in Qt is the signal and slot, it decouples the listener and receiver, and uses qt preprocessing to generate static connection code. If golang is used to implement this mechanism, it is easy to implement. without the need to generate code in advance, the decoupling between the listener and the receiver is a natural manifestation of golang.
The role of interfaces in golang in object-oriented thinking golang does not support the complete object-oriented thinking. It does not inherit, and polymorphism fully depends on interface implementation. Golang can only simulate inheritance. Its essence is a combination, but the golang language provides us with some syntactic sugar to make it seem to have achieved the inheritance effect. Liskov Substitution Principle LSP, a very important basic Principle of object-oriented language, won't work here. Those who are used to object-oriented language may be somewhat uncomfortable, when you direct a parent class pointer to a subclass object, golang will throw a compilation error. Golang's design philosophy is the ultimate simplicity. The traditional inheritance concept is not so necessary in golang. golang achieves polymorphism through interfaces. Let's look at an example, let's take a look at how golang achieves the Dependency inversion principle. Let's first look at the implementation of C ++:
struct IPizzaCooker { virtual void Prepare(Pizza*) = 0; virtual void Bake(Pizza*) = 0; virtual void Cut(Pizza*) = 0;} struct PizzaDefaultCooker : public IPizzaCooker { Pizza* CookOnePizza() { Pizza* p = new Pizza(); Prepare(p); Bake(p); Cut(p); return p; } virtual void Prepare(Pizza*) { //....default prepare pizza } virtual void Bake(Pizza*) { //....default bake pizza } virtual void Cut(Pizza*) { //....default cut pizza }} struct MyPizzaCooker : public PizzaDefaultCooker { virtual void Bake(Pizza*) { //....bake pizza use my style }} int main() { MyPizzaCooker cooker; Pizza* p = cooker.CookOnePizza(); //.... return 0;}
This example is very simple. It is to cook a new pizza by using a pizza class. The cooking process implements CookOnePizza in the parent class, And the subclass overrides the Bake method. The following describes how to implement this example in golang:
type IPizzaCooker interface { Prepare(*Pizza) Bake(*Pizza) Cut(*Pizza)} func cookOnePizza(ipc IPizzaCooker) *Pizza { p := new(Pizza) ipc.Prepare(p) ipc.Bake(p) ipc.Cut(p) return p} type PizzaDefaultCooker struct {} func (this *PizzaDefaultCooker) CookOnePizza() *Pizza { return cookOnePizza(this)}func (this *PizzaDefaultCooker) Prepare(*Pizza) { //....default prepare pizza}func (this *PizzaDefaultCooker) Bake(*Pizza) { //....default bake pizza}func (this *PizzaDefaultCooker) Cut(*Pizza) { //....default cut pizza} type MyPizzaCooker struct { PizzaDefaultCooker} func (this *MyPizzaCooker) CookOnePizza() *Pizza { return cookOnePizza(this)}func (this *MyPizzaCooker) Bake(*Pizza) { //....bake pizza use my style} func main() { var cooker MyPizzaCooker p := cooker.CookOnePizza() //....}
Because golang polymorphism must be implemented through interfaces, this is not actually a dependency inversion in a strict sense. In this example, golang seems clumsy, in fact, it can have a more elegant Implementation Scheme. This example is just to introduce the implementation method of polymorphism in golang, and the so-called simulated inheritance is not equivalent to the inheritance relationship in Object-Oriented. Interface memory layout it is necessary to understand the interface's memory structure. Only by understanding this can we further analyze the efficiency issues such as type assertions. Let's take a look at an example:
type Stringer interface { String() string} type Binary uint64 func (i Binary) String() string { return strconv.Uitob64(i.Get(), 2)} func (i Binary) Get() uint64 { return uint64(i)} func main() { b := Binary{} s := Stringer(b) fmt.Print(s.String())}
The interface actually consists of two members in the memory. For example, if the tab points to the virtual table, data points to the actually referenced data. The virtual table depicts the actual type information and the method set required by this interface to observe the itable structure. The first is to describe some metadata of the type information, then there is a list of function pointers that meet the Stringger interface (note that this is not the actual type of Binary function pointer set ). Therefore, if we call a function through an interface, the actual operation is actually s. tab-> fun [0] (s. data ). Is it similar to a virtual table in C ++? Next, let's take a look at the differences between golang's virtual table and C ++'s virtual table. First look at C ++. It creates a method set for each type, and its virtual table is actually the method set itself or a part of it, when multiple inheritance (or multiple interfaces are implemented, this is common), multiple virtual table pointers exist in the C ++ object structure, each virtual table Pointer Points to different parts of the method set. Therefore, the function pointers in the C ++ method set are strictly ordered. Many new C ++ beginners have become tough when faced with multi-inheritance, because of its design method, in order to ensure its virtual table can work normally, C ++ introduces many concepts, such as virtual inheritance and interface functions with the same name. The same interface is inherited multiple times at different levels ...... That is, the veteran can easily write the problem code out due to negligence. Let's take a look at golang's implementation method. Like C ++, golang also creates a method set for each type. The difference is that virtual tables of interfaces are specially generated at runtime. You may find out why you need to generate a virtual table at runtime. Because there are too many, the combination of each interface type and all the entity types that satisfy its interface is its possible number of virtual tables. In fact, most of them are not required, therefore, golang chooses to generate it at runtime, for example, when the first encounter
s
: = Stringer (B), golang will generate the Stringer interface corresponding to the Binary type virtual table and cache it. After understanding the golang memory structure, it is easy to analyze the efficiency issues such as type assertions. when determining whether a type meets an interface, golang uses the type method set and the method set required by the interface for matching. If the type method set completely contains the method set of the interface, it can be considered that the type meets the interface. For example, if a certain type has m methods and an interface has n methods, it is easy to know that the time complexity of such determination is O (mXn). However, you can optimize it by pre-sorting, the actual time complexity is O (m + n ).
General principles Portal: golang technology essay General principles
References: go language programming Xu Shiwei http://research.swtch.com/interfaces (seems to be unable to open now ......)