go教程8
1. 合取代继承
Go 不支持继承,但它支持组合(Composition)。组合一般定义为“合并在一起”。汽车就是一个关于组合的例子:一辆汽车由车轮、引擎和其他各种部件组合在一起。
通过嵌套结构体进行组合
在 Go 中,通过在结构体内嵌套结构体,可以实现组合。
组合的典型例子就是博客帖子。每一个博客的帖子都有标题、内容和作者信息。使用组合可以很好地表示它们。通过学习本教程后面的内容,我们会知道如何实现组合。
我们首先创建一个 author 结构体。
package main
import(
"fmt"
)
typeauthorstruct{
firstNamestring
lastNamestring
biostring
}
func(a author)fullName()string{
returnfmt.Sprintf("%s %s",a.firstName,a.lastName)
}
在上面的代码片段中,我们创建了一个 author 结构体,author 的字段有 firstname、lastname 和 bio。我们还添加了一个 fullName() 方法,其中 author 作为接收者类型,该方法返回了作者的全名。
下一步我们创建 post 结构体。
typepoststruct{
titlestring
contentstring
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)
}
post 结构体的字段有 title 和 content。它还有一个嵌套的匿名字段 author。该字段指定 author 组成了 post 结构体。现在 post 可以访问 author 结构体的所有字段和方法。我们同样给 post 结构体添加了 details() 方法,用于打印标题、内容和作者的全名与简介。
一旦结构体内嵌套了一个结构体字段,Go 可以使我们访问其嵌套的字段,好像这些字段属于外部结构体一样。所以上面第 11 行的 p.author.fullName() 可以替换为 p.fullName()。于是,details() 方法可以重写,如下所示:
func(p post)details(){
fmt.Println("Title: ",p.title)
fmt.Println("Content: ",p.content)
fmt.Println("Author: ",p.fullName())
fmt.Println("Bio: ",p.bio)
}
现在,我们的 author 和 post 结构体都已准备就绪,我们来创建一个博客帖子来完成这个程序。
package main
import(
"fmt"
)
typeauthorstruct{
firstNamestring
lastNamestring
biostring
}
func(a author)fullName()string{
returnfmt.Sprintf("%s %s",a.firstName,a.lastName)
}
typepoststruct{
titlestring
contentstring
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)
}
funcmain(){
author1:=author{
"Naveen",
"Ramanathan",
"Golang Enthusiast",
}
post1:=post{
"Inheritance in Go",
"Go supports composition instead of inheritance",
author1,
}
post1.details()
}
在上面程序中,main 函数在第 31 行新建了一个 author 结构体变量。而在第 36 行,我们通过嵌套 author1 来创建一个 post。该程序输出:
Title:InheritanceinGo
Content:Go supports composition instead of inheritance
Author:Naveen Ramanathan
Bio:Golang Enthusiast
结构体切片的嵌套
我们可以进一步处理这个示例,使用博客帖子的切片来创建一个网站。
我们首先定义 website 结构体。请在上述代码里的 main 函数中,添加下面的代码,并运行它。
typewebsitestruct{
[]post
}
func(w website)contents(){
fmt.Println("Contents of Website\n")
for_,v:=rangew.posts{
v.details()
fmt.Println()
}
}
在你添加上述代码后,当你运行程序时,编译器将会报错,如下所示:
main.go:31:9:syntax error:unexpected[,expecting field name or embeddedtype
这项错误指出了嵌套的结构体切片 []post。错误的原因是结构体不能嵌套一个匿名切片。我们需要一个字段名。所以我们来修复这个错误,让编译器顺利通过。
typewebsitestruct{
posts[]post
}
可以看到,我给帖子的切片 []post 添加了字段名 posts。
现在我们来修改主函数,为我们的新网站创建一些帖子吧。
修改后的完整代码如下所示:
package main
import(
"fmt"
)
typeauthorstruct{
firstNamestring
lastNamestring
biostring
}
func(a author)fullName()string{
returnfmt.Sprintf("%s %s",a.firstName,a.lastName)
}
typepoststruct{
titlestring
contentstring
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)
}
typewebsitestruct{
posts[]post
}
func(w website)contents(){
fmt.Println("Contents of Website\n")
for_,v:=rangew.posts{
v.details()
fmt.Println()
}
}
funcmain(){
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()
}
在上面的主函数中,我们创建了一个作者 author1,以及三个帖子 post1、post2 和 post3。我们最后通过嵌套三个帖子,在第 62 行创建了网站 w,并在下一行显示内容。
程序会输出:
Contents of Website
Title:InheritanceinGo
Content:Go supports composition instead of inheritance
Author:Naveen Ramanathan
Bio:Golang Enthusiast
Title:Struct instead of ClassesinGo
Content:Go does not support classes but methods can be added to structs
Author:Naveen Ramanathan
Bio:Golang Enthusiast
Title:Concurrency
Content:Go is a concurrent language and not a parallel one
Author:Naveen Ramanathan
Bio:Golang Enthusiast
本教程到此结束。祝你愉快。
2. 多态
Go 通过接口来实现多态。我们已经讨论过,在 Go 语言中,我们是隐式地实现接口。一个类型如果定义了接口所声明的全部方法,那它就实现了该接口。现在我们来看看,利用接口,Go 是如何实现多态的。
使用接口实现多态
一个类型如果定义了接口的所有方法,那它就隐式地实现了该接口。
所有实现了接口的类型,都可以把它的值保存在一个接口类型的变量中。在 Go 中,我们使用接口的这种特性来实现多态。
通过一个程序我们来理解 Go 语言的多态,它会计算一个组织机构的净收益。为了简单起见,我们假设这个虚构的组织所获得的收入来源于两个项目:fixed billing 和 time and material。该组织的净收益等于这两个项目的收入总和。同样为了简单起见,我们假设货币单位是美元,而无需处理美分。因此货币只需简单地用 int 来表示。(我建议阅读 https://forum.golangbridge.org/t/what-is-the-proper-golang-equivalent-to-decimal-when-dealing-with-money/413 上的文章,学习如何表示美分。感谢 Andreas Matuschek 在评论区指出这一点。)
我们首先定义一个接口 Income。
typeIncomeinterface{
calculate()int
source()string
}
上面定义了接口 Interface,它包含了两个方法:calculate() 计算并返回项目的收入,而 source() 返回项目名称。
下面我们定义一个表示 FixedBilling 项目的结构体类型。
typeFixedBillingstruct{
projectNamestring
biddedAmountint
}
项目 FixedBillin 有两个字段:projectName 表示项目名称,而 biddedAmount 表示组织向该项目投标的金额。
TimeAndMaterial 结构体用于表示项目 Time and Material。
typeTimeAndMaterialstruct{
projectNamestring
noOfHoursint
hourlyRateint
}
结构体 TimeAndMaterial 拥有三个字段名:projectName、noOfHours 和 hourlyRate。
下一步我们给这些结构体类型定义方法,计算并返回实际收入和项目名称。
func(fb FixedBilling)calculate()int{
returnfb.biddedAmount
}
func(fb FixedBilling)source()string{
returnfb.projectName
}
func(tm TimeAndMaterial)calculate()int{
returntm.noOfHours*tm.hourlyRate
}
func(tm TimeAndMaterial)source()string{
returntm.projectName
}
在项目 FixedBilling 里面,收入就是项目的投标金额。因此我们返回 FixedBilling 类型的 calculate() 方法。
而在项目 TimeAndMaterial 里面,收入等于 noOfHours 和 hourlyRate 的乘积,作为 TimeAndMaterial 类型的 calculate() 方法的返回值。
我们还通过 source() 方法返回了表示收入来源的项目名称。
由于 FixedBilling 和 TimeAndMaterial 两个结构体都定义了 Income 接口的两个方法:calculate() 和 source(),因此这两个结构体都实现了 Income 接口。
我们来声明一个 calculateNetIncome 函数,用来计算并打印总收入。
funccalculateNetIncome(ic[]Income){
varnetincomeint=0
for_,income:=rangeic{
fmt.Printf("Income From %s = $%d\n",income.source(),income.calculate())
netincome+=income.calculate()
}
fmt.Printf("Net income of organisation = $%d",netincome)
}
上面的函数接收一个 Income 接口类型的切片作为参数。该函数会遍历这个接口切片,并依个调用 calculate() 方法,计算出总收入。该函数同样也会通过调用 source() 显示收入来源。根据 Income 接口的具体类型,程序会调用不同的 calculate() 和 source() 方法。于是,我们在 calculateNetIncome 函数中就实现了多态。
如果在该组织以后增加了新的收入来源,calculateNetIncome 无需修改一行代码,就可以正确地计算总收入了。
最后就剩下这个程序的 main 函数了。
funcmain(){
project1:=FixedBilling{projectName:"Project 1",biddedAmount:5000}
project2:=FixedBilling{projectName:"Project 2",biddedAmount:10000}
project3:=TimeAndMaterial{projectName:"Project 3",noOfHours:160,hourlyRate:25}
incomeStreams:=[]Income{project1,project2,project3}
calculateNetIncome(incomeStreams)
}
在上面的 main 函数中,我们创建了三个项目,有两个是 FixedBilling 类型,一个是 TimeAndMaterial 类型。接着我们创建了一个 Income 类型的切片,存放了这三个项目。由于这三个项目都实现了 Interface 接口,因此可以把这三个项目放入 Income 切片。最后我们将该切片作为参数,调用了 calculateNetIncome 函数,显示了项目不同的收益和收入来源。
以下完整的代码供你参考。
package main
import(
"fmt"
)
typeIncomeinterface{
calculate()int
source()string
}
typeFixedBillingstruct{
projectNamestring
biddedAmountint
}
typeTimeAndMaterialstruct{
projectNamestring
noOfHoursint
hourlyRateint
}
func(fb FixedBilling)calculate()int{
returnfb.biddedAmount
}
func(fb FixedBilling)source()string{
returnfb.projectName
}
func(tm TimeAndMaterial)calculate()int{
returntm.noOfHours*tm.hourlyRate
}
func(tm TimeAndMaterial)source()string{
returntm.projectName
}
funccalculateNetIncome(ic[]Income){
varnetincomeint=0
for_,income:=rangeic{
fmt.Printf("Income From %s = $%d\n",income.source(),income.calculate())
netincome+=income.calculate()
}
fmt.Printf("Net income of organisation = $%d",netincome)
}
funcmain(){
project1:=FixedBilling{projectName:"Project 1",biddedAmount:5000}
project2:=FixedBilling{projectName:"Project 2",biddedAmount:10000}
project3:=TimeAndMaterial{projectName:"Project 3",noOfHours:160,hourlyRate:25}
incomeStreams:=[]Income{project1,project2,project3}
calculateNetIncome(incomeStreams)
}
该程序会输出:
Income From Project 1 = $5000
Income From Project 2 = $10000
Income From Project 3 = $4000
Net income of organisation = $19000
新增收益流
假设前面的组织通过广告业务,建立了一个新的收益流(Income Stream)。我们可以看到添加它非常简单,并且计算总收益也很容易,我们无需对 calculateNetIncome 函数进行任何修改。这就是多态的好处。
我们首先定义 Advertisement 类型,并在 Advertisement 类型中定义 calculate() 和 source() 方法。
typeAdvertisementstruct{
adNamestring
CPCint
noOfClicksint
}
func(a Advertisement)calculate()int{
returna.CPC*a.noOfClicks
}
func(a Advertisement)source()string{
returna.adName
}
Advertisement 类型有三个字段,分别是 adName、CPC(每次点击成本)和 noOfClicks(点击次数)。广告的总收益等于 CPC 和 noOfClicks 的乘积。
现在我们稍微修改一下 main 函数,把新的收益流添加进来。
funcmain(){
project1:=FixedBilling{projectName:"Project 1",biddedAmount:5000}
project2:=FixedBilling{projectName:"Project 2",biddedAmount:10000}
project3:=TimeAndMaterial{projectName:"Project 3",noOfHours:160,hourlyRate:25}
bannerAd:=Advertisement{adName:"Banner Ad",CPC:2,noOfClicks:500}
popupAd:=Advertisement{adName:"Popup Ad",CPC:5,noOfClicks:750}
incomeStreams:=[]Income{project1,project2,project3,bannerAd,popupAd}
calculateNetIncome(incomeStreams)
}
我们创建了两个广告项目,即 bannerAd 和 popupAd。incomeStream 切片包含了这两个创建的广告项目。
package main
import(
"fmt"
)
typeIncomeinterface{
calculate()int
source()string
}
typeFixedBillingstruct{
projectNamestring
biddedAmountint
}
typeTimeAndMaterialstruct{
projectNamestring
noOfHoursint
hourlyRateint
}
typeAdvertisementstruct{
adNamestring
CPCint
noOfClicksint
}
func(fb FixedBilling)calculate()int{
returnfb.biddedAmount
}
func(fb FixedBilling)source()string{
returnfb.projectName
}
func(tm TimeAndMaterial)calculate()int{
returntm.noOfHours*tm.hourlyRate
}
func(tm TimeAndMaterial)source()string{
returntm.projectName
}
func(a Advertisement)calculate()int{
returna.CPC*a.noOfClicks
}
func(a Advertisement)source()string{
returna.adName
}
funccalculateNetIncome(ic[]Income){
varnetincomeint=0
for_,income:=rangeic{
fmt.Printf("Income From %s = $%d\n",income.source(),income.calculate())
netincome+=income.calculate()
}
fmt.Printf("Net income of organisation = $%d",netincome)
}
funcmain(){
project1:=FixedBilling{projectName:"Project 1",biddedAmount:5000}
project2:=FixedBilling{projectName:"Project 2",biddedAmount:10000}
project3:=TimeAndMaterial{projectName:"Project 3",noOfHours:160,hourlyRate:25}
bannerAd:=Advertisement{adName:"Banner Ad",CPC:2,noOfClicks:500}
popupAd:=Advertisement{adName:"Popup Ad",CPC:5,noOfClicks:750}
incomeStreams:=[]Income{project1,project2,project3,bannerAd,popupAd}
calculateNetIncome(incomeStreams)
}
上面程序会输出:
Income From Project 1 = $5000
Income From Project 2 = $10000
Income From Project 3 = $4000
Income From Banner Ad = $1000
Income From Popup Ad = $3750
Net income of organisation = $23750
你会发现,尽管我们新增了收益流,但却完全没有修改 calculateNetIncome 函数。这就是多态带来的好处。由于新的 Advertisement同样实现了 Income 接口,所以我们能够向 incomeStreams 切片添加 Advertisement。calculateNetIncome 无需修改,因为它能够调用 Advertisement 类型的 calculate() 和 source() 方法。
本教程到此结束。祝你愉快。
本文来自博客园,作者:易先讯,转载请注明原文链接:https://www.cnblogs.com/gongxianjin/p/17981810

浙公网安备 33010602011771号