16、面向对象

面向对象

面向对象三要素:

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

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

​ 严格意义来说,Go语言就是不想实现面向对象编程范式。但是面向对象又有一些不错的特性,Go语言通过组合的方式实现了类似的功能。Go语言实现了一种非常有自我特征的面向对象。

封装

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

访问控制:

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

构造函数

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

package main

import "fmt"

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}
}

func main() {
	p := NewDefaultAnimal()
	fmt.Println(*p, p.name, p.age)
	fmt.Println(*NewAnimal("zfl", 21))
}

{nobody 1} nobody 1
{zfl 21}

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

继承

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

package main

import "fmt"

// 定义 Animal 结构体,表示动物
type Animal struct {
	name string
	age  int
}

// 定义 Cat 结构体,继承自 Animal 结构体,表示猫
type Cat struct {
	Animal
	color string
}

// 为 Animal 结构体定义 run 方法
func (*Animal) run() {
	fmt.Println("Animal run ~~~")
}


// 为 Cat 结构体定义 run 方法,覆盖了父类 Animal 的 run 方法
func (*Cat) run() {
	fmt.Println("cat run~~~")
}

func main() {
	cat := new(Cat)
	cat.run()   //调用 Cat 结构体的 run 方法,输出cat run信息  ,如果没有cat的函数方法,默认输出的是cat.Animal.run()      
	cat.Animal.run() //用 Cat 结构体内嵌的 Animal 结构体的 run 方法,输出动物在奔跑的信息
}

cat run~~~
Animal run ~~~

覆盖

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

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

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

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

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

func (c *Cat) run() {
	c.run()         //cat.run() 这是无限递归调用,小心!自己调自己
	c.Animal.run() // 	这是调用父结构体方法
	fmt.Println("Cat 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() {
	d := new(Dog)
	d.name = "tom"
	test(d)
	c := new(Cat)
	c.name = "herry"
	test(c)
}

animal run ~~~
Dog run+++
animal run ~~~
cat run+++

定义了一个 Runner 接口,其中包含了一个 run 方法。然后,定义了一个 Animal 结构体,以及继承自 Animal 的 Cat 和 Dog 结构体。这两个结构体分别实现了 Runner 接口的 run 方法。

在 main 函数中,创建了一个 Dog 类的实例 d 和一个 Cat 类的实例 c,并分别调用了 test 函数,将这两个实例作为参数传入。由于 Dog 和 Cat 类都实现了 Runner 接口的 run 方法,因此它们都可以作为 Runner 接口的实例进行传递和调用。

结构体排序

排序接口

要实现某类型的排序

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

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

结构体排序

假设有N个学生,学生有姓名和年龄,按照年龄排序结构体实例。
学生使用结构体Student,多个学生就使用切片[ ]Student。

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"
	"strconv"
	"time"
)

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)
}

[{Name:Tom0 Age:46} {Name:Tom1 Age:27} {Name:Tom2 Age:48} {Name:Tom3 Age:49} {Name:Tom4 Age:40}], []main.Student
​~~~~~~~~~~~~~~~~~~~~~~~~~~~
[{Name:Tom1 Age:27} {Name:Tom4 Age:40} {Name:Tom0 Age:46} {Name:Tom2 Age:48} {Name:Tom3 Age:49}], []main.Student

切片排序简化

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

package main

import (
	"fmt"
	"math/rand"
	"sort"
	"strconv"
	"time"
)

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)
}

[{Name:Tom0 Age:21} {Name:Tom1 Age:41} {Name:Tom2 Age:21} {Name:Tom3 Age:28} {Name:Tom4 Age:27}], []main.Student
​~~~~~~~~~~~~~~~~~~~~~~~~~~~
[{Name:Tom1 Age:41} {Name:Tom3 Age:28} {Name:Tom4 Age:27} {Name:Tom0 Age:21} {Name:Tom2 Age:21}],[]main.Studet

map排序

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

key排序

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

package main

import (
	"fmt"
	"sort"
)

func main() {
	m := make(map[int]string)
	m[1] = "a"
	m[2] = "b"
	m[0] = "c"
	var keys []int
	for k := range m {
		keys = append(keys, k)
	}
	sort.Ints(keys)
	fmt.Println(keys)

	for _, k := range keys {
		fmt.Println("Key:", k, "Value:", m[k])
	}
}

[0 1 2]
Key: 0 Value: c
Key: 1 Value: a
Key: 2 Value: b

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] = "b"
	m[0] = "c"

	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)
}

[{1 a} {2 b} {0 c}]
[{1 a} {2 b} {0 c}]

你可能感兴趣的:(go,golang,开发语言)