通过示例学习-Go-语言-2023-二十三-
通过示例学习 Go 语言 2023(二十三)
在 Go (Golang) 中将文件从一个位置移动到另一个位置或使用命令 mv
来源:
golangbyexample.com/move-file-from-one-location-to-another-golang/
os.Rename() 函数可用于在不同位置之间移动文件。它相当于 Linux 的命令 ‘mv’。以下是该函数的签名
func Rename(oldpath, newpath string) error
代码:
package main
import (
"log"
"os"
)
func main() {
//Create a file
file, err := os.Create("temp.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close()
//Change permission so that it can be moved
err = os.Chmod("temp.txt", 0777)
if err != nil {
log.Println(err)
}
newLocation := "~/Desktop/temp.txt"
err = os.Rename("temp.txt", newLocation)
if err != nil {
log.Fatal(err)
}
}
Go(Golang)中的多个常量声明
目录
-
概述
-
同时声明多个常量,具有不同的值和类型
-
同时声明多个常量,具有相同的值和类型
-
组合上述两个
-
单行多个声明
概述
以下是一些同时声明多个常量的方法。
同时声明多个常量,具有不同的值和类型
const (
a = "circle"
b = 1
c float = 4.65
)
声明可以是有类型的或无类型的。请参阅本文了解有类型常量和无类型常量之间的区别 – golangbyexample.com/typed-untyped-constant-golang/
-
a 是一个无类型声明。它将是string类型,值为“circle”。
-
b 也是一个未类型声明。它将是int类型,值为1。
-
c 是一个有类型的声明。它将是float64类型,值为 4.65。
同时声明多个常量,具有相同的值和类型
const (
a string = "circle"
b
)
当常量的类型和值未提供时,它将从之前的声明中获取其类型和值。
-
a 将是string类型,值为“circle”。
-
b 将是string类型,值为“circle”。
组合上述两个
const (
a string "circle"
b
c = 1
)
-
a 将是string类型,值为“circle”。
-
b 将是string类型,值为“circle”。
-
c 将是int类型,值为1。
单行多个声明
const a, b = 1, 2
const c, d int = 3, 4
声明再次可以是有类型的或无类型的。
-
a将是int类型,值为1。
-
b将是int类型,值为2。
-
c将是int类型,值为3。
-
d将是int类型,值为4。
Go(Golang)中的多个延迟函数
目录
概述
-
在特定函数中的多个延迟函数
-
在不同函数中的多个延迟函数
概述
多个延迟函数有两种情况。
-
在特定函数中的多个延迟函数
-
在不同函数中的多个延迟函数
在我们看到这两者的示例之前,让我们先看看延迟函数是如何工作的。当编译器在一个函数中遇到延迟语句时,它会将其推入一个列表中。这个列表内部实现了一个类似栈的数据结构。所有在同一个函数中遇到的延迟语句都将被推入这个列表。当外部函数返回时,栈中的所有函数(从上到下)将在调用函数的执行开始之前被执行。现在,调用函数中也会发生同样的事情。
现在让我们看一下这两者的示例。
在特定函数中的多个延迟函数
如果我们在某个特定函数中有多个延迟函数,那么所有的延迟函数将按照后进先出(last in first out)的顺序执行,这与我们上面提到的类似。
让我们看一下相关的程序。
package main
import "fmt"
func main() {
i := 0
i = 1
defer fmt.Println(i)
i = 2
defer fmt.Println(i)
i = 3
defer fmt.Println(i)
}
输出
3
2
1
在上面的程序中,我们有三个延迟(defer)函数,每个函数打印变量i的值。变量i在每个延迟函数之前递增。代码首先输出 3,这意味着第三个延迟函数是第一个执行的。然后输出 2,意味着第二个延迟函数在此之后执行,最后输出 1,意味着第一个延迟函数是最后执行的。这表明,当一个特定函数中有多个延迟函数时,它们遵循“后进先出”规则。因此程序输出:
3
2
1
在不同函数中的多个延迟函数
让我们理解当我们在不同的函数中有多个延迟(defer)函数时会发生什么……想象从主函数调用f1函数,再到f2函数的场景。
主->f1->f2
下面是f2返回后将发生的顺序。
-
如果存在,f2中的延迟函数将会被执行。控制将返回给调用者,即f1函数。
-
如果存在,f1中的延迟函数将会被执行。控制将返回给调用者,即主函数。请注意,如果中间有更多的函数,过程将以类似的方式继续向上执行栈。
-
当主函数返回时,主函数中的延迟函数(如果存在)将被执行。
让我们看一下相关的程序。
package main
import "fmt"
func main() {
defer fmt.Println("Defer in main")
fmt.Println("Stat main")
f1()
fmt.Println("Finish main")
}
func f1() {
defer fmt.Println("Defer in f1")
fmt.Println("Start f1")
f2()
fmt.Println("Finish f1")
}
func f2() {
defer fmt.Println("Defer in f2")
fmt.Println("Start f2")
fmt.Println("Finish f2")
}
输出
Stat main
Start f1
Start f2
Finish f2
Defer in f2
Finish f1
Defer in f1
Finish main
Defer in main
Go 语言中的变量和常量命名规范
来源:
golangbyexample.com/naming-conventions-variable-constant-go/
以下是 Go 语言中变量和常量的命名规范
-
变量或常量的名称只能以字母或下划线开头。
-
在其后可以跟任意数量的字母、数字或下划线
-
Go 语言区分大小写,因此大写字母和小写字母被视为不同。
-
变量或常量的名称不能是 Go 中的任何关键字
-
变量或常量名称的长度没有限制,但建议选择最佳长度的变量名称。
Go (Golang) 中的嵌套包
目录
-
概述
-
示例
概述
在 Go 中,可以创建嵌套包。嵌套包是指位于另一个包内部的那些包。让我们来看一个例子
示例
创建一个名为 learn 的目录。我们将在 learn 目录中创建一个导入路径为 “sample.com/learn” 的模块。
go mod init sample.com/learn
它将创建一个 go.mod 文件
go.mod
module sameple.com/learn
go 1.14
让我们创建以下文件和目录
-
learn/main.go
-
learn/math/math.go
-
learn/math/advanced/advanced.go
你可以看到 advanced 是 math 包内的嵌套包。
learn/math/math.go
package math
func Add(a, b int) int {
return a + b
}
func Subtract(a, b int) int {
return a - b
}
learn/math/advanced/advanced.go
package advanced
func Square(a int) int {
return a * a
}
learn/main.go
package main
import (
"fmt"
"sample.com/learn/math"
"sample.com/learn/math/advanced"
)
func main() {
fmt.Println(math.Add(2, 1))
fmt.Println(math.Subtract(2, 1))
fmt.Println(advanced.Square(2))
}
让我们运行这个程序
learn $ go install
learn $ learn
3
1
4
关于上述程序的注意事项
-
我们在 main.go 中使用完整的合格路径导入了 advanced 包,即 import “sample.com/learn/math/advanced”
-
Square 函数是通过 advanced 包来调用的,即 advanced.Square(2)
Go (Golang) 中的嵌套结构体
一个结构体可以嵌套另一个结构体。让我们来看一个嵌套结构体的例子。在下面的employee结构体中嵌套了address结构体。
package main
import "fmt"
type employee struct {
name string
age int
salary int
address address
}
type address struct {
city string
country string
}
func main() {
address := address{city: "London", country: "UK"}
emp := employee{name: "Sam", age: 31, salary: 2000, address: address}
fmt.Printf("City: %s\n", emp.address.city)
fmt.Printf("Country: %s\n", emp.address.country)
}
输出
City: London
Country: UK
注意如何访问嵌套结构体字段。
emp.address.city
emp.address.country
net/http 包获取 Go (Golang) 中的查询参数
来源:
golangbyexample.com/net-http-package-get-query-params-golang/
注意: 如果你对学习 Golang 感兴趣,我们有一个全面的 Golang 教程系列。请务必查看一下 – Golang 综合教程系列。现在让我们来看当前的教程。
在 HTTP 请求的上下文中,通常需要获取作为请求一部分发送的查询参数。一个特定的查询参数键值可以有一个或多个值。
- 在下面的示例中,filters 有一个值,即 [“color”]。
http://localhost:8080/products?filters=color
- 在下面的示例中,filters 有多个值,即 [“color”, “price”, “brand”]。注意如何定义多个值。
http://localhost:8080/products?filters=red&filters=color&filters=price&filters=brand
让我们探索获取这些查询参数的两种方法。
1.使用 reqeust.URL.Query()
查询参数位于 URL 本身。我们通过 r.URL.Query() 获取查询参数,该函数返回 Values,它是 map[string][]string。存在两种情况:
1.1 当特定键在查询参数中包含多个值时。例如,请参见下面的请求
http://localhost:8080/products?filters=red&filters=color&filters=price&filters=brand
代码:
package main
import (
"fmt"
"net/http"
"strings"
)
func main() {
getProductsHandler := http.HandlerFunc(getProducts)
http.Handle("/products", getProductsHandler)
http.ListenAndServe(":8080", nil)
}
func getProducts(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query()
filters, present := query["filters"] //filters=["color", "price", "brand"]
if !present || len(filters) == 0 {
fmt.Println("filters not present")
}
w.WriteHeader(200)
w.Write([]byte(strings.Join(filters, ",")))
}
输出:
color,price,brand
1.2 当特定键在查询参数中包含单个值时。例如,请参见下面的请求
http://localhost:8080/products?filters=color
当我们知道特定键在查询参数中只有一个值时,可以使用 r.URL.Query().Get(keyName)。Get 函数将获取与该键关联的第一个值。如果你想获取所有值,则必须直接访问映射,如我们在上面的程序中所做的。在下面的程序中,我们在 r.URL.Query() 上使用 Get(),并且它返回一个单一值。
package main
import (
"net/http"
)
func main() {
getProductsHandler := http.HandlerFunc(getProducts)
http.Handle("/products", getProductsHandler)
http.ListenAndServe(":8080", nil)
}
func getProducts(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query()
filters := query.Get("filters") //filters="color"
w.WriteHeader(200)
w.Write([]byte(filters))
}
输出:
color
2.使用 request.Form
查询参数位于 URL 本身。我们通过 r.URL.Query() 获取查询参数,该函数返回 values,它是 map[string][]string。存在两种情况:
2.1 当特定键在查询参数中包含多个值时。例如,请参见下面的请求
http://localhost:8080/products?filters=red&filters=color&filters=price&filters=brand
代码:
package main
import (
"fmt"
"net/http"
"strings"
)
func main() {
getProductsHandler := http.HandlerFunc(getProducts)
http.Handle("/products", getProductsHandler)
http.ListenAndServe(":8080", nil)
}
func getProducts(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
filters, present := r.Form["filters"] //filters=["color", "price", "brand"]
if !present || len(filters) == 0 {
fmt.Println("filters not present")
}
w.WriteHeader(200)
w.Write([]byte(strings.Join(filters, ",")))
}
输出:
color,price,brand
需要注意的一个重要点:
- 在尝试通过 r.Form 获取查询参数值时,要小心在 POST 和 PUT 请求中,主体参数会优先于 URL 查询字符串值。即,如果有一个键 X 同时出现在表单主体(=“a”)和查询参数(=“b”)中。那么调用 r.Form[“X”] 时,它将返回 [“a”] 而不是 [“b”]。
2.2 当特定键在查询参数中包含单个值时。例如,请参见下面的请求
http://localhost:8080/products?filters=color
当我们知道特定键在查询参数中只有一个值时,可以使用 r.FormValue(keyName)。FormValue 函数将获取与该键关联的第一个值。如果你想获取所有值,则必须直接访问请求的表单映射,正如我们在上面的程序中所做的。在下面的程序中,我们使用 FormValue() 函数,并且它返回一个单一值。
package main
import (
"net/http"
)
func main() {
getProductsHandler := http.HandlerFunc(getProducts)
http.Handle("/products", getProductsHandler)
http.ListenAndServe(":8080", nil)
}
func getProducts(w http.ResponseWriter, r *http.Request) {
filters := r.FormValue("filters") //filters=["color"]
w.WriteHeader(200)
w.Write([]byte(filters))
}
输出:
color
需要注意的一个重要点:
- 在通过
r.FormValue()
获取查询参数值时,要注意在 POST 和 PUT 请求的情况下,主体参数会优先于 URL 查询字符串中的值,也就是说,如果有一个键 X 同时出现在表单主体(=“a”)和查询参数(=“b”)中。那么在调用r.Form[“X”]
时,它将返回 [“a”] 而不是 [“b”]。
GoLang 中使用深度监控的 New Relic 示例
来源:
golangbyexample.com/go-new-relic-example-in-golang-with-deep-instrumentation/
在 GoLang 中集成 newrelic 很简单。我们只需要其中一个中间件来使用 newrelic.Application。例如,在 GO 的 GIN 框架中,我们可以这样做。
nrConfig := newrelic.NewConfig("test", "somekey")
nrapp, err = newrelic.NewApplication(nrConfig)
r := gin.Default()
r.Use(nrgin.Middleware(nrapp))
通过这个更改,你将能够在 New Relic 中看到你的应用程序像这样。
但你将无法在“Transaction”中看到 API 的拆解,因为 Go 是一种编译语言。因此,与 JAVA 不同,要查看 API 的细分,你必须在 golang 中进行显式的深度监控。以下是如何在 Go 的 GIN web 框架中使用 newrelic 进行深度监控的简单示例。
main.go
package main
import (
"context"
"net/http"
"time"
"github.com/gin-gonic/gin"
nr "github.com/newrelic/go-agent"
"github.com/newrelic/go-agent/_integrations/nrgin/v1"
)
type key int
const (
keyNrID key = iota
)
var (
nrapp newrelic.Application
)
func main() {
initNewRelic()
r := gin.Default()
r.Use(nrgin.Middleware(nrapp))
r.Use(setNewRelicInContext())
setUpRoutes(r)
// Listen and Server in 0.0.0.0:8080
s := &http.Server{
Addr: ":8080",
Handler: r,
}
s.ListenAndServe()
}
//populateNewRelicInContext get the request context populated
func setNewRelicInContext() gin.HandlerFunc {
return func(c *gin.Context) {
//Setup context
ctx := c.Request.Context()
//Set newrelic context
var txn nr.Transaction
//newRelicTransaction is the key populated by nrgin Middleware
value, exists := c.Get("newRelicTransaction")
if exists {
if v, ok := value.(nr.Transaction); ok {
txn = v
}
ctx = context.WithValue(ctx, keyNrID, txn)
}
c.Request = c.Request.WithContext(ctx)
c.Next()
}
}
func initNewRelic() {
var err error
nrConfig := newrelic.NewConfig("test", "somekey")
nrapp, err = newrelic.NewApplication(nrConfig)
if err != nil {
panic("Failed to setup NewRelic: " + err.Error())
}
}
routes.go
package main
import (
"context"
"fmt"
"net/http"
"time"
"github.com/gin-gonic/gin"
nr "github.com/newrelic/go-agent"
)
//setUpRoutes set all the routes
func setUpRoutes(r *gin.Engine) {
r.GET("/app/status", getStatus)
}
func getStatus(c *gin.Context) {
ctx := c.Request.Context()
err := callGoogle(ctx)
if err != nil {
c.Writer.WriteHeader(400)
return
}
doSomeThing(ctx)
c.Writer.WriteHeader(200)
}
func callGoogle(ctx context.Context) error {
if t := ctx.Value(keyNrID); t != nil {
txn := t.(nr.Transaction)
defer nr.StartSegment(txn, "callGoogle").End()
}
resp, err := http.Get("http://google.com/")
if err != nil {
return fmt.Errorf("Some error occuerd %s", err.Error())
}
defer resp.Body.Close()
return nil
}
func doSomeThing(ctx context.Context) {
if t := ctx.Value(keyNrID); t != nil {
txn := t.(nr.Transaction)
defer nr.StartSegment(txn, "doSomeThing").End()
}
time.Sleep(time.Millisecond * 100)
}
这就是在 NewRelic 中如何显示拆解的方式。可以看到它显示了在“callGoogle”和“doSomeThing”函数中花费的平均时间。
* context* go* golang* intrumentation* newrelic
Go(Golang)中的非重叠区间程序
目录
-
概述
-
程序
概述
给定一个区间数组,其中 intervals[i] = [starti, endi]。我们需要找出最少需要移除的区间数量,以使区间数组中的区间不重叠。
让我们通过一个例子来理解。
Input: intervals = [[2,3],[3,4],[4,5],[2,4]]
Output: 1
Explanation: [2,4] can be removed and the rest of the intervals are non-overlapping.
思路是先按区间开始时间排序,然后计算重叠的区间。
程序
这里是相应的程序。
package main
import (
"fmt"
"sort"
)
func eraseOverlapIntervals(intervals [][]int) int {
lenIntervals := len(intervals)
sort.Slice(intervals, func(i, j int) bool {
return intervals[i][0] < intervals[j][0]
})
prevIntervalEnd := intervals[0][1]
minIntervals := 0
for i := 1; i < lenIntervals; i++ {
currentIntervalStart := intervals[i][0]
currentIntervalEnd := intervals[i][1]
if currentIntervalStart < prevIntervalEnd {
minIntervals++
if prevIntervalEnd >= currentIntervalEnd {
prevIntervalEnd = currentIntervalEnd
}
} else {
prevIntervalEnd = currentIntervalEnd
}
}
return minIntervals
}
func main() {
output := eraseOverlapIntervals([][]int{{2, 3}, {3, 4}, {4, 5}, {2, 4}})
fmt.Println(output)
}
输出
6
13
注意: 查看我们的 Golang 高级教程。该系列的教程内容详尽,我们尝试覆盖所有概念并提供示例。此教程适合希望获得 Golang 专业知识和扎实理解的人——Golang 高级教程
如果你有兴趣了解所有设计模式如何在 Golang 中实现。如果是的话,那么这篇文章适合你——Golang 中的所有设计模式
非结构体自定义类型在 Go(Golang)中实现接口
来源:
golangbyexample.com/non-struct-type-implementing-interface-go/
目录
-
概述
-
代码
概述
任何非结构体自定义类型实现接口也是完全可以的。让我们来看一个例子。
假设我们有一个接口animal如下
type animal interface {
breathe()
walk()
}
代码
package main
import "fmt"
type animal interface {
breathe()
walk()
}
type cat string
func (c cat) breathe() {
fmt.Println("Cat breathes")
}
func (c cat) walk() {
fmt.Println("Cat walk")
}
func main() {
var a animal
a = cat("smokey")
a.breathe()
a.walk()
}
输出
Cat breathes
Cat walk
上述程序说明了任何自定义类型也可以实现接口的概念。cat是字符串类型,它实现了breathe和walk方法,因此将cat类型的实例赋值给animal类型的变量是正确的。
Go 中的空对象设计模式(Golang)。
注意:想了解其他所有设计模式如何在 GO 中实现,请查看这个完整参考 – Go 中的所有设计模式
目录
** 简介:
- 完整工作代码:
简介:
空对象设计模式是一种行为设计模式。当客户端代码依赖于某些可能为 null 的依赖项时,它非常有用。使用此设计模式可以防止客户端在这些依赖项的结果上进行 null 检查。值得注意的是,客户端的行为对于这些 null 依赖项也是可以接受的。
空对象设计模式的主要组件是:
-
实体 – 它是定义子结构体必须实现的原始操作的接口。
-
ConcreteEntity – 它实现了实体接口。
-
NullEntity – 它表示 null 对象。它也实现了实体接口,但具有 null 属性。
-
客户端 – 客户端获取实体接口的实现并使用它。它并不关心实现是 ConcreteEntity 还是 NullEntity。它将两者视为相同。
让我们考虑一个例子。假设我们有一个学院,里面有多个部门,每个部门有一些教授。
department 由一个接口表示。
type department interface {
getNumberOfProfessors() int
getName() string
}
其中college表示为
type college struct {
departments []department
}
现在假设有一个机构想要计算特定学院特定部门的教授总数。我们将使用空对象设计模式处理这种情况,如果一个部门在学院中不存在,学院将返回一个 nullDepartment 对象(见nullDepartment.go)。
注意 agency.go 中的代码。
-
agency.go 根本不关心特定部门是否存在于学院中。如果该部门在学院中不存在,college 会返回一个 null 部门对象。
-
它将 nullDepartment 和真实部门视为相同,因此避免了 null 检查。它在两个对象上调用getNumberOfProfessors()。
以上是我们通过使用 null 对象设计模式在这种情况下获得的两个优点。请参见下面的代码。
agency.go
package main
import "fmt"
func main() {
college1 := createCollege1()
college2 := createCollege2()
totalProfessors := 0
departmentArray := []string{"computerscience", "mechanical", "civil", "electronics"}
for _, deparmentName := range departmentArray {
d := college1.getDepartment(deparmentName)
totalProfessors += d.getNumberOfProfessors()
}
fmt.Printf("Total number of professors in college1 is %d\n", totalProfessors)
//Reset the professor count
totalProfessors := 0
for _, deparmentName := range departmentArray {
d := college2.getDepartment(deparmentName)
totalProfessors += d.getNumberOfProfessors()
}
fmt.Printf("Total number of professors in college2 is %d\n", totalProfessors)
}
func createCollege1() *college {
college := &college{}
college.addDepartment("computerscience", 4)
college.addDepartment("mechanical", 5)
return college
}
func createCollege2() *college {
college := &college{}
college.addDepartment("computerscience", 2)
return college
}
college.go – 表示学院。
package main
type college struct {
departments []department
}
func (c *college) addDepartment(departmentName string, numOfProfessors int) {
if departmentName == "computerscience" {
computerScienceDepartment := &computerscience{numberOfProfessors: numOfProfessors}
c.departments = append(c.departments, computerScienceDepartment)
}
if departmentName == "mechanical" {
mechanicalDepartment := &mechanical{numberOfProfessors: numOfProfessors}
c.departments = append(c.departments, mechanicalDepartment)
}
return
}
func (c *college) getDepartment(departmentName string) department {
for _, department := range c.departments {
if department.getName() == departmentName {
return department
}
}
//Return a null department if the department doesn't exits
return &nullDepartment{}
}
department.go – 它表示部门接口。
package main
type department interface {
getNumberOfProfessors() int
getName() string
}
computerscience.go – 部门接口的具体实现。
package main
type computerscience struct {
numberOfProfessors int
}
func (c *computerscience) getNumberOfProfessors() int {
return c.numberOfProfessors
}
func (c *computerscience) getName() string {
return "computerscience"
}
mechanical.go – 部门接口的具体实现。
package main
type mechanical struct {
numberOfProfessors int
}
func (c *mechanical) getNumberOfProfessors() int {
return c.numberOfProfessors
}
func (c *mechanical) getName() string {
return "mechanical"
}
nullDepartment.go – 部门接口的 null 对象实现。
package main
type nullDepartment struct {
numberOfProfessors int
}
func (c *nullDepartment) getNumberOfProfessors() int {
return 0
}
func (c *nullDepartment) getName() string {
return "nullDepartment"
}
输出:
Total number of professors in college1 is 9
Total number of professors in college2 is 2
完整工作代码:
package main
import "fmt"
type college struct {
departments []department
}
func (c *college) addDepartment(departmentName string, numOfProfessors int) {
if departmentName == "computerscience" {
computerScienceDepartment := &computerscience{numberOfProfessors: numOfProfessors}
c.departments = append(c.departments, computerScienceDepartment)
}
if departmentName == "mechanical" {
mechanicalDepartment := &mechanical{numberOfProfessors: numOfProfessors}
c.departments = append(c.departments, mechanicalDepartment)
}
return
}
func (c *college) getDepartment(departmentName string) department {
for _, department := range c.departments {
if department.getName() == departmentName {
return department
}
}
//Return a null department if the department doesn't exits
return &nullDepartment{}
}
type department interface {
getNumberOfProfessors() int
getName() string
}
type computerscience struct {
numberOfProfessors int
}
func (c *computerscience) getNumberOfProfessors() int {
return c.numberOfProfessors
}
func (c *computerscience) getName() string {
return "computerscience"
}
type mechanical struct {
numberOfProfessors int
}
func (c *mechanical) getNumberOfProfessors() int {
return c.numberOfProfessors
}
func (c *mechanical) getName() string {
return "mechanical"
}
type nullDepartment struct {
numberOfProfessors int
}
func (c *nullDepartment) getNumberOfProfessors() int {
return 0
}
func (c *nullDepartment) getName() string {
return "nullDepartment"
}
func main() {
college1 := createCollege1()
college2 := createCollege2()
totalProfessors := 0
departmentArray := []string{"computerscience", "mechanical", "civil", "electronics"}
for _, deparmentName := range departmentArray {
d := college1.getDepartment(deparmentName)
totalProfessors += d.getNumberOfProfessors()
}
fmt.Printf("Total number of professors in college1 is %d\n", totalProfessors)
totalProfessors = 0
for _, deparmentName := range departmentArray {
d := college2.getDepartment(deparmentName)
totalProfessors += d.getNumberOfProfessors()
}
fmt.Printf("Total number of professors in college2 is %d\n", totalProfessors)
}
func createCollege1() *college {
college := &college{}
college.addDepartment("computerscience", 4)
college.addDepartment("mechanical", 5)
return college
}
func createCollege2() *college {
college := &college{}
college.addDepartment("computerscience", 2)
return college
}
输出:
Total number of professors in college1 is 9
Total number of professors in college2 is 2
Go 语言中的字符数或字符串长度
在 Go 语言中,字符串是一系列字节。字符串字面量实际上表示的是 UTF-8 字节序列。在 UTF-8 中,ASCII 字符对应于前 128 个 Unicode 字符,各占用一个字节。其他字符则占用 1 到 4 个字节。因此,使用 Go 内置的len函数无法获得字符串的确切长度。如果字符串只包含 ASCII 字符,可能会正常工作。但如果字符串包含非 ASCII 字符,则会给出正确的输出。
例如,见下面的程序及其输出。下面字符串中的£是一个非 ASCII 字符。
package main
import "fmt"
func main() {
sample := "ab£c"
for i := 0; i < 4; i++ {
fmt.Printf("%c\n", sample[i])
}
fmt.Printf("Length is %d\n", len(sample))
}
输出:
a
b
Â
£
Length is 5
正如你可能注意到的,它打印出不同于预期的字符,并且长度也是 5 而不是 4。为什么会这样呢?请记住,我们说字符串本质上是字节的切片。让我们使用以下方式打印这个字节切片
sample := "ab£c"
fmt.Println([]byte(sample))
输出将是
[97 98 194 163 99]
这是每个字符与其字节序列的映射。如你所见,a、b、c各占 1 个字节,但£占用两个字节。这就是字符串长度为 5 而不是 4 的原因。
a | 97 |
---|---|
b | 98 |
£ | 194, 163 |
c | 99 |
有两种方法可以获取字符串的正确长度
-
使用 rune 数据类型。rune数据类型表示一个 Unicode 点。一旦字符串转换为rune数组,就可以获得字符串的正确长度。
-
使用 range 操作符遍历字符串并计算长度。range 操作符遍历字符串中的 UTF-8 字符。
以下代码说明了上述两点。
package main
import "fmt"
func main() {
sample := "ab£c"
//Using rune
s := []rune(sample)
fmt.Println(len(s))
//Using range
len := 0
for range sample {
len++
}
fmt.Println(len)
}
输出:
4
4
Go (Golang) 中的数字/整数/浮点常量。
目录。
概述。
-
示例。
-
类型化整数常量。
-
未类型命名的整数常量。
-
未类型未命名的整数常量。
-
-
数字表达式
概述。
Go 中的数字常量进一步分为三种类型。
-
整数。
-
浮点数。
-
复数。
为了更好地理解 Go 中的数字常量,理解类型化和未类型常量非常重要。请参考这篇文章 – golangbyexample.com/typed-untyped-constant-golang
。
一旦你阅读完本文,你会理解常量可以以三种方式声明。
-
类型化常量。
-
未类型未命名常量。
-
未类型命名常量。
数字的情况也是一样。
未类型的整数常量(命名和未命名)可以赋值给 int 类型、float 类型和 complex。这是因为一个 int 值可以是 int 或 float 或 complex。例如,整数值 123 可以是。
-
一个值为 123 的 int。
-
一个值为 123.0 的 float。
-
一个虚部为 0 的 complex。
根据类似的逻辑,未类型的 float 常量可以赋值给所有 floats 和 complex 类型,但不能赋值给 integer,因为例如浮点数 5.3 不能是整数。
根据类似的逻辑,未类型的 complex 常量可以赋值给 complex 类型,但不能赋值给 integer 和 float,因为例如浮点数 5i+3 不能是 integer 或 float。
让我们看看一个程序来理解它。
示例。
请参见下面的程序说明上述观点。在程序中我们有例子。
-
类型化整数常量。
-
未类型未命名的整数常量。
-
未类型命名的整数常量。
package main
import "fmt"
func main() {
//Typed int constant
const aa int = 123
var uu = aa
fmt.Println("Typed named integer constant")
fmt.Printf("uu: Type: %T Value: %v\n\n", uu, uu)
//Below line will raise a compilation error
//var v int32 = aa
//Untyped named int constant
const bb = 123
var ww = bb
var xx int32 = bb
var yy float64 = bb
var zz complex128 = bb
fmt.Println("Untyped named integer constant")
fmt.Printf("ww: Type: %T Value: %v\n", ww, ww)
fmt.Printf("xx: Type: %T Value: %v\n", xx, xx)
fmt.Printf("yy: Type: %T Value: %v\n", yy, yy)
fmt.Printf("zz: Type: %T Value: %v\n\n", zz, zz)
//Untyped unnamed int constant
var ll = 123
var mm int32 = 123
var nn float64 = 123
var oo complex128 = 123
fmt.Println("Untyped unnamed integer constant")
fmt.Printf("ll: Type: %T Value: %v\n", ll, ll)
fmt.Printf("mm: Type: %T Value: %v\n", mm, mm)
fmt.Printf("nn: Type: %T Value: %v\n", nn, nn)
fmt.Printf("oo: Type: %T Value: %v\n", oo, oo)
}
输出。
Typed named integer constant
uu: Type: int Value: 123
Untyped named integer constant
ww: Type: int Value: 123
xx: Type: int32 Value: 123
yy: Type: float64 Value: 123
zz: Type: complex128 Value: (123+0i)
Untyped unnamed integer constant
ll: Type: int Value: 123
mm: Type: int32 Value: 123
nn: Type: float64 Value: 123
oo: Type: complex128 Value: (123+0i)
现在上面的程序展示了一个例子。
-
类型化整数常量。
-
未类型未命名的整数常量。
-
未类型命名的整数常量。
让我们了解每种方式及其行为。
类型化整数常量。
它定义如下。
const aa int = 123
类型化整数常量可以赋值给使用 var 关键字创建的变量,如下所示。
var uu = aa
当赋值给另一个 int 类型时会引发编译错误。因此下面的代码会引发编译错误,因为 aa 变量已经是 int 类型。
var v int32 = aa
未类型命名的整数常量。
它定义如下。
const bb = 123
未类型命名的整数常量可以赋值给任何 int 类型、任何 float 类型和任何 complex 数字类型,以及使用 var 关键字创建的任何变量。因此下面的代码是可行的。
var ww = bb
var xx int32 = bb
var yy float64 = bb
var zz complex128 = bb
未类型未命名的整数常量。
它如下所示。
123
无类型命名整数常量可以赋值给任何int类型、任何float类型以及任何complex数字类型,也可以赋值给任何使用var关键字创建的变量。因此,下面的代码是可行的。
var ww = 123
var xx int32 = 123
var yy float64 = 123
var zz complex128 = 123
数值表达式
由于常量数值的无类型特性,不同的数值常量类型可以混合使用以形成一个表达式。
package main
import "fmt"
func main() {
var p = 5.2 / 3
fmt.Printf("p: Type: %T Value: %v\n", p, p)
}
输出:
p: Type: float64 Value: 1.7333333333333334
Go 中的对象池设计模式
注意:如果想了解如何在 GO 中实现所有其他设计模式,请查看这个完整参考 – Go 中的所有设计模式
目录
** 介绍:
-
何时使用:
-
示例:
-
完整工作代码:
介绍:
对象池设计模式是一种创建型设计模式,其中一个对象池在之前被初始化和创建,并保持在池中。根据需要,客户端可以从池中请求一个对象,使用它,然后将其返回到池中。池中的对象从未被销毁。
何时使用:
- 当创建类的对象成本高,而在特定时间所需的此类对象数量不多时。
-让我们以数据库连接为例。每个连接对象的创建成本很高,因为涉及网络调用,而且在某个时刻不需要超过一定数量的连接。对象池设计模式非常适合这种情况。
- 当池对象是不可变对象时。
-再以数据库连接为例。数据库连接是一个不可变对象。几乎没有其属性需要改变。
- 出于性能原因。它会显著提高应用程序的性能,因为池已经创建好了。
示例:
iPoolObject.go
package main
type iPoolObject interface {
getID() string //This is any id which can be used to compare two different pool objects
}
pool.go
package main
import (
"fmt"
"sync"
)
type pool struct {
idle []iPoolObject
active []iPoolObject
capacity int
mulock *sync.Mutex
}
//InitPool Initialize the pool
func initPool(poolObjects []iPoolObject) (*pool, error) {
if len(poolObjects) == 0 {
return nil, fmt.Errorf("Cannot craete a pool of 0 length")
}
active := make([]iPoolObject, 0)
pool := &pool{
idle: poolObjects,
active: active,
capacity: len(poolObjects),
mulock: new(sync.Mutex),
}
return pool, nil
}
func (p *pool) loan() (iPoolObject, error) {
p.mulock.Lock()
defer p.mulock.Unlock()
if len(p.idle) == 0 {
return nil, fmt.Errorf("No pool object free. Please request after sometime")
}
obj := p.idle[0]
p.idle = p.idle[1:]
p.active = append(p.active, obj)
fmt.Printf("Loan Pool Object with ID: %s\n", obj.getID())
return obj, nil
}
func (p *pool) receive(target iPoolObject) error {
p.mulock.Lock()
defer p.mulock.Unlock()
err := p.remove(target)
if err != nil {
return err
}
p.idle = append(p.idle, target)
fmt.Printf("Return Pool Object with ID: %s\n", target.getID())
return nil
}
func (p *pool) remove(target iPoolObject) error {
currentActiveLength := len(p.active)
for i, obj := range p.active {
if obj.getID() == target.getID() {
p.active[currentActiveLength-1], p.active[i] = p.active[i], p.active[currentActiveLength-1]
p.active = p.active[:currentActiveLength-1]
return nil
}
}
return fmt.Errorf("Targe pool object doesn't belong to the pool")
}
connection.go
package main
type connection struct {
id string
}
func (c *connection) getID() string {
return c.id
}
main.go
package main
import (
"log"
"strconv"
)
func main() {
connections := make([]iPoolObject, 0)
for i := 0; i < 3; i++ {
c := &connection{id: strconv.Itoa(i)}
connections = append(connections, c)
}
pool, err := initPool(connections)
if err != nil {
log.Fatalf("Init Pool Error: %s", err)
}
conn1, err := pool.loan()
if err != nil {
log.Fatalf("Pool Loan Error: %s", err)
}
conn2, err := pool.loan()
if err != nil {
log.Fatalf("Pool Loan Error: %s", err)
}
pool.receive(conn1)
pool.receive(conn2)
}
输出:
Loan Pool Object with ID: 0
Loan Pool Object with ID: 1
Return Pool Object with ID: 0
Return Pool Object with ID: 1
完整工作代码:
package main
import (
"fmt"
"log"
"strconv"
"sync"
)
type iPoolObject interface {
getID() string //This is any id which can be used to compare two different pool objects
}
type pool struct {
idle []iPoolObject
active []iPoolObject
capacity int
mulock *sync.Mutex
}
//InitPool Initialize the pool
func initPool(poolObjects []iPoolObject) (*pool, error) {
if len(poolObjects) == 0 {
return nil, fmt.Errorf("Cannot craete a pool of 0 length")
}
active := make([]iPoolObject, 0)
pool := &pool{
idle: poolObjects,
active: active,
capacity: len(poolObjects),
mulock: new(sync.Mutex),
}
return pool, nil
}
func (p *pool) loan() (iPoolObject, error) {
p.mulock.Lock()
defer p.mulock.Unlock()
if len(p.idle) == 0 {
return nil, fmt.Errorf("No pool object free. Please request after sometime")
}
obj := p.idle[0]
p.idle = p.idle[1:]
p.active = append(p.active, obj)
fmt.Printf("Loan Pool Object with ID: %s\n", obj.getID())
return obj, nil
}
func (p *pool) receive(target iPoolObject) error {
p.mulock.Lock()
defer p.mulock.Unlock()
err := p.remove(target)
if err != nil {
return err
}
p.idle = append(p.idle, target)
fmt.Printf("Return Pool Object with ID: %s\n", target.getID())
return nil
}
func (p *pool) remove(target iPoolObject) error {
currentActiveLength := len(p.active)
for i, obj := range p.active {
if obj.getID() == target.getID() {
p.active[currentActiveLength-1], p.active[i] = p.active[i], p.active[currentActiveLength-1]
p.active = p.active[:currentActiveLength-1]
return nil
}
}
return fmt.Errorf("Targe pool object doesn't belong to the pool")
}
type connection struct {
id string
}
func (c *connection) getID() string {
return c.id
}
func main() {
connections := make([]iPoolObject, 0)
for i := 0; i < 3; i++ {
c := &connection{id: strconv.Itoa(i)}
connections = append(connections, c)
}
pool, err := initPool(connections)
if err != nil {
log.Fatalf("Init Pool Error: %s", err)
}
conn1, err := pool.loan()
if err != nil {
log.Fatalf("Pool Loan Error: %s", err)
}
conn2, err := pool.loan()
if err != nil {
log.Fatalf("Pool Loan Error: %s", err)
}
pool.receive(conn1)
pool.receive(conn2)
}
输出:
Loan Pool Object with ID: 0
Loan Pool Object with ID: 1
Return Pool Object with ID: 0
Return Pool Object with ID: 1
```*
<!--yml
类别:未分类
日期:2024-10-13 06:03:59
-->
# Go 中的观察者设计模式
> 来源:[`golangbyexample.com/observer-design-pattern-golang/`](https://golangbyexample.com/observer-design-pattern-golang/)
注意:有兴趣了解其他所有设计模式如何在 GO 中实现。请参见此完整参考 – [Go 中的所有设计模式](https://golangbyexample.com/all-design-patterns-golang/)
目录
** 介绍:
+ UML 图:
+ 映射:
+ 实用示例:
+ 完整工作代码:
# **介绍:**
观察者设计模式是一种行为设计模式。该模式允许一个实例**(称为主题)**将事件发布给多个实例**(称为观察者)。** 这些**观察者**订阅**主题**,因此在**主题**发生任何变化时会收到事件通知。
让我们举个例子。在电子商务网站上,许多商品会缺货。可能会有客户对某个缺货的特定商品感兴趣。对此问题有三种解决方案
1. 客户以某种频率检查项目的可用性。
1. 电子商务向客户推送所有新到的、现货商品。
1. 客户仅订阅他感兴趣的特定项目,并在该项目可用时收到通知。同时,多个客户可以订阅同一产品
选项 3 是最可行的,这正是观察者模式的核心。观察者模式的主要组件有:
1. **主题** – 它是当任何变化发生时发布事件的实例。
1. **观察者** – 它订阅主题并通过事件收到通知。
通常,**主题**和**观察者**实现为接口。两者的具体实现被使用
# **UML 图:**

# **映射:**
下表表示 UML 图中的角色与下面**“实用示例”**中的实际实现角色之间的映射
| 主题 | subject.go |
| --- | --- |
| 具体主题 | item.go |
| 观察者 | observer.go |
| 具体观察者 1 | customer.go |
| 客户端 | main.go |
# **实用示例:**
**subject.go**
```go
package main
type subject interface {
register(Observer observer)
deregister(Observer observer)
notifyAll()
}
item.go
package main
import "fmt"
type item struct {
observerList []observer
name string
inStock bool
}
func newItem(name string) *item {
return &item{
name: name,
}
}
func (i *item) updateAvailability() {
fmt.Printf("Item %s is now in stock\n", i.name)
i.inStock = true
i.notifyAll()
}
func (i *item) register(o observer) {
i.observerList = append(i.observerList, o)
}
func (i *item) deregister(o observer) {
i.observerList = removeFromslice(i.observerList, o)
}
func (i *item) notifyAll() {
for _, observer := range i.observerList {
observer.update(i.name)
}
}
func removeFromslice(observerList []observer, observerToRemove observer) []observer {
observerListLength := len(observerList)
for i, observer := range observerList {
if observerToRemove.getID() == observer.getID() {
observerList[observerListLength-1], observerList[i] = observerList[i], observerList[observerListLength-1]
return observerList[:observerListLength-1]
}
}
return observerList
}
observer.go
package main
type observer interface {
update(string)
getID() string
}
customer.go
package main
import "fmt"
type customer struct {
id string
}
func (c *customer) update(itemName string) {
fmt.Printf("Sending email to customer %s for item %s\n", c.id, itemName)
}
func (c *customer) getID() string {
return c.id
}
main.go
package main
func main() {
shirtItem := newItem("Nike Shirt")
observerFirst := &customer{id: "abc@gmail.com"}
observerSecond := &customer{id: "xyz@gmail.com"}
shirtItem.register(observerFirst)
shirtItem.register(observerSecond)
shirtItem.updateAvailability()
}
输出:
Item Nike Shirt is now in stock
Sending email to customer abc@gmail.com for item Nike Shirt
Sending email to customer xyz@gmail.com for item Nike Shirt
完整工作代码:
package main
import "fmt"
type subject interface {
register(Observer observer)
deregister(Observer observer)
notifyAll()
}
type item struct {
observerList []observer
name string
inStock bool
}
func newItem(name string) *item {
return &item{
name: name,
}
}
func (i *item) updateAvailability() {
fmt.Printf("Item %s is now in stock\n", i.name)
i.inStock = true
i.notifyAll()
}
func (i *item) register(o observer) {
i.observerList = append(i.observerList, o)
}
func (i *item) deregister(o observer) {
i.observerList = removeFromslice(i.observerList, o)
}
func (i *item) notifyAll() {
for _, observer := range i.observerList {
observer.update(i.name)
}
}
func removeFromslice(observerList []observer, observerToRemove observer) []observer {
observerListLength := len(observerList)
for i, observer := range observerList {
if observerToRemove.getID() == observer.getID() {
observerList[observerListLength-1], observerList[i] = observerList[i], observerList[observerListLength-1]
return observerList[:observerListLength-1]
}
}
return observerList
}
type observer interface {
update(string)
getID() string
}
type customer struct {
id string
}
func (c *customer) update(itemName string) {
fmt.Printf("Sending email to customer %s for item %s\n", c.id, itemName)
}
func (c *customer) getID() string {
return c.id
}
func main() {
shirtItem := newItem("Nike Shirt")
observerFirst := &customer{id: "abc@gmail.com"}
observerSecond := &customer{id: "xyz@gmail.com"}
shirtItem.register(observerFirst)
shirtItem.register(observerSecond)
shirtItem.updateAvailability()
}
输出:
Item Nike Shirt is now in stock
Sending email to customer abc@gmail.com for item Nike Shirt
Sending email to customer xyz@gmail.com for item Nike Shirt
- 设计* Golang* 观察者* Go 中的观察者设计模式* 模式*
OOP:GOLANG 中继承的完整指南
我们将尝试通过与 JAVA 中的继承进行比较来解释 GO 中的继承。我们首先想提到的是 GOLANG 没有像 JAVA 那样的“Extends”和“Implements”关键字。Go 以不同的方式提供有限的“Extends”和“Implements”功能,各自有其限制。在继续理解 GO 中的继承之前,有一些值得提及的要点。
-
Go 更倾向于组合而非继承。它允许将结构体嵌入到其他结构体中。
-
Go 不支持类型继承。
我们将从 GO 中继承的最简单示例开始。然后列出一个限制或缺失的功能。在后续的迭代中,我们将修复限制或继续添加缺失的功能,直到我们编写出展示 GO 中继承所有可能/不可能特性的程序。所以让我们开始吧。
继承的基本用例是子类型应该能够访问父类型的公共数据和方法。这在 GO 中通过嵌入实现。基结构体嵌入在子结构体中,子结构体可以直接访问基的数据显示和方法。请参见下面的代码:子结构体能够直接访问数据“color”并直接调用函数“say()”。
程序 1
package main
import "fmt"
type base struct {
color string
}
func (b *base) say() {
fmt.Println("Hi from say function")
}
type child struct {
base //embedding
style string
}
func main() {
base := base{color: "Red"}
child := &child{
base: base,
style: "somestyle",
}
child.say()
fmt.Println("The color is " + child.color)
}
输出:
Hi from say function
The color is Red
上述程序的一个限制是,你不能将子类型传递给一个期望基类型的函数,因为 GO 不允许类型继承。例如,下面的代码无法编译并给出错误——“无法将 child(类型*child)用作传递给 check 的参数中的类型 base”。
程序 2
package main
import "fmt"
type base struct {
color string
}
func (b *base) say() {
fmt.Println("Hi from say function")
}
type child struct {
base //embedding
style string
}
func check(b base) {
b.say()
}
func main() {
base := base{color: "Red"}
child := &child{
base: base,
style: "somestyle",
}
child.say()
fmt.Println("The color is " + child.color)
check(child)
}
输出:
cannot use child (type *child) as type base in argument to check
上述错误基本上说明在 GO 中仅通过使用嵌入不可能实现子类型。让我们尝试修复这个错误。这就是 GO 接口派上用场的地方。请参见下面版本的程序,它除了上述功能外,还修复了这个子类型错误。
程序 3
package main
import "fmt"
type iBase interface {
say()
}
type base struct {
color string
}
func (b *base) say() {
fmt.Println("Hi from say function")
}
type child struct {
base //embedding
style string
}
func check(b iBase) {
b.say()
}
func main() {
base := base{color: "Red"}
child := &child{
base: base,
style: "somestyle",
}
child.say()
fmt.Println("The color is " + child.color)
check(child)
}
输出:
Hi from say function
The color is Red
Hi from say function
在上述程序中,我们:(a) 创建了一个接口“iBase”,其中有“say”方法 (b) 我们将“check”方法更改为接受 iBase 类型的参数。
由于基结构体实现了“say”方法,反过来,子结构体嵌入了基结构体。因此,子方法间接实现了“say”方法,成为“iBase”的一种类型,这就是我们现在可以将子结构体传递给 check 函数的原因。很好,我们现在使用结构体和接口的组合修复了一个限制。
但还有一个限制。假设子类和基类都有一个名为 “clear” 的函数。现在 “say” 方法调用 “clear” 方法。当使用子结构调用 “say” 方法时,“say” 方法将调用基类的 “clear” 方法,而不是子类的 “clear” 方法。请见下面的示例。
程序 4
package main
import "fmt"
type iBase interface {
say()
}
type base struct {
color string
}
func (b *base) say() {
b.clear()
}
func (b *base) clear() {
fmt.Println("Clear from base's function")
}
type child struct {
base //embedding
style string
}
func (b *child) clear() {
fmt.Println("Clear from child's function")
}
func check(b iBase) {
b.say()
}
func main() {
base := base{color: "Red"}
child := &child{
base: base,
style: "somestyle",
}
child.say()
}
输出:
Clear from base's function
正如你所看到的,上述基类的 “clear” 函数被调用,而不是子类的 “clear” 方法。这与 Java 不同,在 Java 中会调用 “child” 的 “clear” 方法。
解决上述问题的一种方法是将 “clear” 作为基结构中的函数类型属性。这在 GO 中是可能的,因为函数是 GO 中的一等公民。请见下面的解决方案。
程序 5
package main
import "fmt"
type iBase interface {
say()
}
type base struct {
color string
clear func()
}
func (b *base) say() {
b.clear()
}
type child struct {
base //embedding
style string
}
func check(b iBase) {
b.say()
}
func main() {
base := base{color: "Red",
clear: func() {
fmt.Println("Clear from child's function")
}}
child := &child{
base: base,
style: "somestyle",
}
child.say()
}
输出:
Clear from child's function
让我们尝试为上面的程序添加一个新特性——
- 多重继承——子结构应该能够从两个基础结构访问多个属性和方法,并且子类型化也应该是可能的。以下是代码。
程序 6
package main
import "fmt"
type iBase1 interface {
say()
}
type iBase2 interface {
walk()
}
type base1 struct {
color string
}
func (b *base1) say() {
fmt.Println("Hi from say function")
}
type base2 struct {
}
func (b *base1) walk() {
fmt.Println("Hi from walk function")
}
type child struct {
base1 //embedding
base2 //embedding
style string
}
func (b *child) clear() {
fmt.Println("Clear from child's function")
}
func check1(b iBase1) {
b.say()
}
func check2(b iBase2) {
b.walk()
}
func main() {
base1 := base1{color: "Red"}
base2 := base2{}
child := &child{
base1: base1,
base2: base2,
style: "somestyle",
}
child.say()
child.walk()
check1(child)
check2(child)
}
输出:
Hi from say function
Hi from walk function
Hi from say function
Hi from walk function
在上面的程序中,子结构同时嵌入了 base1 和 base2。它也可以作为 iBase1 和 iBase2 接口的实例传递给 check1 和 check2 函数。这就是我们实现多重继承的方式。
现在一个大问题是我们如何在 GO 中实现 “类型层次结构”。如前所述,Go 不允许类型继承,因此没有类型层次结构。GO 有意不允许此特性,因此 接口行为的任何变化仅传播到其定义了接口所有方法的直接结构。
尽管我们可以像下面这样使用接口和结构体实现类型层次结构。
程序 7
package main
import "fmt"
type iAnimal interface {
breathe()
}
type animal struct {
}
func (a *animal) breathe() {
fmt.Println("Animal breate")
}
type iAquatic interface {
iAnimal
swim()
}
type aquatic struct {
animal
}
func (a *aquatic) swim() {
fmt.Println("Aquatic swim")
}
type iNonAquatic interface {
iAnimal
walk()
}
type nonAquatic struct {
animal
}
func (a *nonAquatic) walk() {
fmt.Println("Non-Aquatic walk")
}
type shark struct {
aquatic
}
type lion struct {
nonAquatic
}
func main() {
shark := &shark{}
checkAquatic(shark)
checkAnimal(shark)
lion := &lion{}
checkNonAquatic(lion)
checkAnimal(lion)
}
func checkAquatic(a iAquatic) {}
func checkNonAquatic(a iNonAquatic) {}
func checkAnimal(a iAnimal) {}
请查看上面的程序,我们是如何能够创建层次结构的(见下文)。这是 Go 创建类型层次结构的惯用方法,我们通过在结构体层级和接口层级上使用嵌入来实现这一点。需要注意的是,如果你希望在类型层次结构中有所区分,比如 “鲨鱼” 不应该同时是 “iAquatic” 和 “iNonAquatic”,那么在 “iAquatic” 和 “iNonAquatic” 的方法集中,应该至少有一个方法在另一个中不存在。在我们的例子中,“游泳” 和 “行走” 就是这些方法。
iAnimal
--iAquatic
----shark
--iNonAquatic
----lion
结论:
Go 不支持类型继承,但可以通过嵌入实现相同的功能,不过在创建这种类型层次结构时需要谨慎。此外,Go 不提供方法重写。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!