Go中的面向对象
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) }