面向对象编程
Go 并不是完全面向对象的编程语言。
Go 官网的 FAQ 回答了 Go 是否是面向对象语言,摘录如下:
可以说是,也可以说不是。虽然 Go 有类型和方法,支持面向对象的编程风格,但却没有类型的层次结构。Go 中的“接口”概念提供了一种不同的方法,我们认为它易于使用,也更为普遍。Go 也可以将结构体嵌套使用,这与子类化(Subclassing)类似,但并不完全相同。此外,Go 提供的特性比 C++ 或 Java 更为通用:子类可以由任何类型的数据来定义,甚至是内建类型(如简单的“未装箱的”整型)。这在结构体(类)中没有受到限制。
使用结构体,而非类
Go 不支持类,而是提供了结构体。结构体中可以添加方法。这样可以将数据和操作数据的方法绑定在一起,实现与类相似的效果。
先来看一个例子,建立如下目录:
编辑people.go:
package demo
import "fmt"
type Boy struct {
Name string
Age int
Sex string
}
func (b Boy)GetBoy() int{
fmt.Println(b.Name, b.Age, b.Sex)
return b.Age
}
一个结构体,挂了一个方法。
编辑main.go
package main
import "oop_demo/demo"
func main() {
b := demo.Boy{"allen", 20,"男"}
b.GetBoy()
}
运行main.go。
以上就是可以用结构体实现面向对象的代码。
但是,这种方式如果在main中调用方法而未赋值的时候,会自动使用Boy结构体中变量的零值,如果在Java或Python这种OOP语言中,是使用构造器来解决这种问题,Go 并不支持构造器。如果某类型的零值不可用,需要程序员来隐藏该类型,避免从其他包直接访问。
使用New()函数,而非构造器
更改 people.go:
package demo
import "fmt"
type boy struct {
name string
age int
sex string
}
func New(name string, age int, sex string) boy{
b := boy {name, age, sex}
return b
}
func (b boy)GetBoy() int{
fmt.Println(b.name, b.age, b.sex)
return b.age
}
更改main.go
package main
import "oop_demo/demo"
func main() {
b := demo.New("allen", 20,"男")
b.GetBoy()
}
运行main.go查看结果与之前相同。
虽然 Go 不支持类,但结构体能够很好地取代类,而以 New(parameters) 签名的方法可以替代构造器。
组合取代继承
Go 不支持继承,但它支持组合(Composition)。组合一般定义为“合并在一起”。汽车就是一个关于组合的例子:一辆汽车由车轮、引擎和其他各种部件组合在一起。
1.通过嵌套结构体进行组合
一旦结构体内嵌套了一个结构体字段,Go 可以使我们访问其嵌套的字段,好像这些字段属于外部结构体一样。所以上面第 11 行的 p.author.fullName() 可以替换为 p.fullName()。于是,details() 方法可以重写,如下所示:
type post struct {
title string
content string
author
}
func (p post) details() {
fmt.Println("Title: ", p.title)
fmt.Println("Content: ", p.content)
fmt.Println("Author: ", p.author.fullName())
fmt.Println("Bio: ", p.author.bio)
}
重写
func (p post) details() {
fmt.Println("Title: ", p.title)
fmt.Println("Content: ", p.content)
fmt.Println("Author: ", p.fullName())
fmt.Println("Bio: ", p.bio)
}
2.结构体切片的嵌套
package main
import (
"fmt"
)
type author struct {
firstName string
lastName string
bio string
}
func (a author) fullName() string {
return fmt.Sprintf("%s %s", a.firstName, a.lastName)
}
type post struct {
title string
content string
author
}
func (p post) details() {
fmt.Println("Title: ", p.title)
fmt.Println("Content: ", p.content)
fmt.Println("Author: ", p.fullName())
fmt.Println("Bio: ", p.bio)
}
type website struct {
posts []post
}
func (w website) contents() {
fmt.Println("Contents of Website\n")
for _, v := range w.posts {
v.details()
fmt.Println()
}
}
func main() {
author1 := author{
"Naveen",
"Ramanathan",
"Golang Enthusiast",
}
post1 := post{
"Inheritance in Go",
"Go supports composition instead of inheritance",
author1,
}
post2 := post{
"Struct instead of Classes in Go",
"Go does not support classes but methods can be added to structs",
author1,
}
post3 := post{
"Concurrency",
"Go is a concurrent language and not a parallel one",
author1,
}
w := website{
posts: []post{post1, post2, post3},
}
w.contents()
}
多态
Go 通过接口来实现多态。我们已经讨论过,在 Go 语言中,我们是隐式地实现接口。一个类型如果定义了接口所声明的全部方法,那它就实现了该接口。
用接口实现多态
关键代码:
type Income interface {
calculate() int
source() string
}
type FixedBilling struct {
projectName string
biddedAmount int
}
type TimeAndMaterial struct {
projectName string
noOfHours int
hourlyRate int
}
func (fb FixedBilling) calculate() int {
return fb.biddedAmount
}
func (fb FixedBilling) source() string {
return fb.projectName
}
func (tm TimeAndMaterial) calculate() int{
return tm.noOfHours * tm.hourlyRate
}
func (tm TimeAndMaterial) source() string {
return tm.projectName
}
func calculateNetIncome(ic []Income) {
var count int = 0
for _, v := range ic{
fmt.Println(v.calculate())
fmt.Println(v.source())
count += v.calculate()
}
fmt.Println(count)
}
func main() {
pro1 := FixedBilling{"allen", 12000}
pro2 := FixedBilling{"mary", 7000}
pro3 := TimeAndMaterial{"tom", 12, 80}
fmt.Println(pro1, pro2, pro3)
ic := []Income{pro1, pro2, pro3}
calculateNetIncome(ic)
}
上边代码中,FixedBilling 结构体和 TimeAndMaterial 都实现了接口中的两个方法,所以在calculateNetIncome函数中,可以通用通过v.calculate()调用不同结构体的calculate方法,这就是简单的多态。
参考 https://studygolang.com/articles/12681
Defer
defer 语句的用途是:含有 defer 语句的函数,会在该函数将要返回之前,调用另一个函数。
defer
不仅限于函数的调用,调用方法也是合法的。
示例:
package main
import (
"fmt"
)
func finished() {
fmt.Println("Finished finding largest")
}
func largest(nums []int) {
defer finished()
fmt.Println("Started finding largest")
max := nums[0]
for _, v := range nums {
if v > max {
max = v
}
}
fmt.Println("Largest number in", nums, "is", max)
}
func main() {
nums := []int{78, 109, 2, 563, 300}
largest(nums)
}
输出:
Started finding largest
Largest number in [78 109 2 563 300] is 563
Finished finding largest
延迟方法
package main
import (
"fmt"
)
type person struct {
firstName string
lastName string
}
func (p person) fullName() {
fmt.Printf("%s %s",p.firstName,p.lastName)
}
func main() {
p := person {
firstName: "John",
lastName: "Smith",
}
defer p.fullName()
fmt.Printf("Welcome ")
}
运行结果:
Welcome John Smith
延迟函数实参取值(Arguments Evaluation)
在 Go 语言中,并非在调用延迟函数的时候才确定实参,而是当执行 defer 语句的时候,就会对延迟函数的实参进行求值。
package main
import (
"fmt"
)
func printA(a int) {
fmt.Println("value of a in deferred function", a)
}
func main() {
a := 5
defer printA(a)
a = 10
fmt.Println("value of a before deferred function call", a)
}
可以看到结果,a 变量值仍然为 5。
defer 栈
当一个函数内多次调用 defer 时,Go 会把 defer 调用放入到一个栈中,随后按照后进先出(Last In First Out, LIFO)的顺序执行。
func demo1() {
fmt.Println("allen")
}
func demo2() {
fmt.Println("is")
}
func demo3() {
fmt.Println("student")
}
func main() {
defer demo3()
defer demo2()
defer demo1()
fmt.Println("end")
}
输出结果:
end
allen
is
student
再比如:字符串逆序输出
func main() {
name := "Naveen"
fmt.Printf("Orignal String: %s\n", name)
fmt.Printf("Reversed String: ")
for _, v := range []rune(name) {
defer fmt.Printf("%c", v)
}
}