Go Interfaces
An interface (interface) is a type in the Go language used to define a set of behaviors. It specifies a type's behavioral contract by describing the methods that the type must implement.
Go provides another data type called an interface, which defines all methods with common characteristics together. Any other type that implements these methods is considered to have implemented this interface.
Go's interface design is simple yet powerful, serving as an important tool for implementing polymorphism and decoupling.
Interfaces allow us to bind different types to a set of common methods, thereby achieving polymorphism and flexible design.
### Characteristics of Interfaces
**Implicit Implementation**:
* There is no keyword in Go to explicitly declare that a type implements a certain interface.
* As long as a type implements all the methods required by an interface, that type is automatically considered to have implemented the interface.
**Interface Type Variables**:
* An interface variable can store any value that implements the interface.
* An interface variable actually contains two parts:
* **Dynamic Type**: Stores the actual type of the value.
* **Dynamic Value**: Stores the concrete value.
**Zero Value of an Interface**:
* The zero value of an interface is `nil`.
* An uninitialized interface variable has a value of `nil` and does not contain any dynamic type or value.
**Empty Interface**:
* Defined as `interface{}`, it can represent any type.
### Common Uses of Interfaces
1. **Polymorphism**: Different types implement the same interface to achieve polymorphic behavior.
2. **Decoupling**: Define dependencies through interfaces to reduce coupling between modules.
3. **Generalization**: Use the empty interface `interface{}` to represent any type.
* * *
## Interface Definition and Implementation
An interface is defined using the `interface` keyword, which contains method declarations.
## Example
/* Define an interface */
type interface_name interface{
method_name1
method_name2
method_name3
...
method_namen
}
/* Define a struct */
type struct_name struct{
/* variables */
}
/* Implement interface methods */
func(struct_name_variable struct_name) method_name1(){
/* method implementation */
}
...
func(struct_name_variable struct_name) method_namen(){
/* method implementation */
}
**Define a simple interface:**
type Shape interface { Area() float64 Perimeter() float64 }
* `Shape` is an interface that defines two methods: `Area` and `Perimeter`.
* Any type that implements these two methods is considered to have implemented the `Shape` interface.
**Implementing an interface:** A type implements an interface by implementing all the methods required by the interface.
## Example
package main
import(
"fmt"
"math"
)
// Define an interface
type Shape interface{
Area()float64
Perimeter()float64
}
// Define a struct
type Circle struct{
Radius float64
}
// Circle implements the Shape interface
func(c Circle) Area()float64{
return math.Pi * c.Radius * c.Radius
}
func(c Circle) Perimeter()float64{
return 2* math.Pi * c.Radius
}
func main(){
c := Circle{Radius:5}
var s Shape = c // Interface variable can store a type that implements the interface
fmt.Println("Area:", s.Area())
fmt.Println("Perimeter:", s.Perimeter())
}
Executing the above code, the output is:
Area: 78.53981633974483Perimeter: 31.41592653589793
* * *
## Empty Interface
The empty interface `interface{}` is a special interface in Go, representing the superset of all types.
* Any type implements the empty interface.
* It is commonly used in scenarios where data of any type needs to be stored, such as generic containers, universal parameters, etc.
## Example
package main
import"fmt"
func printValue(val interface{}){
fmt.Printf("Value: %v, Type: %Tn", val, val)
}
func main(){
printValue(42)// int
printValue("hello")// string
printValue(3.14)// float64
printValue([]int{1,2})// slice
}
Executing the above code, the output is:
Value: 42, Type: intValue: hello, Type: stringValue: 3.14, Type: float64 Value: , Type: []int
* * *
## Type Assertion
Type assertion is used to extract the underlying value from an interface type.
Basic syntax:
value := iface.(Type)
* `iface` is the interface variable.
* `Type` is the concrete type to assert.
* If the types do not match, it will trigger a `panic`.
## Example
package main
import"fmt"
func main(){
var i interface{}="hello"
str :=i.(string)// Type assertion
fmt.Println(str)// Output: hello
}
### Checked Type Assertion
To avoid panic, you can use a checked type assertion:
value, ok := iface.(Type)
* `ok` is a boolean value indicating whether the assertion was successful.
* If the assertion fails, `value` is the zero value, and `ok` is `false`.
## Example
package main
import"fmt"
func main(){
var i interface{}=42
if str, ok :=i.(string); ok {
fmt.Println("String:", str)
}else{
fmt.Println("Not a string")
}
}
Executing the above code, the output is:
Not a string
* * *
## Type Switch
`type switch` is a syntactic structure in Go used to execute different logic based on the concrete type of an interface variable.
## Example
package main
import"fmt"
func printType(val interface{}){
switch v := val.(type){
case int:
fmt.Println("Integer:", v)
case string:
fmt.Println("String:", v)
case float64:
fmt.Println("Float:", v)
default:
fmt.Println("Unknown type")
}
}
func main(){
printType(42)
printType("hello")
printType(3.14)
printType([]int{1,2,3})
}
Executing the above code, the output is:
Integer: 42String: hello Float: 3.14Unknown type
* * *
## Interface Composition
Interfaces can be composed through embedding to achieve more complex behavioral descriptions.
## Example
package main
import"fmt"
type Reader interface{
Read()string
}
type Writer interface{
Write(data string)
}
type ReadWriter interface{
Reader
Writer
}
type File struct{}
func(f File) Read()string{
return"Reading data"
}
func(f File) Write(data string){
fmt.Println("Writing data:", data)
}
func main(){
var rw ReadWriter = File{}
fmt.Println(rw.Read())
rw.Write("Hello, Go!")
}
* * *
## Dynamic Value and Dynamic Type
An interface variable actually contains two parts:
1. **Dynamic Type**: The concrete type stored in the interface variable.
2. **Dynamic Value**: The value of the concrete type.
Example of dynamic value and dynamic type:
## Example
package main
import"fmt"
func main(){
var i interface{}=42
fmt.Printf("Dynamic type: %T, Dynamic value: %vn", i, i)
}
Executing the above code, the output is:
Dynamic type: int, Dynamic value: 42
* * *
## Zero Value of an Interface
The zero value of an interface is `nil`.
When both the dynamic type and dynamic value of an interface variable are `nil`, the interface variable is `nil`.
Example of interface zero value:
## Example
package main
import"fmt"
func main(){
var i interface{}
fmt.Println(i==nil)// Output: true
}
* * *
## Practice Examples
The following two examples demonstrate the use of interfaces:
## Example 1
package main
import(
"fmt"
)
type Phone interface{
call()
}
type NokiaPhone struct{
}
func(nokiaPhone NokiaPhone) call(){
fmt.Println("I am Nokia, I can call you!")
}
type IPhone struct{
}
func(iPhone IPhone) call(){
fmt.Println("I am iPhone, I can call you!")
}
func main(){
var phone Phone
phone =new(NokiaPhone)
phone.call()
phone =new(IPhone)
phone.call()
}
In the above example, we defined an interface **Phone**, which has a method `call()`. Then, in the **main** function, we defined a variable of type **Phone** and assigned it to **NokiaPhone** and **IPhone** respectively. Then, we called the `call()` method, and the output is as follows:
I am Nokia, I can call you! I am iPhone, I can call you!
Second interface example:
## Example
package main
import"fmt"
type Shape interface{
area()float64
}
type Rectangle struct{
width float64
height float64
}
func(r Rectangle) area()float64{
return r.width * r.height
}
type Circle struct{
radius float64
}
func(c Circle) area()float64{
return 3.14* c.radius * c.radius
}
func main(){
var s Shape
s = Rectangle{width:10, height:5}
fmt.Printf("Rectangle area: %fn", s.area())
s = Circle{radius:3}
fmt.Printf("Circle area: %fn", s.area())
}
In the above example, we defined a `Shape` interface, which defines a method `area()` that returns a `float64` area value. Then, we defined two structs, `Rectangle` and `Circle`, which respectively implemented the `area()` method of the `Shape` interface. In the `main()` function, we first defined a variable `s` of type `Shape`, then assigned instances of `Rectangle` and `Circle` to it, and calculated their areas by calling the `area()` method and printed them. The output is as follows:
Rectangle area: 50.000000Circle area: 28.260000
It should be noted that an interface type variable can store the value of any type that implements that interface. In the example, we assigned instances of both `Rectangle` and `Circle` types to the `Shape` type variable `s` and called their area calculation methods through the `area()` method.
YouTip