Go中的面向对象

面相对象三要素

  • 封装:将属性(数据)和方法(操作)封装,提供访问控制,隐藏实现细节,暴露该暴露的
  • 继承:子类可以从父类直接获得属性和方法,减少重复定义。子类中如果与父类不同,可以自己定义新的属性和方法,也可以覆盖同名的属性和方法
  • 多态:前提是继承和覆盖,使得子类中虽然使用同一个方法,但是不同子类表现不同,就是不同的态

实现了以上特征的语言,才能称为面向对象编程范式语言。

严格意义上说,Go语言不想实现面向对象编程范式。但是面相对象又有一些不错的特征,Go语言通过组合的方式实现了类似的功能。

只能说,Go语言实现了一种非常有自我特征的面向对象。

封装

通过结构体,可以把数据字段封装在内,还可以为结构体提供方法。

访问控制:

  • 属性、方法标识符首字母大写,实现了对包外可见的访问控制
  • 属性、方法标识符首字母小写,仅包内可见
  • 这些一定程度上实现了public、private的访问控制。

构造函数

Go没有提供类似C++、Java一样的构造函数、析构函数。在Go中,用构造结构体实例的函数,这个函数没有特别的要求,只要返回结构体实例或其指针即可(建议返回指针,不然返回值会拷贝)。习惯上,构造函数命名是New或new开头。如果有多个构造函数,可以使用不同命名函数,因为Go也没有函数重载。

type Animal struct {
 name string
 age  int
}
func NewDefaultAnimal() *Animal {
 return &Animal{"nobody", 1} 
}
func NewAnimal(name string, age int) *Animal {
 return &Animal{name, age} 
}

通过不同的函数名来模拟构造函数重载

继承

Go语言没有提供继承的语法,实际上需要通过匿名结构体嵌入(组合)来实现类似效果

package main
import "fmt"
type Animal struct {
 name string
 age  int
}
func (*Animal) run() {
 fmt.Println("Animal run~~~") 
}
type Cat struct {
 Animal // 匿名结构体嵌入
 color  string
}
func main() {
 cat := new(Cat)
 cat.run()
    cat.Animal.run()
}

通过匿名结构体嵌入,子结构体就拥有了父结构体的属性name、age、和run方法

覆盖

覆盖override,也称为重写。注意不是重载overload

func (*Cat) run() {
 fmt.Println("Cat run+++") 
}

为Cat增加一个run方法,这就是覆盖。特别注意cat.run() cat.Animal.run()的区别

上例增加run方法时完全覆盖,就是不依赖父结构体方法,重写功能。

如果是依赖父结构体方法,那就要在子结构体方法中显式调用它

func (c *Cat) run() {
 c.run()        // 可以吗? 不可以,无限递归了
 c.Animal.run() // 可以吗?
 fmt.Println("Cat run+++") }

cat.run() 这是无限递归,不能这么用

c.Animal.run()这是调用父结构体方法

多态

Go语言不能像java语言一样使用多态,但是可以通过引入interface接口来解决

package main
import "fmt"
type Runner interface {
 run()
}
type Animal struct {
 name string
 age  int
}
func (*Animal) run() {
 fmt.Println("Animal run~~~") }
type Cat struct {
 Animal // 匿名结构体嵌入
 color  string
}
func (c *Cat) run() {
 c.Animal.run()
 fmt.Println("Cat run+++") }
type Dog struct {
 Animal // 匿名结构体嵌入
 color  string
}
func (d *Dog) run() {
 d.Animal.run()
 fmt.Println("Dog run+++") }
func test(a Runner) { // 多态
 a.run()
}
func main() {
 // var a Animal = Cat{} // Go做不到这样赋值
 // a.run() // Go无法写出这2行,用接口
    
 d := new(Dog)
 d.name = "snoopy"
 test(d)
 c := new(Cat)
 c.name = "Garfield"
 test(c)
}

test使用同一个类型的同一个接口却表现出不同的结果,这就是多态

结构体排序

排序接口
// An implementation of Interface can be sorted by the routines in this package.
// The methods refer to elements of the underlying collection by integer index.
type Interface interface {
 // Len is the number of elements in the collection.
 Len() int
 // Less reports whether the element with index i
 // must sort before the element with index j.
 //
 // If both Less(i, j) and Less(j, i) are false,
 // then the elements at index i and j are considered equal.
 // Sort may place equal elements in any order in the final result,
 // while Stable preserves the original input order of equal elements.
 //
 // Less must describe a transitive ordering:
 // - if both Less(i, j) and Less(j, k) are true, then Less(i, k) must be true as well.
 // - if both Less(i, j) and Less(j, k) are false, then Less(i, k) must be false as well.
 //
 // Note that floating-point comparison (the < operator on float32 or 
float64 values)
 // is not a transitive ordering when not-a-number (NaN) values are involved.
 // See Float64Slice.Less for a correct implementation for floating-point values.
 Less(i, j int) bool
 // Swap swaps the elements with indexes i and j.
 Swap(i, j int) }

从接口定义来看,要实现某类型的排序

  • 要知道有多少元素
  • 2个指定索引的元素怎么比较大小,索引i 的元素小于索引j 的值返回true,反之返回false
  • 如何交换指定索引上的元素

那么自定义类型,要想排序,就要实现sort包中该接口

结构体实例排序

假设有N个学生,学生有姓名和年龄,按照年龄排序结构体实例。

学生使用结构体Student,多个学生就使用切片[ ]Student。

参照 sort.Ints() 的实现

func Ints(x []int) { Sort(IntSlice(x)) } 观察这个方法,它依赖下面的定义
// IntSlice attaches the methods of Interface to []int, sorting in increasing order.
type IntSlice []int
func (x IntSlice) Len() int           { return len(x) }
func (x IntSlice) Less(i, j int) bool { return x[i] < x[j] }
func (x IntSlice) Swap(i, j int)     { x[i], x[j] = x[j], x[i] }

就是要在[]Student上实现interface接口的Len、Less、Swap方法。为了 方便可以定义一个新类型,好实现方法

package main
import (
 "fmt"
 "math/rand"
 "sort"
 "time"
 "strconv"
)
type Student struct {
 Name string
 Age  int
}
type StudentSlice []Student
func (x StudentSlice) Len() int           { return len(x) }
func (x StudentSlice) Less(i, j int) bool { return x[i].Age < x[j].Age }
func (x StudentSlice) Swap(i, j int)     { x[i], x[j] = x[j], x[i] }
func main() {
 // 随机生成学生数据
 r := rand.New(rand.NewSource(time.Now().UnixNano()))
 students := make([]Student, 0, 5)
 for i := 0; i < 5; i++ {
 name := "Tom" + strconv.Itoa(i)
 age := r.Intn(30) + 20
 students = append(students, Student{name, age})
 }
 fmt.Printf("%+v, %[1]T\n", students)
 fmt.Println("~~~~~~~~~~~~~~~~~~~~~~~~~~~")
 sort.Sort(StudentSlice(students))
 // 强制类型转化为StudentSlice后就可以应用接口方法排序了
 fmt.Printf("%+v, %[1]T\n", students) }

切片排序简化

上例中,对于切片来说,Len、Swap实现其实都这么写,切片中元素排序,就是某种类型的元素之间如何比较大小不知道,能否只提出这一部分的逻辑单独提供?从而简化切片的排序。这就要靠sort.Slice(待排序切片,less函数) 了。

package main
import (
 "fmt"
 "math/rand"
 "sort"
 "time"
 "strconv"
)
type Student struct {
 Name string
 Age  int
}
func main() {
 // 随机生成学生数据
 r := rand.New(rand.NewSource(time.Now().UnixNano()))
 students := make([]Student, 0, 5)
 for i := 0; i < 5; i++ {
 name := "Tom" + strconv.Itoa(i)
 age := r.Intn(30) + 20
 students = append(students, Student{name, age})
 }
 fmt.Printf("%+v, %[1]T\n", students)
 fmt.Println("~~~~~~~~~~~~~~~~~~~~~~~~~~~")
 sort.Slice(students, func(i, j int) bool {
 return students[i].Age < students[j].Age
 })
 fmt.Printf("%+v, %[1]T\n", students) }

map的排序

map是键值对的集合,是无序的hash表。但是排序输出是序列,也就是排序所需的键或值要存入序列中,然后才能排序。

key排序

思路:提取key为序列,排序后,用有序序列中的key映射value输出

package main
import (
    "fmt"
    "sort"
)
func main() {
    // To create a map as input
    m := make(map[int]string)
    m[1] = "a"
    m[2] = "c"
    m[0] = "b"
    // To store the keys in slice in sorted order
    var keys []int
    for k := range m {
        keys = append(keys, k)
   }
    sort.Ints(keys)
    // key排好序,就可以用key找到value了
    for _, k := range keys {
        fmt.Println("Key:", k, "Value:", m[k])
   }
}
value排序

不能使用key排序思路,想象每一个键值对就是一个{key:xxx, value:yyy}的结构体实例,就转换成了结构体序列排序了。

package main
import (
 "fmt"
 "sort"
)
type Entry struct {
 Key   int
 Value string
}
func main() {
 m := make(map[int]string)
 m[1] = "a"
 m[2] = "c"
 m[0] = "b"
 entries := make([]Entry, len(m))
 i := 0 // 为什么用了i
 for k, v := range m {
 entries[i] = Entry{k, v}
 i++
 }
 fmt.Println(entries)
 sort.Slice(entries, func(i, j int) bool {
 return entries[i].Value < entries[j].Value
 }) // Value升序
 fmt.Println(entries) }