面向对象 | 青训营笔记

82 阅读5分钟

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

1. Go语言中的面向对象

  1. Go语言中没有Class类的概念,但是有struct结构体的概念
  2. 虽然Go语言没有Class,但是并不意味着Go不能面向对象编程,毕竟面向对象编程只是一个概念

2. 封装

1. 属性

  1. struct结构体中定义的变量就可以视为属性、字段

2. 方法

  1. struct结构体中不能定义方法
  2. Go语言中的方法是作用在接收者上的一个函数,接收者是某种特殊类型的变量。所以Go语言中的方法是特殊类型的函数
  3. 语法:func (recv recv_type) func_name([parameter list]) ([return list])
  4. 举一个栗子
// 1. 定义一个三角形类
type Triangle struct {
  Bottom float32
  Height float32
}

// 2. 定义三角形类计算面积的方法Area()
func (t *Triangle) Area() (area float32) {
  area = 0.5 * t.Bottom * t.Height
}

func main() {
  // 1. 创建对象,或者叫结构体变量
  r := Triangle{1, 2}
  // 2. 调用对象的方法获取面积
  fmt.Println(r.Area())
}

3. 访问权限

  1. Go语言中没有public这类的权限访问修饰符,而是使用首字母大小写来进行判断
  2. Go语言中的常量、变量、类型、接口、函数、结构体等命名开头是大写的话就表示是公开的,可以被其它包调用,非大写的就只能在包内访问
  3. 对于小写字母开头的私有类型,Go语言中没有提供get或set方法,我们只能自己定义,通常情况下get方法使用Get开头,set方法使用set方法
package person

type Student struct {
  name String
  age int
}

// 定义get方法,获取私有类型值
func (s *Student) GetName() string {
  return s.name
}
// 定义set方法,设置私有属性值
func (s *Student) SetName(name string) {
  s.name = name
}


// --------------------------------------------
package main

import "person"

func main() {
  s := new(person.Student)
  s.SetName("Tom")
  fmt.Println(s.GetName())
}

// 这只是一条测试语句

4. toString()函数

  1. Go语言中也有类似于Java中的toString()函数,只需要定义String() string方法,就可以以想要的形式打印输出结果,比如说time包中的Month

3. 继承

  1. Go语言中没有extends关键字,而是在结构体中内嵌匿名类型的方法来实现继承,下面举一个简单的栗子
// 1. 定义一个引擎接口
type Engine interface {
    Run()  // 第一个方法
    Stop()  // 第二个方法
}
// 2. 定义一个Bus类型
type Bus struct {
    Engine  // 内嵌接口
}

// 3. 定义结构体的方法,可以在方法中使用接口类型中定义的方法
// 这个时候代码是跑不起来的,需要额外实现这几个方法
func (c *Bus) Working() {
    c.Run()
    c.Stop()
}

4. 多态

  1. Go语言中的多态只需要让不同的结构体实现相同接口中的方法即可,下面举个例子
package main

import "fmt"

type Shape interface {
	Area() float32
}

type Square struct {
	side float32
}

func (s *Square) Area() float32 {
	return s.side * s.side
}

type Triangle struct {
	Bottom float32
	Height float32
}

func (t *Triangle) Area() float32 {
	return (t.Bottom * t.Height) / 2
}

func main() {
	s := &Square{10}
	t := &Triangle{3, 4}
	shapes := []Shape{s, t}
	for n, _ := range shapes {
		fmt.Printf("类型为:%T\n", shapes[n])
		fmt.Printf("面积为:%v\n", shapes[n].Area())
	}
}

5. 接口

1. 什么是接口

  1. 接口类型是Go语言中最重要的特性之一,接口类型定义了一组方法,但只是定义了方法,并没有给出方法的具体实现
  2. 接口本质上是一种类型,确切的说,是指针类型
  3. Go语言会根据非指针成员方法自动生成一个指针成员方法
  4. 但凡有一个指针成员方法,给接口赋值的时候就得赋值实例对象的指针(地址)

2. 接口的赋值

  1. Go语言中的接口不支持实例化操作,但是支持赋值操作,有如下两种
    1. 将实现接口的对象或者对象指针赋值给接口
      1. 如果接口中全部是非指针成员方法,就可以将结构体对象赋值给接口,但只要接口中有一个指针成员方法,就要将结构体对象的指针赋值给接口,因为指针成员方法的接受者是指针变量,所以只有指针对象才能调用,Go语言会自动根据非指针成员方法生成对应的指针成员方法
      2. 这种要求实例类对象所属的类(结构体)要实现接口中的所有方法
      3. Go语言规定了不能给内置的数据类型定义方法,但是我们可以使用内置的类型定义自己的类型,然后给自己的类型添加方法,下面举个简单的栗子:
type Integer int

type NumI interface {
	add(Integer) Integer
}

func (a Integer) add(b Integer) (res Integer) {
	res = a + b
	return
}

func main() {
	var a NumI
	var b Integer = 1
	a = b
	fmt.Println(a.add(1))
}
    1. 将一个接口赋值给另一个接口
      1. 在Go语言中,只要两个接口拥有相同的方法列表(不管这些方法的顺序是怎么样的),它们就是等同的,就可以相互赋值了
      2. Go语言中并不强制要求接口赋值给接口的时候接口要完全等价,比如A接口中的方法列表是B接口方法列表的子集,那么就可以将B接口赋值给A接口,注意顺序不要弄反了,是将大的赋值给小的这样才能完全覆盖

3. 接口的查询

  1. 接口查询是在程序动态运行的时候进行的
  2. 语法:接口名.(类型)

4. 接口的组合

  1. 不仅结构体可以和结构体组合,接口与接口之间也可以组合,下面举个简单的栗子
// 1. 定义第一个接口
type Interface1 interface {
    a() int
    b() int
}

// 2. 定义第二个接口
type Interface2 interface {
    c() int
    d() int
}

// 3. 定义一个接口然后组合这些interface1和interface2
// 实际上这种就是将方法提前组合了一下
type Interface3 interface {
    Interface1
    Interface2
}

5. 接口的常见应用

1. 类型推断

package main

import "fmt"

func main() {
	var a interface{} = func(a int) string {
		return fmt.Sprintf("d: %d", a)
	}
	switch b := a.(type) { // 局部变量b是类型转换后的结果
	case nil:
		fmt.Println("这是一个空指针!")
	case *int:
		fmt.Println(*b)
	case func(int) string:
		fmt.Println(b(66))
	default:
		fmt.Println("这是默认的情况")
	}
}

2. 多态的实现,这个上面有例子不再举了