https://studygolang.com/articles/1113
Overview
In the Go language, what happens if a struct and an embedded field implement the same interface at the same time? Let's guess, there are two problems:
- Will the compiler make an error because we have two interface implementations at the same time?
- If the compiler accepts such a definition, how does the compiler determine which implementation to use when the interface is called?
After writing some test code and reading the standard carefully, I found something interesting and felt it necessary to share it, so let's start with the way in the Go language.
Method
There are both functions and methods in the Go language. A method is a function that contains the recipient, which can be either a value of a named type or struct type or a pointer. All methods of a given type belong to the method set of that type.
The following defines a struct type and a method of that type:
type User struct { Name string Email string}func (u User) Notify() error
First we define a User
struct type called, and then define a method of the type called Notify
, the recipient of the method is a User
type of value. To invoke Notify
a method we need a User
value of type or a pointer:
// User 类型的值可以调用接受者是值的方法damon := User{"AriesDevil", "[email protected]"}damon.Notify()// User 类型的指针同样可以调用接受者是值的方法alimon := &User{"A-limon", "[email protected]"}alimon.Notify()
In this example, when we use the pointer, Go adjusts and references the pointer so that the call can be executed. Note that when the recipient is not a pointer, the method operates a copy of the value of the recipient (meaning that even if you use a pointer to call a function, but the function's recipient is a value type, the function's internal operation is the operation of the copy, not the pointer operation, see:/http PLAY.GOLANG.ORG/P/DBHWU0P1PV).
We can modify the Notify
method so that its recipient uses the pointer type:
func (u *User) Notify() error
One more call (note: When the recipient is a pointer, that is, using a value type to invoke the inside of the function is also the operation of the pointer, see: HTTP://PLAY.GOLANG.ORG/P/SYBB4XPFPH):
// User 类型的值可以调用接受者是指针的方法damon := User{"AriesDevil", "[email protected]"}damon.Notify()// User 类型的指针同样可以调用接受者是指针的方法alimon := &User{"A-limon", "[email protected]"}alimon.Notify()
If you do not know when to use the value, when should use the pointer as the recipient, you can go to see this introduction. This article also contains a community-agreed how to name the recipient.
Interface
The interface in the Go language is unique and offers an incredibly wide range of flexibility and abstraction. They specify a specific type of value and the pointer behaves in a specific way. From a language perspective, an interface is a type that specifies a set of methods that all methods are considered to be interface types.
The following defines an interface:
type Notifier interface { Notify() error}
We define an interface called Notifier
and include a Notify
method. When an interface contains only one method, the suffix is added when the interface is named according to the Go language Convention -er
. This Convention is useful, especially when interfaces and methods have the same name and meaning.
We can define as many methods as possible in the interface, but in the Go language standard library It is difficult to find an interface that contains more than two methods.
Implementing interfaces
The Go language is a special one when it comes to how we can get our types to implement interfaces. The Go language does not require an explicit implementation of the type's interface. If all the methods in an interface are implemented by our type, then we say that the type implements the interface.
Let's go on to the previous example, define a function to accept either a value or a pointer of the type that implements the interface Notifier
:
func SendNotification(notify Notifier) error { return notify.Notify()}
SendNotification
The function calls the Notify
method, which is implemented by passing in a value or pointer to the function. A function can then be used to execute any of the values that implement the interface or the specified behavior of the pointer.
Use our User
type to implement the interface and pass in a User
value of type to invoke the SendNotification
method:
func (u *User) Notify() error { log.Printf("User: Sending User Email To %s<%s>\n", u.Name, u.Email) return nil}func main() { user := User{ Name: "AriesDevil", Email: "[email protected]", } SendNotification(user)}// Output:cannot use user (type User) as type Notifier in function argument:User does not implement Notifier (Notify method has pointer receiver)
Detail code: Http://play.golang.org/p/KG8-Qb7gqM
Why does the compiler not consider our value to be the type that implements the interface? The invocation rules of an interface are based on how the recipients and interfaces of these methods are called. The following are the rules defined in the language specification, which are used to illustrate whether we have a type of value or pointer that implements the interface:
*T
the set of callable methods for a type contains *T
T
all the set of methods that the recipient is or
This rule says that if the interface variable that we use to invoke a particular interface method is a pointer type, then the recipient of the method can be a value type or a pointer type. Obviously our example does not conform to the rule, because SendNotification
the interface variable that we pass into the function is a value type.
T
the set of callable methods for a type contains T
all the methods that are accepted by the recipient
This rule says that if the interface variable that we use to invoke a particular interface method is a value type, then the recipient of the method must also be the value type that the method can be called. Obviously our example also does not conform to this rule, because Notify
the recipient of our method is a pointer type.
There are only two rules in the language specification, and I have drawn the rules that conform to our example through these two rules:
T
the callable method set of the type does not contain *T
the method that the recipient is
We happened to catch up with the rule I inferred, so the compiler would give an error. Notify
method uses a pointer type as the recipient and we call the method by value type. The solution is also very simple, we just need User
to pass in the value of the address to the SendNotification
function just fine:
func main() { user := &User{ Name: "AriesDevil", Email: "[email protected]", } SendNotification(user)}// Output:User: Sending User Email To AriesDevil<[email protected]>
Detail code: Http://play.golang.org/p/kEKzyTfLjA
Embedding type
Struct types can contain anonymous or embedded fields. Also called embedding a type. When we embed a type into a struct, the name of that type acts as the field name for the embedded field.
The following defines a new type and then embeds our User
type in it:
type Admin struct { User Level string}
We define a new type Admin
and then User
embed the type, noting that this is not called inheritance but called a combination. User
type Admin
has no relationship with type.
Let's change the main
function, create a Admin
variable of type and pass the address of the variable into the SendNotification
function:
func main() { admin := &Admin{ User: User{ Name: "AriesDevil", Email: "[email protected]", }, Level: "master", } SendNotification(admin)}// OutputUser: Sending User Email To AriesDevil<[email protected]>
Detail code: Http://play.golang.org/p/ivzzzk78TC
It turns out that we can Admin
type a pointer to invoke the SendNotification
function. Now Admin
the type also User
implements the interface by means of a method promoted from the embedded type.
If a Admin
type contains User
fields and methods of a type, what is the relationship between them in the struct?
When we embed a type, the method of this type becomes the method of the outer type, but when it is called, the recipient of the method is the inner type (the embedded type), not the outer type. –effective Go
So the name of the embedded type acts as the field name, and the embedded type exists as an internal type, we can use the following calling method:
admin.User.Notify()// OutputUser: Sending User Email To AriesDevil<[email protected]>
Detail code: Http://play.golang.org/p/0WL_5Q6mao
Here we access the fields and methods of the internal type through the type name. However, these fields and methods are also promoted to the external type:
admin.Notify()// OutputUser: Sending User Email To AriesDevil<[email protected]>
Detail code: Http://play.golang.org/p/2snaaJojRo
So invoking a method from an external type Notify
is essentially a method of an internal type.
The following are the rules for promoting the set of intrinsic type methods in the Go language:
Given a struct type S
and a named T
type, the method promotes such inclusion in the struct method set as specified below:
- If you
S
include an anonymous field T
, S
and *S
The method set contains the T
method promotion for the recipient.
This rule says that when we embed a type, the recipient of the embedded type is the value type of the method that is promoted and can be called by the values and pointers of the external type.
- Method
*S
set for a type contains *T
method promotion for the recipient
The rule is that when we embed a type, the set of methods that can be called by an external type's pointer is only the recipient of the embedded type as a set of methods of the pointer type, that is, when the external type uses pointers to invoke methods of the inner type, only the set of internal types that the recipient is a pointer type is promoted.
- If
S
an anonymous field is included *T
, S
and *S
The method set contains the T
*T
method promoted by the recipient or
This rule says that when we embed a pointer to a type, the recipient of the embedded type is promoted as a value type or pointer type, and can be called by the value of an external type or by a pointer.
This is the only three rules in the method promotion in the language specification, and I derive a rule based on this:
- If
S
an anonymous field is included T
, S
the set of methods does not contain *T
the method promotion for the recipient.
This rule says that when we embed a type, the method that the recipient of the embedded type is a pointer cannot be accessed by the value of the external type. This is also consistent with the interface rules we stated above.
Answer the question at the beginning
Now we can write the program to answer the two questions at the beginning, first we let the Admin
type implement Notifier
interface:
func (a *Admin) Notify() error { log.Printf("Admin: Sending Admin Email To %s<%s>\n", a.Name, a.Email) return nil}
Admin
Type implements an interface that displays information about an admin. When we use Admin
a pointer to a type to invoke SendNotification
a function, this will help us determine which interface implementation is called.
Now create a Admin
value of type and pass its address to the SendNotification
function to see what happens:
func main() { admin := &Admin{ User: User{ Name: "AriesDevil", Email: "[email protected]", }, Level: "master", } SendNotification(admin)}// OutputAdmin: Sending Admin Email To AriesDevil<[email protected]>
Detail code: Http://play.golang.org/p/JGhFaJnGpS
As expected, Admin
the type of the interface implementation is called by the SendNotification
function. Now what happens when we invoke the method with an external type Notify
:
admin.Notify()// OutputAdmin: Sending Admin Email To AriesDevil<[email protected]>
Detail code: Http://play.golang.org/p/EGqK6DwBOi
We get the Admin
output of the type of interface implementation. An User
interface implementation of type is not promoted to an external type.
Now we have enough evidence to answer the question:
- Will the compiler make an error because we have two interface implementations at the same time?
No, because when we use an embedded type, the type name serves as the field name. An embedded type contains its own fields and methods as the inner type of the struct, and has a unique name. So we can have an internal implementation of the same interface and an external implementation.
- If the compiler accepts such a definition, how does the compiler determine which implementation to use when the interface is called?
If the external type contains an interface implementation that meets the requirements, it will be used. Otherwise, an interface implementation of any internal type can be used directly by an external type by means of a method promotion.
Summarize
In the Go language, methods, interfaces, and embedded types work together in a unique way. These features can help us organize structures like object-oriented and then achieve the same goal, and there are no other complex things. Using the language features discussed in this article, we can build a framework of abstraction and scalability with minimal code.
Reference Http://blog.go-china.org/26-methods-interfaces-and-embedded-types-in-golang
methods, interfaces, and embedded types in the Go language