面向对象编程
结构体
结构体的声明语法
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实现封装的步骤
-
将结构体、 字段(属性)的首字母小写(不能导出了, 其它包不能使用, 类似 private)
-
给结构体所在包提供一个工厂模式的函数, 首字母大写。 类似一个构造函数
-
提供一个首字母大写的 Set 方法(类似其它语言的 public), 用于对属性判断并赋值
func ( 名称 结构体类型) SetXxx(参数列表) (返回值列表) {
//加入数据验证的业务逻辑
var.字段 = 参数
}
- 提供一个首字母大写的 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)
}