Go---面向对象那些事(2) | 青训营笔记

63 阅读4分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第3天

继承

昨天讲了结构体的特点和方法,今天来讲讲继承。
Go语言实现继承的方式和其他语言不一样,它没有extends等关键字,而是通过结构体中的匿名字段实现的。

Go语言本身并没有继承的概念,但是可以通过组合来实现类似继承的效果。

组合是通过在一个结构体中包含另一个结构体来实现的。例如,我们有一个基础结构体Person,它有一些 基本属性,如NameAge。还有一个方法叫做CommonInfo. 然后我们定义一个新的结构体Student,它需要包含Person结构体,并且需要添加一些新的属性,如ID:

type Person struct {
    Name string
    Age  int
}

func (p *Person) CommonInfo() string {
    return fmt.Println("Name: %s, Age: %d", p.Name, p.Age)
}

type Student struct {
    Person //匿名字段
    ID     int
}

func main(){
    s := &Student{}
    s.Name = "lisi"
    s.Age = 18
    s.ID = 666
    fmt.Println(s.CommonInfo()) // Name: lisi, Age: 18\n
}

如上,便实现了继承:
另外,在Go语言中,如果一个结构体中有多个匿名字段,那么在访问这些字段时会使用就近原则。就近原则表示如果当前结构体中有同名字段,那么优先访问当前结构体中的字段,而不是匿名字段中的字段。

接口

在 Go 语言中,接口是一种类型,它定义了一组方法(也就是接口签名),但不提供实现。接口类型的变量可以存储任何实现了这些方法的值。

接口可以被实现(implemented)而不是继承(inherited)。这意味着任何类型,只要它实现了接口定义的方法,就可以被视为该接口类型。

接口类型变量可以存储实现了接口的任何类型的值。这意味着,如果一个函数接受一个接口类型参数,那么它可以接受任何实现了该接口的类型的值。

面向对象编程和接口编程是两种不同的编程范式,但在 Go 语言中它们是相辅相成的。接口可以用来实现面向对象编程的多态性。

例如,我们可以定义一个 Shape 接口,它有一个 Area() float64 方法,用来计算图形的面积:

type Shape interface {
	Area() float64
}
// 然后我们可以定义一个 Rectangle 类型,它实现了 Shape 接口:
type Rectangle struct {
	width, height float64
}

func (r Rectangle) Area() float64 {
	return r.width * r.height
}
// 同样我们可以定义一个 Circle 类型,它也实现了 Shape 接口:
type Circle struct {
	radius float64
}

func (c Circle) Area() float64 {
	return math.Pi * c.radius * c.radius
}
// 由于这两个类型都实现了 Shape 接口,我们可以定义一个函数,它接受 Shape 类型的参数,并打印出面积:
func printArea(s Shape) {
	fmt.Println("Area:", s.Area())
}
// 然后我们可以使用这个函数,传入一个 Rectangle 类型或者 Circle 类型的变量,它们都实现了 Shape 接口,都可以被函数处理:
r := Rectangle{width: 3, height: 4}
c := Circle{radius: 5}

printArea(r) // Area: 12
printArea(c) // Area: 78.53981633974483

接口还可以用来实现依赖注入(Dependency Injection),这是一种设计模式,可以让程序更具灵活性和可测试性。例如,我们可以定义一个 Logger 接口,它有一个 Log(message string) 方法,用来记录日志,然后我们可以实现多种 Logger 类型,如 ConsoleLogger 和 FileLogger,它们都实现了 Logger 接口。

然后我们可以在应用程序中,将 Logger 接口作为参数传入其他函数和类型,这样就可以在不改变原来类型的基础上,更改日志记录的方式。这就是依赖注入的基本思想。

例如,可以定义一个 Service 类型,它接受 Logger 接口类型的参数:

type Service struct {
    logger Logger
}

通过这种方式,我们就可以在不改变 Service 类型的情况下,在不同的环境中使用不同的日志记录方式,而不需要在 Service 中写入大量的条件语句。

另外,Go语言中的接口的实现是隐式的,一个类型如果实现了接口所有方法,那么就实现了这个接口。

接口为 Go 的类型系统和面向对象编程提供了更大的灵活性和可扩展性,使用接口可以减少代码之间的耦合,提高代码的可重用性和可测试性。