go教程8

1.     合取代继承

       Go 不支持继承,但它支持组合(Composition)。组合一般定义为“合并在一起”。汽车就是一个关于组合的例子:一辆汽车由车轮、引擎和其他各种部件组合在一起。

通过嵌套结构体进行组合

在 Go 中,通过在结构体内嵌套结构体,可以实现组合。

组合的典型例子就是博客帖子。每一个博客的帖子都有标题、内容和作者信息。使用组合可以很好地表示它们。通过学习本教程后面的内容,我们会知道如何实现组合。

我们首先创建一个 author 结构体。

package main
 
 
  
    

)
 
 author 
 
  
    firstName 

    lastName  

    bio       

}
 
 
a author
 
 
 
  
    
 fmt
 a
firstName
 a
lastName

}

在上面的代码片段中,我们创建了一个 author 结构体,author 的字段有 firstnamelastname 和 bio。我们还添加了一个 fullName() 方法,其中 author 作为接收者类型,该方法返回了作者的全名。

下一步我们创建 post 结构体。

 post 
 
  
    title     

    content   

    author
}
 
 
p post
 
 
  
    fmt
 p
title

    fmt
 p
content

    fmt
 p
author

    fmt
 p
author
bio

}

post 结构体的字段有 title 和 content。它还有一个嵌套的匿名字段 author。该字段指定 author 组成了 post 结构体。现在 post 可以访问 author 结构体的所有字段和方法。我们同样给 post 结构体添加了 details() 方法,用于打印标题、内容和作者的全名与简介。

一旦结构体内嵌套了一个结构体字段,Go 可以使我们访问其嵌套的字段,好像这些字段属于外部结构体一样。所以上面第 11 行的 p.author.fullName() 可以替换为 p.fullName()。于是,details() 方法可以重写,如下所示:

 
p post
 
 
  
    fmt
 p
title

    fmt
 p
content

    fmt
 p

    fmt
 p
bio

}

现在,我们的 author 和 post 结构体都已准备就绪,我们来创建一个博客帖子来完成这个程序。

package main
 
 
  
    

)
 
 author 
 
  
    firstName 

    lastName  

    bio       

}
 
 
a author
 
 
 
  
    
 fmt
 a
firstName
 a
lastName

}
 
 post 
 
  
    title   

    content 

    author
}
 
 
p post
 
 
  
    fmt
 p
title

    fmt
 p
content

    fmt
 p

    fmt
 p
bio

}
 
 
 
  
    author1 
 author

        

        

        

    

    post1 
 post

        

        

        author1

    

    post1

}

playground 上运行

在上面程序中,main 函数在第 31 行新建了一个 author 结构体变量。而在第 36 行,我们通过嵌套 author1 来创建一个 post。该程序输出:

Title
  Inheritance 
 Go  
Content
  Go supports composition instead of inheritance  
Author
  Naveen Ramanathan  
Bio
  Golang Enthusiast

结构体切片的嵌套

我们可以进一步处理这个示例,使用博客帖子的切片来创建一个网站。

我们首先定义 website 结构体。请在上述代码里的 main 函数中,添加下面的代码,并运行它。

 website 
 
  
        
post
}
 
w website
 
 
  
    fmt

    
 
 v 
 
 w
posts 

        v

        fmt

    

}

在你添加上述代码后,当你运行程序时,编译器将会报错,如下所示:

main
go
 syntax error
 unexpected 
 expecting field name or embedded 

这项错误指出了嵌套的结构体切片 []post。错误的原因是结构体不能嵌套一个匿名切片。我们需要一个字段名。所以我们来修复这个错误,让编译器顺利通过。

 website 
 
  
        posts 
post
}

可以看到,我给帖子的切片 []post 添加了字段名 posts

现在我们来修改主函数,为我们的新网站创建一些帖子吧。

修改后的完整代码如下所示:

package main
 
 
  
    

)
 
 author 
 
  
    firstName 

    lastName  

    bio       

}
 
 
a author
 
 
 
  
    
 fmt
 a
firstName
 a
lastName

}
 
 post 
 
  
    title   

    content 

    author
}
 
 
p post
 
 
  
    fmt
 p
title

    fmt
 p
content

    fmt
 p

    fmt
 p
bio

}
 
 website 
 
  
 posts 
post
}
 
w website
 
 
  
    fmt

    
 
 v 
 
 w
posts 

        v

        fmt

    

}
 
 
 
  
    author1 
 author

        

        

        

    

    post1 
 post

        

        

        author1

    

    post2 
 post

        

        

        author1

    

    post3 
 post

        

        

        author1

    

    w 
 website

        posts
 
post
post1
 post2
 post3

    

    w

}

playground 中运行

在上面的主函数中,我们创建了一个作者 author1,以及三个帖子 post1post2 和 post3。我们最后通过嵌套三个帖子,在第 62 行创建了网站 w,并在下一行显示内容。

程序会输出:

Contents of Website
 
Title
  Inheritance 
 Go  
Content
  Go supports composition instead of inheritance  
Author
  Naveen Ramanathan  
Bio
  Golang Enthusiast
 
Title
  Struct instead of Classes 
 Go  
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

 Income 
 
  
    
 

    
 

}

上面定义了接口 Interface,它包含了两个方法:calculate() 计算并返回项目的收入,而 source() 返回项目名称。

下面我们定义一个表示 FixedBilling 项目的结构体类型。

 FixedBilling 
 
  
    projectName 

    biddedAmount 

}

项目 FixedBillin 有两个字段:projectName 表示项目名称,而 biddedAmount 表示组织向该项目投标的金额。

TimeAndMaterial 结构体用于表示项目 Time and Material。

 TimeAndMaterial 
 
  
    projectName 

    noOfHours  

    hourlyRate 

}

结构体 TimeAndMaterial 拥有三个字段名:projectNamenoOfHours 和 hourlyRate

下一步我们给这些结构体类型定义方法,计算并返回实际收入和项目名称。

 
fb FixedBilling
 
 
 
  
    
 fb
biddedAmount
}
 
 
fb FixedBilling
 
 
 
  
    
 fb
projectName
}
 
 
tm TimeAndMaterial
 
 
 
  
    
 tm
noOfHours 
 tm
hourlyRate
}
 
 
tm TimeAndMaterial
 
 
 
  
    
 tm
projectName
}

在项目 FixedBilling 里面,收入就是项目的投标金额。因此我们返回 FixedBilling 类型的 calculate() 方法。

而在项目 TimeAndMaterial 里面,收入等于 noOfHours 和 hourlyRate 的乘积,作为 TimeAndMaterial 类型的 calculate() 方法的返回值。

我们还通过 source() 方法返回了表示收入来源的项目名称。

由于 FixedBilling 和 TimeAndMaterial 两个结构体都定义了 Income 接口的两个方法:calculate() 和 source(),因此这两个结构体都实现了 Income 接口。

我们来声明一个 calculateNetIncome 函数,用来计算并打印总收入。

 
ic 
Income
 
  
    
 netincome 
 
 

    
 
 income 
 
 ic 

        fmt
 income
 income

        netincome 
 income

    

    fmt
 netincome

}

上面的函数接收一个 Income 接口类型的切片作为参数。该函数会遍历这个接口切片,并依个调用 calculate() 方法,计算出总收入。该函数同样也会通过调用 source() 显示收入来源。根据 Income 接口的具体类型,程序会调用不同的 calculate() 和 source() 方法。于是,我们在 calculateNetIncome 函数中就实现了多态。

如果在该组织以后增加了新的收入来源,calculateNetIncome 无需修改一行代码,就可以正确地计算总收入了。

最后就剩下这个程序的 main 函数了。

 
 
  
    project1 
 FixedBilling
projectName
 
 biddedAmount
 

    project2 
 FixedBilling
projectName
 
 biddedAmount
 

    project3 
 TimeAndMaterial
projectName
 
 noOfHours
 
 hourlyRate
 

    incomeStreams 
 
Income
project1
 project2
 project3

    
incomeStreams

}

在上面的 main 函数中,我们创建了三个项目,有两个是 FixedBilling 类型,一个是 TimeAndMaterial 类型。接着我们创建了一个 Income 类型的切片,存放了这三个项目。由于这三个项目都实现了 Interface 接口,因此可以把这三个项目放入 Income 切片。最后我们将该切片作为参数,调用了 calculateNetIncome 函数,显示了项目不同的收益和收入来源。

以下完整的代码供你参考。

package main
 
 
  
    

)
 
 Income 
 
  
    
 

    
 

}
 
 FixedBilling 
 
  
    projectName 

    biddedAmount 

}
 
 TimeAndMaterial 
 
  
    projectName 

    noOfHours  

    hourlyRate 

}
 
 
fb FixedBilling
 
 
 
  
    
 fb
biddedAmount
}
 
 
fb FixedBilling
 
 
 
  
    
 fb
projectName
}
 
 
tm TimeAndMaterial
 
 
 
  
    
 tm
noOfHours 
 tm
hourlyRate
}
 
 
tm TimeAndMaterial
 
 
 
  
    
 tm
projectName
}
 
 
ic 
Income
 
  
    
 netincome 
 
 

    
 
 income 
 
 ic 

        fmt
 income
 income

        netincome 
 income

    

    fmt
 netincome

}
 
 
 
  
    project1 
 FixedBilling
projectName
 
 biddedAmount
 

    project2 
 FixedBilling
projectName
 
 biddedAmount
 

    project3 
 TimeAndMaterial
projectName
 
 noOfHours
 
 hourlyRate
 

    incomeStreams 
 
Income
project1
 project2
 project3

    
incomeStreams

}

playground 上运行

该程序会输出:

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() 方法。

 Advertisement 
 
  
    adName     

    CPC        

    noOfClicks 

}
 
 
a Advertisement
 
 
 
  
    
 a
CPC 
 a
noOfClicks
}
 
 
a Advertisement
 
 
 
  
    
 a
adName
}

Advertisement 类型有三个字段,分别是 adNameCPC(每次点击成本)和 noOfClicks(点击次数)。广告的总收益等于 CPC 和 noOfClicks 的乘积。

现在我们稍微修改一下 main 函数,把新的收益流添加进来。

 
 
  
    project1 
 FixedBilling
projectName
 
 biddedAmount
 

    project2 
 FixedBilling
projectName
 
 biddedAmount
 

    project3 
 TimeAndMaterial
projectName
 
 noOfHours
 
 hourlyRate
 

    bannerAd 
 Advertisement
adName
 
 CPC
 
 noOfClicks
 

    popupAd 
 Advertisement
adName
 
 CPC
 
 noOfClicks
 

    incomeStreams 
 
Income
project1
 project2
 project3
 bannerAd
 popupAd

    
incomeStreams

}

我们创建了两个广告项目,即 bannerAd 和 popupAdincomeStream 切片包含了这两个创建的广告项目。

package main
 
 
  
    

)
 
 Income 
 
  
    
 

    
 

}
 
 FixedBilling 
 
  
    projectName  

    biddedAmount 

}
 
 TimeAndMaterial 
 
  
    projectName 

    noOfHours   

    hourlyRate  

}
 
 Advertisement 
 
  
    adName     

    CPC        

    noOfClicks 

}
 
 
fb FixedBilling
 
 
 
  
    
 fb
biddedAmount
}
 
 
fb FixedBilling
 
 
 
  
    
 fb
projectName
}
 
 
tm TimeAndMaterial
 
 
 
  
    
 tm
noOfHours 
 tm
hourlyRate
}
 
 
tm TimeAndMaterial
 
 
 
  
    
 tm
projectName
}
 
 
a Advertisement
 
 
 
  
    
 a
CPC 
 a
noOfClicks
}
 
 
a Advertisement
 
 
 
  
    
 a
adName
}
 
ic 
Income
 
  
    
 netincome 
 
 

    
 
 income 
 
 ic 

        fmt
 income
 income

        netincome 
 income

    

    fmt
 netincome

}
 
 
 
  
    project1 
 FixedBilling
projectName
 
 biddedAmount
 

    project2 
 FixedBilling
projectName
 
 biddedAmount
 

    project3 
 TimeAndMaterial
projectName
 
 noOfHours
 
 hourlyRate
 

    bannerAd 
 Advertisement
adName
 
 CPC
 
 noOfClicks
 

    popupAd 
 Advertisement
adName
 
 CPC
 
 noOfClicks
 

    incomeStreams 
 
Income
project1
 project2
 project3
 bannerAd
 popupAd

    
incomeStreams

}

playground 中运行

上面程序会输出:

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 切片添加 AdvertisementcalculateNetIncome 无需修改,因为它能够调用 Advertisement 类型的 calculate() 和 source() 方法。

本教程到此结束。祝你愉快。

posted @   易先讯  阅读(12)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· 写一个简单的SQL生成工具
点击右上角即可分享
微信分享提示