GO面向对象编程

结构体

结构体的声明语法

type 结构体名称 struct {
	filed1 type
	filed2 type
}

示例:

type Person struct {
	name string
	sex string
	age int
}

注意: 结构体是值类型

创建结构体实例的四种方法

  • 方式一: 直接声明
package main

import "fmt"

func main() {
	var p Person
	p.name = "张三"
	p.age = 23
	p.sex = "男"
    fmt.Println(p)

}

type Person struct {
	name string
	sex string
	age int
}

在这里插入图片描述

  • 方式二: {}
package main

import "fmt"

func main() {
	var p2 Person = Person{"李四","男",25}
	fmt.Println(p2)
}

type Person struct {
	name string
	sex string
	age int
}

在这里插入图片描述

  • 方式三: &
package main

import "fmt"

func main() {
	var p3 *Person = new(Person)
	p3.name = "王五"
	p3.sex = "男"
	p3.age = 25
	fmt.Println(*p3)
}

type Person struct {
	name string
	sex string
	age int
}

在这里插入图片描述

  • 方式四: {}
package main

import "fmt"

func main() {
	var p4 *Person = &Person{"李明","男",20}
	fmt.Println(*p4)
	p4.age = 22
	fmt.Println(*p4)
}

type Person struct {
	name string
	sex string
	age int
}

在这里插入图片描述
方式3和方式4返回的是 结构体指针
结构体指针访问字段的标准方式应该是: (*结构体指针).字段名 , 比如 (*person).Name = “tom”
但 go 做了一个简化, 也支持 结构体指针.字段名, 比如 person.Name = “tom”。 更加符合程序员
使用的习惯, go 编译器底层 对 person.Name 做了转化 (*person).Name。

  • 结构体的所有字段在内存中是连续的
  • struct 的每个字段上, 可以写上一个 tag, 该 tag 可以通过反射机制获取, 常见的使用场景就是序列化和反序列化
package main

import (
	"encoding/json"
	"fmt"
)

func main() {
	var stu Student
	stu.Name = "小红"
	stu.Age = 18
	stu.Sex = "女"
	jstron, err := json.Marshal(stu)
	if err != nil {
		panic(err)
	}
	fmt.Println(string(jstron))
}

type Student struct {
	Name string `json:"name"`
	Sex string `json:"sex"`
	Age int `json:"int"`
}

func (stu *Student) setName(name string) {
	stu.Name = name
}

在这里插入图片描述

工厂模式

在创建结构体时,结构体名称首字母大写,可以在其它包导入使用,但如果结构体名称首字母是小写,这是就不能在其它包导入使用了。这时可以使用工厂模式来解决。
在这里插入图片描述
在这里插入图片描述
如果结构体的属性Filed首字母也是小写,在其它包就不能通过 变量.字段的方法区获取字段值了,该怎么办呢,学过Java的同学,知道JavaBean的get和set方法,这里也是类似的实现方法。

把student修改如下

package model

type student struct {
	name string
	id int64
}

//工厂模式,外部可以访问该方法
func NewStudent(name string,id int64) *student{
	return &student{
		name: name,
		id: id,
	}
}

func (s *student) GetName() string{
	return s.name
}
func (s *student) SetName(name string){
	s.name = name
}
func (s *student) GetId() int64{
	return s.id
}
func (s *student) SetId(id int64){
	s.id = id
}

//创建student实例

package main

import (
	model "Test/obj/factory"
	"fmt"
)

func main() {

	//声明student变量,student首字母小写,可以使用工厂模式,返回一个指向student的指针
    var stu = model.NewStudent("小明",1001)
    fmt.Println(*stu)
    stu.SetId(1002)
    stu.SetName("小红")
    fmt.Println(*stu)
	fmt.Println("name = ",stu.GetName()," id = ",stu.GetId())
}

在这里插入图片描述

面向对象三大特性

封装

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

golang实现封装的步骤

  1. 将结构体、 字段(属性)的首字母小写(不能导出了, 其它包不能使用, 类似 private)

  2. 给结构体所在包提供一个工厂模式的函数, 首字母大写。 类似一个构造函数

  3. 提供一个首字母大写的 Set 方法(类似其它语言的 public), 用于对属性判断并赋值

func ( 名称  结构体类型) SetXxx(参数列表) (返回值列表) {
	//加入数据验证的业务逻辑
	var.字段 = 参数
}
  1. 提供一个首字母大写的 Get 方法(类似其它语言的 public), 用于获取属性的值
func (名称 结构体类型) GetXxx() {
	return var.age;
}

代码示例:

package model

type student struct {
	name string
	id int64
}

//工厂模式,外部可以访问该方法
func NewStudent(name string,id int64) *student{
	return &student{
		name: name,
		id: id,
	}
}

func (s *student) GetName() string{
	return s.name
}
func (s *student) SetName(name string){
	s.name = name
}
func (s *student) GetId() int64{
	return s.id
}
func (s *student) SetId(id int64){
	s.id = id
}

使用

package main

import (
	model "Test/obj/factory"
	"fmt"
)

func main() {

	//声明student变量,student首字母小写,可以使用工厂模式,返回一个指向student的指针
    var stu = model.NewStudent("小明",1001)
    fmt.Println(*stu)
    stu.SetId(1002)
    stu.SetName("小红")
	fmt.Println(*stu)
	fmt.Println("name = ",stu.GetName()," id = ",stu.GetId())
}

继承

继承可以解决代码复用,让我们的编程更加靠近人类思维。
当多个结构体存在相同的属性(字段)和方法时,可以从这些结构体中抽象出结构体(父),在该结构体中定义这些相同的属性和方法。
其它的结构体不需要重新定义这些属性(字段)和方法, 只需嵌套一个(父)匿名结构体即可。

package main

import "fmt"

// 学生基类(父类)
type Student struct {
	Name string
	Age int
	Score float64
}

func (stu *Student) Info() {
	fmt.Printf("学生名 = %v  年龄 = %v  成绩 = %v\n",stu.Name,stu.Age,stu.Score)
}

func (stu *Student) SetScore(score float64){
	stu.Score = score
}
// 小学生
type Pupil struct {
	Student
}

func (p *Pupil) exam(){
	fmt.Println("小学生在考试中.....")
}

type CollegeStudent struct {
	Student
}
func (cs *CollegeStudent) exam(){
	fmt.Println("大学生在考试中.....")
}

func main() {
	pupil := &Pupil{}
	pupil.Student.Name = "小明"  //当子类和父类没有同名的属性时 等价于 pupil.Name = "小明"
	pupil.Age = 10
	pupil.SetScore(90.5)
	pupil.Info()
	pupil.exam()

	grau := &CollegeStudent{
		Student{
			Name: "小刚",
			Age: 19,
			Score: 98,
		},
	}
	grau.SetScore(99.5)
	grau.Info()
	grau.exam()
}

在这里插入图片描述

继承的注意事项

  • 结构体可以使用嵌套匿名结构体所有的字段和方法, 即: 首字母大写或者小写的字段、 方法、都可以使用。
  • 匿名结构体字段访问可以简化
pupil.Student.Name = "小明"  //当子类和父类没有同名的属性时 等价于 pupil.Name = "小明"

对于pupil.Name = “小明”

编译期首先看 pupil对应的类型有没有Name,如果有则直接调用pupil类型Name字段,
如果没有就会看pupil对应的类型的嵌套结构体Student有没有Name字段,有就调用,没有就报错。

  • 当结构体和匿名结构体有相同的字段或者方法时, 编译器采用就近访问原则访问, 如希望访问匿名结构体的字段和方法, 可以通过匿名结构体名来区分

  • 结构体嵌入两个(或多个)匿名结构体, 如两个匿名结构体有相同的字段和方法(同时结构体本身没有同名的字段和方法), 在访问时, 就必须明确指定匿名结构体名字, 否则编译报错

  • 如果一个 struct 嵌套了一个有名结构体, 这种模式就是组合, 如果是组合关系, 那么在访问组合的结构体的字段或方法时, 必须带上结构体的名字

  • 在一个结构体中对于每一种数据类型只能有一个匿名字段

接口 interface

多态的特性主要由接口体现

声明格式

type 接口类型名 interface{
    方法名1( 参数列表1 ) 返回值列表1
    方法名2( 参数列表2 ) 返回值列表2
    …
}
  • 接口类型名:使用 type 将接口定义为自定义的类型名。
  • 方法名:当方法名首字母是大写时,且这个接口类型名首字母也是大写时,这个方法可以被接口所在的包(package)之外的代码访问。
  • 参数列表、返回值列表:参数列表和返回值列表中的参数变量名可以被忽略。
    注意: 接口

如何实现一个接口

接口被实现需要满足以下条件。
条件一: 接口的方法与实现接口的类型方法格式一致。
这里的格式指的是: 方法的名称、参数列表、返回参数列表。
也就是说,只要实现接口类型中的方法的名称、参数列表、返回参数列表中的任意一项与接口要实现的方法不一致,那么接口的这个方法就不会被实现。

条件二: 接口的所有方法都被实现

注意:

  • 一个类型可以实现多个接口。
  • 多个类型共同实现一个接口。
    一个接口的方法,不一定需要由一个类型完全实现,接口的方法可以通过在类型中嵌入其他类型或者结构体来实现。也就是说,使用者并不关心某个接口的方法是通过一个类型完全实现的,还是通过多个结构嵌入到一个结构体中拼凑起来共同实现的。

示例代码

package main

import "fmt"
//Usb接口
type Usb interface {
	info()
}

type TypeC struct {

}
// TypeC类型实现Usb接口
func (tc TypeC) info(){
	fmt.Println("我是typ-c接口")
}
type Usb3 struct {

}
// Usb3类型实现Usb接口
func (u3 Usb3) info(){
	fmt.Println("我是USB3.0接口")
}

type Computer struct {

}

func (cp Computer) work (usb Usb)  {
	usb.info()
}

func main() {
	computer := Computer{}
	tc := TypeC{}
	u3 := Usb3{}
	computer.work(tc)
	computer.work(u3)

}

在这里插入图片描述

接口使用细节

  • 接口本身不能创建实例,但是可以指向一个实现了该接口的自定义类型的变量(实例)
  • 接口中所有的方法都没有方法体,即都是没有实现的方法。
  • 在 Golang 中, 一个自定义类型需要将某个接口的所有方法都实现, 我们说这个自定义类型实现了该接口。
  • 一个自定义类型只有实现了某个接口, 才能将该自定义类型的实例(变量)赋给接口类型
  • 只要是自定义数据类型, 就可以实现接口, 不仅仅是结构体类型
  • 一个自定义类型可以实现多个接口
  • Golang 接口中不能有任何变量
  • 一个接口(比如 A 接口)可以继承多个别的接口(比如 B,C 接口), 这时如果要实现 A 接口, 也必须将 B,C 接口的方法也全部实现。
  • interface 类型默认是一个指针(引用类型), 如果没有对 interface 初始化就使用, 那么会输出 nil。
  • 空接口 interface{} 没有任何方法, 所以所有类型都实现了空接口, 即我们可以把任何一个变量赋给空接口。
    在这里插入图片描述

多态

多态是面向对象的第三大特征, 在 Go 语言, 多态特征是通过接口实现的。 可
以按照统一的接口来调用不同的实现。 这时接口变量就呈现不同的形态。

接口体现多态的两种形式

  • 多态参数
    在前面的Usb接口案例中,Usb参数既可以接收TypeC变量又可以接收Usb3变量。
  • 多态数组
    定义一个Usb数组,存放TypeC和Usb3
package main

import "fmt"
//Usb接口
type Usb interface {
	info()
}

type TypeC struct {
     Desc string
}
// TypeC类型实现Usb接口
func (tc TypeC) info(){
	fmt.Println("我是typ-c接口")
}
type Usb3 struct {
	Desc string
}
// Usb3类型实现Usb接口
func (u3 Usb3) info(){
	fmt.Println("我是USB3.0接口")
}

type Computer struct {

}

func (cp Computer) work (usb Usb)  {
	usb.info()
}



func main() {
	var usbArr [2]Usb
	computer := Computer{}
	tc := TypeC{"type-c"}
	u3 := Usb3{"usb 3.0"}
	computer.work(tc)
	computer.work(u3)
	var u Usb = tc
	u.info()
	usbArr[0] = tc
	usbArr[1] = u3
	fmt.Println(usbArr)

}

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值