Golang 面向对象编程

目录
00 Golang语言面向对象编程说明
01 字段、属性
02 方法
03 面向对象编程
04 工厂模式
05 面向对象编程思想

00 Golang语言面向对象编程说明

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

结构体和结构体变量(实例)的区别和联系

  • 结构体是自定义的数据类型,代表一类事物
  • 结构体变量(实例)是具体的,实际的,代表一个具体变量

如何声明结构体

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

定义Dog结构体

type Dog struct{
    Name string
    Age int64
    Color string
}

01 字段、属性

  • 从概念或叫法上看:结构体字段=属性= field
  • 字段是结构体的一个组成部分,一般是基本数据类型、数组,也可是引用类型

注意事项和细节说明

  • 字段声明语法同变量,示例:字段名字段类型
  • 字段的类型可以为:基本类型、数组或引用类型
  • 在创建一个结构体变量后,如果没有给字段赋值,都对应一个零值(默认值),规则同前面讲的一样:
    • 使用slice一定make :p1.slice1=make([]int,10)
    • 使用map一定make :p1.map1=make(map[string]string)
    • 布尔类型是 false ,数值是0 ,字符串是""
    • 数组类型的默认值和它的元素类型相关,比如score [3]int 则为[0,0, 0]
    • 指针,slice,和map 的零值都是nil ,即还没有分配空间
  • 不同结构体变量的字段是独立,互不影响,结构体是值类型。

创建结构体变量和访问结构体字段

var dd Dogvar dd Dog = Person{"小花",4,"red"}
var dd *Dog = new(Dog)
(*dd).Name = "小花"    dd.Name = "小花"        
var dd *Dog = &Dog{"小花",4,"red"}
var dd *Dog = &Dog{}
(*dd).Name = "小花"    
dd.Name = "小花"        

说明:

1)第3种和第4种方式返回的是结构体指针。

2)结构体指针访问字段的标准方式应该是:(*结构体指针).字段名,
比如(*person).Name = "tom"

3)但go做了一个简化,也支持结构体指针.字段名,比如 person.Name = "tom"。

4)go编译器底层对person.Name做了转(*person).Name.

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

  • 结构体的所有字段在内存中是连续的
  • 结构体是用户单独定义的类型,和其它类型进行转换时需要有完全相同的字段(名字、个数和类型)
  • 结构体进行type重新定义(相当于取别名),Golang认为是新的数据类型,但是相互间可以强转
  • struct 的每个字段上,可以写上一个tag,该tag 可以通过反射机制获取,常见的使用场景就是序列化和反序列化。

02 方法

Golang中的方法是作用在指定的数据类型上的(即:和指定的数据类型绑定),因此自定义类型,都可以有方法,而不仅仅是struct。

方法的声明和调用

type A struct {
    Num int}func (a A) test(){
    fmt.Println(a.Num)
}

说明

  • func(a A) test(书表示A结构体有一方法,方法名为test
  • (aA)体现 test方法是和A类型绑定的
package mainimport(    "fmt")type Person struct {
    Name string}func (P Person) test({
    fmt.Println("test((name=",p.Name)func main(){    var p Personp.Name = "tom”
    p.test()//调用方法
}

//1) test方法和Person类型绑定
//2) test方法只能通过 Person类型的变量来调用,而不能直接调用,也不能使用其它类型变量来调用
//3) func (p Person) test()....p表示哪个Person变量调用,这个p就是它的副本,这点和函数传参常相似。
//4) p这个名字,有程序员指定,不是固定,比如修改成person也是可以

方法的调用和传参机制原理

  • 在通过一个变量去调用方法时,其调用机制和函数一样
  • 不一样的地方是,变量调用方法时,该变量本身也会作为一个参数传递到方法(如果变量是值类型,则进行值拷贝,如果变量是引用类型,则进行地址拷贝)

方法的注意事项和细节

  • 结构体类型是值类型,在方法调用中,遵守值类型的传递机制,是值拷贝传递方式
  • 如程序员希望在方法中,修改结构体变量的值,可以通过结构体指针的方式来处理
  • Golang中的方法作用在指定的数据类型上的(即:和指定的数据类型绑定),因此自定义类型,都可以有方法,而不仅仅是struct,比如int , float32等都可以有方法
  • 方法的访问范围控制的规则,和函数一样。方法名首字母小写,只能在本包访问,方法首字母大写,可以在本包和其它包访问。
  • 如果一个类型实现了String()这个方法,那么fimt.PrintIn默认会调用这个变量的String()进行输出

类型绑定中值拷贝与地址拷贝

  • 不管调用形式如何,真正决定是值拷贝还是地址拷贝,看这个方法是和哪个类型绑定.
  • 如果是和值类型,比如(p Person),则是值拷贝,如果和指针类型,比如是(p*Person)则是地址拷贝。
  • 地址拷贝可以对绑定类型中的属性进行修改

03 面向对象编程

步骤

  • 声明(定义)结构体,确定结构体名
  • 编写结构体的字段
  • 编写结构体的方法

创建结构体变量时指定字段值

var stu1 =Stu{”小明”,19}var stu3 = Stu{
    Name : "jack",
    Age : 20,
}var stu5 *stu = &stu{"小王",29}var stu7 =&stu{
    Name :“小李”,
    Age :49,
}

04 工厂模式

Golang 的结构体没有构造函数,通常可以使用工厂模式来解决这个问题。

结构体的Student 的首字母S是大写的,如果我们想在其它包创建Student的实例(比如main包)引入model包后,就可以直接创建Student结构体的变量(实例)。但是问题来了,如果首字母是小写的,比如是type student struct ,就不不行了。怎么办?工厂模式来解决。

使用工厂模式实现跨包创建结构体实例(变量)

type student struct{
Name stringscore float64)


fune Newstudent(n string, s float64) *student {return &student{
    Name : n,
    Score : s,
}

05 面向对象编程思想

抽象

我们在前面去定义一个结构体时候,实际上就是把一类事物的共有的属性(字段)和行为(方法)提取出来,形成一个物理模型(结构体)。这种研究问题的方法称为抽象。

封装

Golang仍然有面向对象编程的继承,封装和多态的特性,只是实现的方式和其它OOP语言不一样。

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

封装的理解和好处

  • 隐藏实现细节
  • 提可以对数据进行验证,保证安全合理(Age)

如何体现封装

  • 对结构体中的属性进行封装
  • 通过方法,包实现封装

封装的实现步骤

  • 将结构体、字段(属性)的首字母小写(不能导出了,其它包不能使用,类似private)
  • 给结构体所在包提供一个工厂模式的函数,首字母大写。类似一个构造函数
  • 提供一个首字母大写的Set方法(类似其它语言的public),用于对属性判断并赋值
  • 提供一个首字母大写的Get方法(类似其它语言的public),用于获取属性的

特别说明:

在Golang开发中并没有特别强调封装,这点并不像Java, Golang本身对面向对象的特性做了简化的

type person struct {
    Name string
    age int        
}
func NewPerson(name string) *person {    return &person{
        Name : name,
    }
}
func (p*person) setAge(age int) {   
 if age >0 && age <150 {
        p.age = age
    }else{
        fmt.Print1n("年龄范围不正确..")
    }
}
func (p*person) GetAge() int {    return p.age }

继承

继承可以解决代码复用,让我们的编程更加靠近人类思维。

当多个结构体存在相同的属性(字段)和方法时,可以从这些结构体中抽象出结构体,在该结构体中定义这些相同的属性和方法。

嵌套匿名结构体的基本语法

type Goods struct {
    Name stringPrice int}type Book struct {
    Goods        
    Writer string
}

继承的深入讨论

  • 结构体可以使用嵌套匿名结构体所有的字段和方法,即:首字母大写或者小写的字段、方法,都可以使用。
  • 匿名结构体字段访问可以简化。
b.A.Name = "tom"b.A.age= 19b.A.Sayok()
b.A.hello()


b.Name = "smith"b.age = 20b.sayok()
b.hello()
  • 当结构体和匿名结构体有相同的字段或者方法时,编译器采用就近访问原则访问,如希望访问匿名结构体的字段和方法,可以通过匿名结构体名来区分
b.A.age= 19
  • 结构体嵌入两个(或多个)匿名结构体,如两个匿名结构体有相同的字段和方法(同时结构体本身没有同名的字段和方法),在访问时,就必须明确指定匿名结构体名字,否则编译报错。
  • 如果一个struct 嵌套了一个有名结构体,这种模式就是组合,如果是组合关系,那么在访问组合的结构体的字段或方法时,必须带上结构体的名字。
type D struct i
    aA    
}
  • 嵌套匿名结构体后,也可以在创建结构体变量(实例)时,直接指定各个匿名结构体字段的值
type Goods struct {
    Name string
    Price float64}type Brand struct {
    Name string
    Address string}type TV struct {
    Goods
    Brand
}type TV2 struct {
    *Goods
    *Brand
}

tv := TV{Goods{"电视机001",900.99}, Brand{"海尔","山东"},}
tv2 := TV{
    Goods{
        Price : 88.99,
        Name :"电视机ee2"
    },
    Brand{
        Name :"夏普",
        Address :"北京",
    },
}


tv3 := TV2{ &Goodsf{"电视机03",7000.99},&Brand{"创维","河南"}}
tv4 := TV2{
    &Goods{
        Name :"电视机4",
        Price : 9eee.99,
    }
    &Brand{
        Name :"长虹",
        Address :"四川",
    },
}

多重继承

如一个struct嵌套了多个匿名结构体,那么该结构体可以直接访问嵌套的匿名结构体的字段和方法,从而实现了多重继承。

为了保证代码的简洁性,建议尽量不使用多重继承

接口

在Golang中多态特性主要是通过接口来体现的

interface类型可以定义一组方法,但是这些不需要实现。并且 interface不能包含任何变量。到某个自定义类型要使用的时候,在根据具体情况把这些方法写出来(实现)。

type Usb interface {
    
    Start()
    Stop()
}
  • 接口里的所有方法都没有方法体,即接口的方法都是没有实现的方法。接口体现了程序设计的多态和高内聚低偶合的思想。
  • Golang 中的接口,不需要显式的实现。只要一个变量,含有接口类型中的所有方法,那么这个变量就实现这个接口。因此,Golang 中没有implement这样的关键字

注意事项和细节

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

继承和接口的区别

  • 当A结构体继承了B结构体,那么A结构就自动的继承了B结构体的字段和方法,并且可以直接使用
  • 当A结构体需要扩展功能,同时不希望去破坏继承关系,则可以去实现某个接口即可,因此我们可以认为:实现接口是对继承机制的补充.

实现接口可以看作是对继承的一种补充

接口和继承解决的解决的问题不同

  • 继承的价值主要在于:解决代码的复用性和可维护性。
  • 接口的价值主要在于:设计,设计好各种规范(方法),让其它自定义类型去实现这些方法。
  • 接口比继承更加灵活
  • 接口比继承更加灵活,
    • 继承是满足is - a 的关系
    • 接口只需满足like - a的关系
  • 接口在一定程度上实现代码解耦

多态

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

  • 多态参数
  • 多态数组

类型断言

类型断言,由于接口是一般类型,不知道具体类型,如果要转成具体类型,就需要使用类型断言

var x interface{}var b2 float32 =1.1x=b2    
        
y := x.(float32)
fmt.Printf("y的类型是灯值是=%v", y, y)

在进行类型断言时,如果类型不匹配,就会报 panic,因此进行类型断言时,要确保原来的空接口指向的就是断言的类型.

var x interface{}var b2 float32 =2.1 x = b2if y, ok := x.(float32);ok {
    fmt.Println(" convert success")
    fmt.Printf("y的类型是%T值是=%v",y, y)
}else {
    fmt.Print1n(" convert fai1")
}
fmt.Print1n("继续执行...")