Go语言现代web开发13 方法和接口

方法

As you probably noticed, there are no classes in the Go programming language. But we can mimic this by declaring functions on types. The type which declares functions is called the receiver argument and the function declared on the type is called the method. The receiver argument is placed between keyword function and method name. This code block will declare type Circle and two methods for that type.

正如您可能注意到的,Go编程语言中没有类。但是我们可以通过在类型上声明函数来模拟这一点。声明函数的类型称为接收者参数,在该类型上声明的函数称为方法。receiver参数位于关键字function和方法名之间。这个代码块将声明类型Circle和该类型的两个方法。

go 复制代码
type Circle struct {
    r float64
}

func (c Circle) Perimeter() float64 {
    return 2 * c.r * math.Pi
}

func (c Circle) Area() float64 {
    return c.r * c.r * math.Pi
}

We use operator .(dot) to call a method defined on type.

我们使用操作符号.(点) 来调用定义在类型上的方法。

go 复制代码
func main(){
    c := Circle{5.0}
    fmt.Println(c.Perimeter())
    fmt.Println(c.Area())
}

There is no explicit way to declare a method on basic types, but there is a catch on how to override this restriction. The receiver must be in the same package as the method, so if we have a need to define a method on an integer, we must redeclare the integer type inside our package.

没有显式的方法在基本类型上声明方法,但是在如何覆盖此限制方面存在一个问题。接收方必须与方法在同一个包中,因此如果需要在整数上定义方法,则必须在包中重新声明整数类型。

go 复制代码
type CustomInt int

funct (ci CustomInt) Double() int {
    return int(ci *2)
}

Technically, the previous method is not declared on basic type but the re-declared type has all the characteristics of the base type from which it was derived.

从技术上讲,前面的方法不是在基本类型上声明的,但是重新声明的类型具有派生它的基类型的所有特征。

A method with receiver argument will act the same as function with it as an argument. The method from the previous example is identical with the following function.

带有receiver参数的方法与带有receiver参数的函数的行为相同。前面示例中的方法与下面的函数相同。

go 复制代码
func Double(ci CustomInt) int {
    return int(ci *2)
}

We can also declare a method on pointers. Pointer receivers are quite common for two reasons:

  • Method can modify the value to which the reicever points.
  • Method will operate on a copy of the original receiver value. With a pointer receiver, we will avoid copying of whole value, only the memory address will be copied.

我们也可以在指针上声明方法。指针接收器非常常见,原因有两个:

  • 方法可以修改接收方所指向的值。
  • 方法将对原始接收器值的副本进行操作。使用指针接收器,我们将避免复制整个值,只复制内存地址。

When we declare methods for the concrete type, we should decide which receiver we will use, value or pointer. It is not a good practice to combine them.

当为具体类型声明方法时,应该决定使用哪个接收者,值还是指针。把它们结合起来并不是一个好的做法。

接口

The interface can be defined as a set of method signatures. We define interface with keywords type and interface, between which we specify the name of the interface, followed by method signatures between curly brackets. This code block will declare interface Geometry with two methods, for the calculation of perimeter and area of different shapes.

接口可以定义为一组方法签名。我们用关键字type和interface定义接口,在这两个关键字之间指定接口的名称,后面是花括号之间的方法签名。这个代码块将用两个方法声明接口Geometry,用于计算不同形状的周长和面积。

go 复制代码
type Geometry interface {
    Perimeter() int
    Area() int
}

Most programming languages have keywords dedicated to interface implementation. But that is not the case with Go programming language. Any type that implements all interface methods will automatically implement the interface.

大多数编程语言都有专门用于接口实现的关键字。但是Go编程语言不是这样的。实现所有接口方法的任何类型都将自动实现该接口。

Here we can see how type Square implements Geometry interface.

我们来看看Square是怎么实现Geometry接口的。

go 复制代码
type Square struct {
    a int
}

func (s Square) Perimeter() int {
    return s.a * 4
}

func (s Square) Area() int {
    return s.a * s.a
}

Now, we can create a Square whose side is equal to 5 and execute methods on it.

现在,我们可以创建一个边长等于5的Square对象,然后调用它的方法。

go 复制代码
func main() {
    s := Square{5}
    fmt.Println(s.Perimeter())
    fmt.Println(s.Area())
}

If we have a function with interface type as an argument, we can pass any type that implements that interface to function. Here is an example of that kind of function.

如果函数有接口类型作为参数,则可以将实现该接口的任何类型传递给函数。这是这类函数的一个例子。

go 复制代码
func Details (g Geometry) {
    fmt.Println("Perimeter:", g.Perimeter())
    fmt.Println("Area:", g.Area())
}

This function will display details about perimeter and area. Inside the main() function, we can create a Square and pass it to the Details() function. The code will be executed successfully and proper information will be displayed.

此功能将显示有关周长和面积的详细信息。在main()函数中,我们可以创建一个Square并将其传递给Details()函数。代码将成功执行,并显示适当的信息。

go 复制代码
func main() {
    s := Square{5}
    Details(s)
}

Under the hood, the interface can be examined as a pair consisting of a type and value. Following the previous example, the type is Square and the value is newly created Square, where a side is 5. It is interesting that the value can be nil. This situation is not irregular but should be handled in order to avoid incorrect execution of the program.

在底层,接口可以作为由类型和值组成的一对来检查。在前面的例子中,类型是Square,值是新创建的Square,边长是5。有趣的是,该值可以为nil。这种情况并非不正常,但应该加以处理,以避免程序的错误执行。

We can declare a variable with the interface as a type. Thisi variable will be interpreted a s an interface that contains neither type nor value, called a nil interface. If we try to call any method on a nil interface, a run-time error will occur.

我们可以将接口中的变量声明为类型。这个变量将被解释为一个既不包含类型也不包含值的接口,称为nil接口。如果我们尝试在nil接口上调用任何方法,将会发生运行时错误。

go 复制代码
var g Geometry

g.Perimeter() // <- this line will cause run-time exeception

An empty interface is an interface without ethods and can hold values of any type. How is this possible? Type will implement the interface if it implements all methods from the declaration of interface; an empty interface has zero methods, so technically every type implements it.

空接口是没有方法的接口,可以保存任何类型的值。这怎么可能呢?如果Type实现了接口声明中的所有方法,它将实现该接口;空接口没有方法,因此从技术上讲,每个类型都实现它。

The empty interface is a very useful thing. Each time we need to handle an unknown type in our code, we can use the empty interface. One good example of usage is the Print() function from the fmt package. The function takes any number of type interface{} so that we can pass different types (strings, numbers, Boolean, and so on) and values of that type will be printed on standard output. The empty interface will be surpassed by generics.

空接口是一个非常有用的东西。每次需要处理代码中的未知类型时,都可以使用空接口。一个很好的使用示例是fmt包中的Print()函数。该函数接受任意数量的类型interface{},因此我们可以传递不同的类型(字符串、数字、布尔值等),并且该类型的值将在标准输出中打印。泛型将超越空接口。

If we declare a variable of interface type, we can assign a value of any type that implements that interface. This can put us in a situation where we are not sure what is assigned to a variable. Fortunately, there is a concept known as type assertion that can help us. This is how we can check if the interface variable holds the value of a Square type.

如果声明了接口类型的变量,则可以为实现该接口的任何类型赋值。这可能会使我们处于一种情况,即我们不确定变量的赋值是什么。幸运的是,有一个称为类型断言的概念可以帮助我们。这就是我们如何检查接口变量是否包含Square类型的值。

go 复制代码
var g Geoetry = Square{5}
v, ok := g.(Square)

As we can see two values will be returned, the underlying value and Boolean (variables v and ok in the previous example). If a variable holds a Square type (which was the case in the previous example), v will hold the actual value of the interface variable (Square whose side is equal to5), and ok will be true. Otherwise, the default value for concrete type will be assigned to v (Square with default values assigned to all fields), and false will be assigned to ok. The second valeu (ok) can be omitted, but this can cause an error in case of an unsuccessful assertion, so it is not recommended to do so.

正如我们所看到的,将返回两个值,底层值和布尔值(前面示例中的变量v和ok)。如果变量为Square类型(前面的例子就是这种情况),v将保存接口变量的实际值(边长等于5的Square), ok将为真。否则,具体类型的默认值将被赋给v(将默认值赋给所有字段的Square), false将被赋给ok。第二个值(ok)可以省略,但如果断言不成功,这可能会导致错误,因此不建议这样做。

go 复制代码
swith g.(type) {
    case Square:
        fmt.Println("Square")
    case Rectangle:
        fmt.Println("Rectangle")
    default:
        fmt.Println("Unkonw type")
}

Here is one interesting example for the end of interface section. This function will check if the provided value is equal to nil.

下面是界面结束部分的一个有趣示例。这个函数将检查提供的值是否等于nil。

go 复制代码
func IsNill(a interface{}) bool {
    return a == nil
}

In our main() function, we will just declare Square and pass it to the IsNil() function.

在main()函数中,我们将声明Square并将其传递给IsNil()函数。

go 复制代码
func main() {
    var s *Square
    fmt.Println(IsNil(s))
}

What is an expected output? It would be logical that IsNil() function returns true, but actually, it will return false! As we mentioned before, interface has two fields type and value. Even if we assign nil pointer to the interface, a value will be assigned to type (in our case, type will be *Square and the value will be nil). An interface will be nill only when both the type and value are nil.

期望的输出是什么?IsNil()函数返回true是合乎逻辑的,但实际上,它将返回false!如前所述,接口有两个字段type和value。即使我们将nil指针赋给接口,值也会被赋给type(在我们的例子中,type是*Square,值是nil)。只有当接口的类型和值都为nil时,接口才会为nil。

Go programming languages have a couple of interfaces that are widely used. Some of the popular ones are Stringers, Errors, Readers, and Images.

Go编程语言有几个被广泛使用的接口。一些流行的是Stringers, Errors, Readers和Images。

相关推荐
gqkmiss22 分钟前
Chrome 浏览器插件获取网页 iframe 中的 window 对象
前端·chrome·iframe·postmessage·chrome 插件
mmsx1 小时前
android sqlite 数据库简单封装示例(java)
android·java·数据库
慕城南风2 小时前
Go语言中的defer,panic,recover 与错误处理
golang·go
zpjing~.~2 小时前
Mongo 分页判断是否有下一页
数据库
2401_857600952 小时前
技术与教育的融合:构建现代成绩管理系统
数据库·oracle
秋恬意2 小时前
Mybatis能执行一对一、一对多的关联查询吗?都有哪些实现方式,以及它们之间的区别
java·数据库·mybatis
m0_748247552 小时前
Web 应用项目开发全流程解析与实战经验分享
开发语言·前端·php
潇湘秦3 小时前
一文了解Oracle数据库如何连接(1)
数据库·oracle
雅冰石3 小时前
oracle怎样使用logmnr恢复误删除的数据
数据库·oracle
web前端神器3 小时前
mongodb给不同的库设置不同的密码进行连接
数据库·mongodb