Go quick start 05 struct and interface: what functions do structs and interfaces implement?

Time:2021-5-6

structural morphology

Structure definition

A structure is an aggregation type, which can contain any type of values. These values are members of the structure defined by us, also known as fields. In go language, to customize a structure, you need to use the keyword combination of type and struct.
A structure type named person is defined to represent a person. The person structure has two fields: name for the person’s name and age for the person’s age.

type person struct {
    name string
    age uint
}

When defining a structure, the declaration method of a field is the same as that of a variable. The variable name comes first and the type comes second. Only in a structure, the variable name is a member name or a field name.

The member fields of a structure are not required, or there can be no fields. This structure becomes an empty structure.

According to the above information, we can summarize the expression of structure definition, as shown in the following code:

type structName struct{
    fieldName typeName
    ....
    ....
}

Among them:

  • Type and struct are the key words of go language. The combination of them means to define a new structure type.
  • Structname is the name of the structure type.
  • Fieldname is the field name of the structure, and typename is the corresponding field type.
  • Fields can be zero, one, or more.

    Tip: structure is also a type. For example, person structure and person type actually mean the same thing.

The structure can be used after it is defined. Because it is an aggregate type, it can carry more data than ordinary types.

Structure declaration use

Structure types can be declared and initialized in the same way as ordinary strings and integers.

A variable p of person type is declared below. Because the variable p is not initialized, the zero value of the field in the structure will be used by default.

var p person

Of course, when declaring a structure variable, it can also be initialized by the literal amount of the structure, as shown in the following code:

p:=person{"Golang",5}

Using the method of short declaration and literal initialization, the name of the structure variable p is initialized as “golang”, and the age is initialized as 5, separated by commas.

After declaring a structure variable, you can use it. Run the following code to verify that the values of name and age are the same as those initialized.

fmt.Println(p.name,p.age)

In go language, the dot operator “.” is used to access the fields of a structure as well as to call the methods of a type.

When initializing a structure with literal values, the order of initialization values is very important and must be consistent with the order of field definitions.

In the structure of person, the first field is the name of string type, and the second field is the age of uint type. Therefore, during initialization, the type order of initialization value must correspond one by one before it can be compiled. That is to say, in the example {“golang”, 5}, the string golang for name must come before the number 5 for age.

Can I initialize it out of order? Of course, you can, but you need to point out the name of the field, as follows:

p:=person{age:5,name:"Golang"}

Among them, the first is the integer age, which can also be compiled, because the explicit field:value This way, the go compiler will know which field to initialize.

Have you found that this method is similar to the initialization of map type, which is separated by colon. Go language reuses operations as much as possible and does not invent new expressions, which is convenient for us to remember and use.

Of course, you can only initialize the field age and use the default value of zero for the field name. As shown in the following code, it can still be compiled.

p:=person{age:30}

Field structure

Structure fields can be of any type, including custom structure types, such as the following code:

type person struct {
    name string
    age uint
    addr address
}
type address struct {
    province string
    city string
}

In this example, two structures are defined: person for person and address for address. In the structure person, there is a field of address type, addr, which is the custom structure.

In this way, using code to describe real entities will be more matching and more reusable. For a structure with nested structure field, its initialization is similar to that of a normal structure. It only needs to be initialized according to the corresponding type of the field, as shown in the following code:

p:=person{
    age:5,
    name:"Golang",
    addr:address{
        Province: "Beijing",
        City: "Beijing",
    },
}

If you need to access the value of the province field in the innermost layer of the structure, you can also use the dot operator, except that you need to use two dots, as shown in the following code:

fmt.Println(p.addr.province)

The first point obtains addr, and the second point obtains the province of addr.

Interface

Definition of interface

Interface is a kind of convention with the caller, it is a highly abstract type, not bound with specific implementation details. What an interface needs to do is to define a convention and tell the caller what to do, but do not need to know its internal implementation. This is different from the specific types we see, such as int, map, slice, etc.

The definition and structure of an interface are slightly different. Although they all start with the type keyword, the keyword of an interface is interface, which means that a custom type is an interface. In other words, stringer is an interface, which has a method string () string, as shown in the following code:

type Stringer interface {
    String() string
}

Tip: stringer is an interface of go SDK and belongs to FMT package.

For the stringer interface, it tells the caller to get a string through its string () method, which is the Convention of the interface. As for how the string is obtained and what it looks like, the interface doesn’t care, and the caller doesn’t care, because it’s up to the interface implementer.

Implementation of interface

The implementer of the interface must be a specific type. Continue to take the person structure as an example to implement the stringer interface, as shown in the following code:

func (p person) String()  string{
    return fmt.Sprintf("the name is %s,age is %d",p.name,p.age)
}

Define a method for the structure type person, which is the same as the signature (name, parameter and return value) of the method in the interface. In this way, the structure person implements the stringer interface.

Note: if an interface has multiple methods, each method that needs to implement the interface is considered to implement the interface.

After the stringer interface is implemented, it can be used. First, define a function that can print stringer interface, as follows:

func printString(s fmt.Stringer){
    fmt.Println(s.String())
}

The defined function printstring receives a parameter of stringer interface type, and then prints out the string returned by the string method of stringer interface.

The advantage of the printstring function is that it is interface oriented programming. As long as a type implements the stringer interface, it can print out the corresponding string, regardless of the specific type implementation.

Because person implements the stringer interface, the variable p can be used as a parameter of the function printstring and can be printed in the following way:

printString(p)

The results are as follows

the name is Golang,age is 5

Now let the structure address also implement the stringer interface, as shown in the following code:

func (addr address) String()  string{
    return fmt.Sprintf("the addr is %s%s",addr.province,addr.city)
}

Because the structure address also implements the stringer interface, the printstring function can be used directly to print out the address without any change, as shown below:

printString(p.addr)

The results are as follows

Output: the addr is Beijing

This is the advantage of interface oriented. As long as both sides of the definition and invocation meet the Convention, they can be used, regardless of the specific implementation. The implementer of the interface can also upgrade and refactor better without any impact, because the interface convention has not changed.

Value receiver and pointer receiver

If you want to implement an interface, you must implement all the methods provided by the interface. You also know to define a method, which includes value type receiver and pointer type receiver. Both can call methods, because the go compiler does the conversion automatically, so the value type receiver and the pointer type receiver are equivalent. However, in the implementation of the interface, the value type receiver is different from the pointer type receiver. The difference between the two is analyzed in detail below.
Test the following call

printString(&p)

After testing, you will find that it is OK to pass the pointer of variable p to printstring function as an argument, and the compilation runs normally. This proves that when the value type receiver implements the interface, both the type itself and the pointer type of the type implement the interface.

If the value receiver (P person) implements the stringer interface, then the type person and its pointer type * person implement the stringer interface.

Now, change the receiver to pointer type, as shown in the following code:

func (p *person) String()  string{
    return fmt.Sprintf("the name is %s,age is %d",p.name,p.age)
}

After modifying to a pointer type receiver, you will find that the code printstring (P) of the test fails to compile, and the following error will be prompted:

./main.go:17:13: cannot use p (type person) as type fmt.Stringer in argument to printString:
    person does not implement fmt.Stringer (String method has pointer receiver)

This means that the type person does not implement the stringer interface. This proves that only the corresponding pointer type is considered to implement the interface when the interface is implemented by the pointer type receiver.
The following table summarizes the interface implementation rules of these two receiver types:

Method recipient The type of implementation interface
(p person) Person and * person
(p *person) *person

It can be interpreted as follows:

  • When the value type is the receiver, both person type and * person type implement the interface.
  • When the pointer type is the receiver, only the * person type implements the interface.

It can be found that there are * person types to implement the interface, which also indicates that the pointer type is versatile, and it can implement the interface no matter which kind of receiver.

Factory function

Factory functions are generally used to create user-defined structures, which are easy for users to call. Take person type as an example, define them with the following code:

func NewPerson(name string) *person {
    return &person{name:name}
}

This paper defines a factory function newperson, which receives a string type parameter to represent the name of the person and returns a * person.

By creating user-defined structure with factory function, callers need not pay too much attention to the fields inside the structure, they just need to pass parameters to the factory function.

With the following code, you can create a variable P1 of type * person:

P1: = newperson ("Xiao Song")

Factory functions can also be used to create an interface. The advantage of factory functions is that they can hide the implementation of internal specific types, so that callers only need to pay attention to the use of the interface.

Now take errors. New, the factory function of go language, as an example, to demonstrate how to create an interface through the factory function and hide its internal implementation, as shown in the following code:

//Factory function, which returns an error interface. In fact, the specific implementation is * errorstring
func New(text string) error {
    return &errorString{text}
}
//Structure, an internal field s, stores error information
type errorString struct {
    s string
}
//Used to implement the error interface
func (e *errorString) Error() string {
    return e.s
}

Among them, errorstring is a structure type, which implements the error interface, so you can create a * errorstring type through the new factory function and return it through the interface error.

This is interface oriented programming. If you refactor the code, even if you change another structure to implement the error interface, it will not affect the caller, because the interface has not changed.

Inheritance and combination

There is no concept of inheritance in go language, so there is no parent-child relationship between structure and interface. Go language advocates composition, which is more flexible to achieve the purpose of code reuse.

Take the interface of go language IO standard package as an example to explain the combination of types (also called nesting), as shown in the following code:

type Reader interface {
    Read(p []byte) (n int, err error)
}
type Writer interface {
    Write(p []byte) (n int, err error)
}
//Readwriter is a combination of reader and writer
type ReadWriter interface {
    Reader
    Writer
}

The readwriter interface is the combination of reader and writer. After the combination, the readwriter interface has all the methods in reader and writer. In this way, the new interface readwriter does not need to define its own methods, and the combination of reader and writer is OK.

Not only interfaces can be combined, but also structures can be combined. Now, the address structure can be combined into the structure person instead of being treated as a field, as follows:

type person struct {
    name string
    age uint
    address
}

Put the structure type in directly, that is, combination, no field name is needed. After combination, the combined address is called internal type, and person is called external type. After modifying the person structure, the declaration and use also need to be modified, as follows:

p:=person{
        age:5,
        name:"Golang",
        address:address{
            Province: "Beijing",
            City: "Beijing",
        },
    }
//Just like using your own field, use it directly
fmt.Println(p.province)

Because person combines address, the address field is just like person’s own and can be used directly.

After type combination, external types can use not only the fields of internal types, but also the methods of internal types, just like using their own methods. If the outer type defines the same method as the inner type, the outer type will override the inner type, which is the method override.

Tip: Method override does not affect the method implementation of an inner type.

Type Asserts

With the interface and the type that implements the interface, there are type assertions. Type assertion is used to determine whether the value of an interface is a specific type that implements the interface.
As follows:

func (p *person) String()  string{
    return fmt.Sprintf("the name is %s,age is %d",p.name,p.age)
}
func (addr address) String()  string{
    return fmt.Sprintf("the addr is %s%s",addr.province,addr.city)
}

You can see that * person and address both implement the stringer interface, and then explain the type assertion through the following example:

    var s fmt.Stringer
    s = p1
    p2:=s.(*person)
    fmt.Println(p2)

As shown above, the interface variable s is called the value of interface FMT. Stringer, which is assigned by P1. Then, using the type assertion expression s. (person), try to return a P2. If the value s of the interface is a person, then the type assertion is correct and P2 can be returned normally. If the value s of the interface is not a * person, an exception will be thrown at runtime, and the program will terminate.

Tip: the P2 returned here is of * person type, that is, the type conversion is completed at the same time when the type is asserted.

In the above example, because s is indeed a * person, it will not be abnormal and can return P2 normally. However, if you add the following code to assert the address type of S, some problems will arise:

a:=s.(address)
fmt.Println(a)

This code will not have any problems when compiling, because address implements the stringer interface, but when running, it will throw the following exception information:

panic: interface conversion: fmt.Stringer is *main.person, not main.address

This is obviously not in line with the original intention. Originally, I wanted to determine whether the value of an interface is a specific type, but I can’t cause the program exception just because the judgment fails. With this in mind, the go language provides multiple value returns for type assertions, as follows:

a,ok:=s.(address)
    if ok {
        fmt.Println(a)
    }else {
        FMT. Println ("s is not an address")
    }

The second value “OK” returned by a type assertion is a sign of whether the assertion is successful. If it is true, it is successful, otherwise it fails.

summary

Structure is the description of the real world, and interface is the specification and abstraction of a certain kind of behavior. Through them, code can be abstracted and reused. At the same time, interface oriented programming can be used to hide the specific implementation details, making the written code more flexible and adaptable.

This work adoptsCC agreementReprint must indicate the author and the link of this article

golang

Recommended Today

Analysis on the investigation scheme of the integrated platform of medical and welfare (2)

Business Tags: hospital information integration platform, Internet hospital, Internet nursing, chronic disease follow-up Technical labels: ESB, ETL + CDC, NLP, FAAS, SaaS, Hadoop, microservice   Technology wechat group:Add wechat: wonter send: technical QMedical wechat group:Add wechat: wonter send: Medical Q   —————— BEGIN ——————   Analysis on the investigation scheme of Neusoft integration platform (1) […]