Go:面向对象编程

一、Go面向对象编程说明

  1. Go也支持面向对象编程(OOP),但是和传统的面向对象编程有区别,并不是纯粹的面向对象语言,所以我们说Go支持面向对象编程特性是比较准确的。
  2. Go没有类(class), Go语言的结构体(struct)和其它编程语言的类(class)有同等的地位,Go是基于struct来实现OOP特性的。
  3. Go面向对象编程非常简洁,去掉了传统OOP语言的继承、方法重载、构造函数和析构函数、隐藏的this指针等等。
  4. Go仍然有面向对象编程的继承,封装和多态的特性,只是实现的方式和其它OOP语言不一样,比如继承: Go没有extends关键字,继承是通过匿名字段来实现。
  5. Go面向对象(OOP)很优雅,OOP本身就是语言类型系统(type system)的一部分,通过接口(interface)关联,耦合性低,也非常灵活。

案例:

type Person struct {
	Name string
	Age  int
}

func main() {
	var p Person
	p.Name = "张三"
	p.Age = 20
	fmt.Println(p)//{张三 20}
}

二、结构体

语法:

type 结构体名称 struct{
	field1 type
 	field2 type
}

注意: 在创建一个结构体变量后,如果没有给字段赋值,都对应一个零值(默认值)。布尔类型是 false,数值是0,字符串是 "";指针、slicemap的零值都是nil,即还没有分配空间。

2.1 结构体的使用

var p Person

var p Person = Person{"张三", 20}

var p *Person = new(Person)

func main() {
	var p *Person = new(Person)
	
	//必须加上括号,因为.的优先级高于*
	(*p).Name = "张三"
	//p是结构体指针,设计者为了方便,允许使用p.xxx调用
	p.Age = 20
	fmt.Println(*p)//{张三 20}
}

var p *Person = &Person{}

func main() {
	var p *Person = &Person{}
	(*p).Name = "张三"
	p.Age = 20
	fmt.Println(*p)//{张三 20}
}

说明:

  1. 第3种和第4种方式返回的是结构体指针
  2. 结构体指针访问字段的标准方式应该是:(*结构体指针).字段名 ,比如(*person).Name = "tom"
  3. Go做了一个简化,也支持结构体指针.字段名,比如person.Name = "tom"。更加符合程序员 使用的习惯,Go编译器底层对person.Name做了转化(*person).Name

结构体使用注意事项和细节:

  1. 结构体的所有字段在内存中是连续的
  2. struct的每个字段上,可以写上一个tag, 该tag可以通过反射机制获取,常见的使用场景就是序
    列化和反序列化
type Person struct {
	Name string `json:name`
	Age  int    `json:age`
}

func main() {
	p := Person{"张三", 20}
	jsonStr, _ := json.Marshal(p)
	fmt.Println(string(jsonStr))//{"Name":"张三","Age":20}
}

2.2 方法

方法的定义:

func (recevier type) methodName(参数列表) (返回值列表){ 
	方法体 
	return 返回值
}

案例:

type Person struct {
	Name string
	Age  int
}
//这个方法showName()是结构体Person的方法
func (p Person) showName() {
	fmt.Println("Name=", p.Name)
}
func main() {
	p := Person{"张三", 20}
	p.showName() //Name= 张三
}

方法的注意事项和细节:

  1. 结构体类型是值类型,在方法调用中,遵守值类型的传递机制,是值拷贝传递方式
  2. 如程序员希望在方法中,修改结构体变量的值,可以通过结构体指针的方式来处理
func (p *Person) updateName() {
	*p.name="A"
}

三、面向对象编程特性

3.1 封装

封装(encapsulation)就是把抽象出的字段和对字段的操作封装在一起,数据被保护在内部,程序的其它包只有通过被授权的操作(方法),才能对字段进行操作。

封装可以隐藏实现细节,还可以对数据进行验证,保证安全合理。

封装的实现步骤:

  1. 将结构体、属性的首字母小写(类似private
  2. 给结构体所在包提供一个工厂模式的函数,首字母大写,类似一个构造函数
  3. 提供一个首字母大写的Set方法(类似public),用于对属性判断并赋值
func (var 结构体类型名) SetXxx(参数列表) (返回值列表) { 
	//加入数据验证的业务逻辑 
	var.字段 = 参数
}
  1. 提供一个首字母大写的Get方法(类似public),用于获取属性的值

案例:

实体类person.go

type person struct {
	name string //小写的属性不能直接通过p.xxx访问
	age  int
}

//工厂模式的函数相当于构造器
func NewPerson(name string, age int) *person {
	return &person{name: name, age: age}
}

//setter、getter方法
func (p *person) SetAge(age int) {
	if age > 0 && age < 150 {
		p.age = age
	} else {
		age = 0
	}
}

func (p *person) GetAge() int {
	return p.age
}

func (p *person) SetName(name string) {
	p.name = name
}
func (p *person) GetName() string {
	return p.name
}

main.go

import (
	"../model"
	"fmt"
)

func main() {
	person := model.NewPerson("smith", 12)
	person.SetAge(12)
	fmt.Println(*person)            //{smith 12}
	fmt.Println((*person).GetAge()) //12
}

3.2 继承

当多个结构体存在相同的属性和方法时,可以从这些结构体中抽象出结构体,在该结构体中定义这些相同的属性和方法,其它的结构体不需要重新定义这些属性(字段)和方法,只需嵌套这个匿名结构体即可。

继承使得代码利于维护和对功能的扩展。

语法:

type Goods struct { 
	Name string 
	Price int
}

type Book struct { 
	Goods //这里就是嵌套匿名结构体Goods 
	Writer string
}

说明:

  1. 当结构体和匿名结构体有相同的字段或者方法时,编译器采用就近访问原则访问,如希望访问匿名结构体的字段和方法,可以通过匿名结构体名来区分
  2. 结构体嵌入两个(或多个)匿名结构体(多重继承),如两个匿名结构体有相同的字段和方法(同时结构体本身没有同名的字段和方法),在访问时,就必须明确指定匿名结构体名字,否则编译报错
  3. 如果一个struct嵌套了一个有名结构体,这种模式就是组合,如果是组合关系,那么在访问组合 的结构体的字段或方法时,必须带上结构体的名字
type D struct {
   a A
}
var d D
d.a.Name = "jack"

3.3 多态

Go中的多态特性主要通过接口来体现的。

3.3.1 接口

interface类型可以定义一组方法,但是这些不需要实现。并且interface不能包含任何变量。到某个类型使用接口需要实现这些方法。

语法:

type 接口名 interface{
	method1(参数列表) 返回值列表
	method2(参数列表) 返回值列表
}

说明:

  1. 接口里的所有方法都没有方法体,即接口的方法都是没有实现的方法。接口体现了程序设计的多态和高内聚低偶合的思想
  2. Go中的接口不需要显式的实现。只要一个变量含有接口类型中的所有方法,那么这个变量就实现这个接口。因此,Go中没有implement这样的关键字

接口使用注意事项和细节:

  1. 接口本身不能创建实例,但是可以指向一个实现了该接口的自定义类型的变量
type Person interface {
	SayHello()
}

type Student struct {
	Person
	Name string
}

func (stu Student) SayHello() {
	fmt.Println("Hello Student")
}

func main() {
	var stu Student
	var p Person = stu
	p.SayHello() //Hello Student
}
  1. Go中,一个自定义类型需要将某个接口的所有方法都实现,我们说这个自定义类型实现了该接口
  2. 一个自定义类型只有实现了某个接口,才能将该自定义类型的实例(变量)赋给接口类型
  3. 只要是自定义数据类型,就可以实现接口,不仅仅是结构体类型
type integer int
func (int integer) SayHello(){
	fmt.Println("Hello integer")
}
func main(){
var i integer = 30
var p Person = i
p.Say()//Hello integer
}
  1. 一个自定义类型可以实现多个接口
  2. 一个接口(比如A接口)可以继承多个别的接口(比如BC 接口),这时如果要实现A接口,也必 须将BC接口的方法也全部实现
  3. interface类型默认是一个指针(引用类型),如果没有对interface初始化就使用,那么会输出nil
  4. 空接口interface{}没有任何方法,所以所有类型都实现了空接口, 即我们可以把任何一个变量赋给空接口

猜你喜欢

转载自blog.csdn.net/qq_38697437/article/details/106396200