通过示例学习-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)中的多个常量声明

来源:golangbyexample.com/multiple-constant-declarations-go/

目录

  • 概述

  • 同时声明多个常量,具有不同的值和类型

  • 同时声明多个常量,具有相同的值和类型

  • 组合上述两个

  • 单行多个声明

概述

以下是一些同时声明多个常量的方法。

同时声明多个常量,具有不同的值和类型

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)中的多个延迟函数

来源:golangbyexample.com/multiple-defer-functions-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* 命名* 变量

Go (Golang) 中的嵌套包

来源:golangbyexample.com/nested-packages-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

你可以看到 advancedmath 包内的嵌套包。

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) 中的嵌套结构体

来源:golangbyexample.com/nested-struct-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 中看到你的应用程序像这样。

![](https://gitee.com/OpenDocCN/geekdoc-golang-zh/raw/master/docs/go-exam-2023/img/4f5038db14d03848095a716e1ee21aa3.png)

但你将无法在“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”函数中花费的平均时间。

![](https://gitee.com/OpenDocCN/geekdoc-golang-zh/raw/master/docs/go-exam-2023/img/a9f1c59342bddde8e47f4441b4866c15.png)* context* go* golang* intrumentation* newrelic

Go(Golang)中的非重叠区间程序

来源:golangbyexample.com/non-overlapping-intervals-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是字符串类型,它实现了breathewalk方法,因此将cat类型的实例赋值给animal类型的变量是正确的。

Go 中的空对象设计模式(Golang)。

来源:golangbyexample.com/null-object-design-pattern-golang/

注意:想了解其他所有设计模式如何在 GO 中实现,请查看这个完整参考 – Go 中的所有设计模式

目录

** 简介:

  • 完整工作代码:

简介:

空对象设计模式是一种行为设计模式。当客户端代码依赖于某些可能为 null 的依赖项时,它非常有用。使用此设计模式可以防止客户端在这些依赖项的结果上进行 null 检查。值得注意的是,客户端的行为对于这些 null 依赖项也是可以接受的。

空对象设计模式的主要组件是:

  1. 实体 – 它是定义子结构体必须实现的原始操作的接口。

  2. ConcreteEntity – 它实现了实体接口。

  3. NullEntity – 它表示 null 对象。它也实现了实体接口,但具有 null 属性。

  4. 客户端 – 客户端获取实体接口的实现并使用它。它并不关心实现是 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 语言中的字符数或字符串长度

来源:golangbyexample.com/number-characters-string-golang/

在 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]

这是每个字符与其字节序列的映射。如你所见,abc各占 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) 中的数字/整数/浮点常量。

来源:golangbyexample.com/integer-constant-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 常量可以赋值给所有 floatscomplex 类型,但不能赋值给 integer,因为例如浮点数 5.3 不能是整数。

根据类似的逻辑,未类型的 complex 常量可以赋值给 complex 类型,但不能赋值给 integerfloat,因为例如浮点数 5i+3 不能是 integerfloat

让我们看看一个程序来理解它。

示例

请参见下面的程序说明上述观点。在程序中我们有例子。

  • 类型化整数常量。

  • 未类型未命名的整数常量。

  • 未类型命名的整数常量。

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 中的对象池设计模式

来源:golangbyexample.com/golang-object-pool/

注意:如果想了解如何在 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 图:**

![](https://gitee.com/OpenDocCN/geekdoc-golang-zh/raw/master/docs/go-exam-2023/img/5087b754286321979495bdbe677f2030.png)

# **映射:**

下表表示 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

OOP:GOLANG 中继承的完整指南

来源:golangbyexample.com/oop-inheritance-golang-complete/

我们将尝试通过与 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 不提供方法重写。

posted @   绝不原创的飞龙  阅读(9)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示