Golang学习+深入-面向“对象“编程
Posted 杀神lwz
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Golang学习+深入-面向“对象“编程相关的知识,希望对你有一定的参考价值。
目录
一、概述
1、结构体
一个程序就是一个世界,有很多对象(变量)
- Golang也支持面向对象编程(OOP),但是和传统的面向对象编程有区别,并不是纯粹的面向对象语言。所以我们说Golang支持面向对象编程特性是比较准确的。
- Golang没有类(class),Go语言的结构体(struct)和其它编程语言的类(class)有同等的地位。
- Golang面向对象编程非常简洁,去掉了传统OOP语言的继承、方法重载、构造函数和析构函数、隐藏的this指针等等。
- Golang仍然有面向对象编程的继承、封装和多态的特性,只是实现的方式和其他OOP语言不一样,比如继承:Golang没有extends关键字,继承是通过匿名字段来实现。
- Golang面向对象(OOP)很优雅,OOP本身就是语言类型系统(type system)的一部分,通过接口(interface)关联,耦合性低,也非常灵活。在Golang中面向接口编程是非常重要的特性。
package main
import (
"fmt"
)
type Cat struct
Name string
Age int
Color string
func main()
var cat1 Cat
cat1.Name="小白"
cat1.Age=3
cat1.Color="白色"
fmt.Println("cat1=",cat1)
fmt.Println("cat1 Name=",cat1.Name)
结构体是自定义的数据类型,代表一类事物
声明结构体
type 结构体名称 struct
field1 type
field2 type
例:
type Cat struct
Name string
Age int
Color string
- 字段的类型可以为:基本类型,数组或引用类型
- 在创建一个结构体变量后,如果没有给字段赋值,都对应一个零值(默认值)
- 数组的默认值和它的元素类型相关,score[3]int 则为[0,0,0]
- 指针,slice,map的零值都是nil,即还没有分配内存空间
- 不同结构体变量的字段是独立,互不影响,一个结构体变量字段的更改,不影响另外一个。
package main
import (
"fmt"
)
func main()
var p1 Person
fmt.Println("p1=",p1)
p1.Name="小明"
p1.Age=10
p1.slice= make([]int,10)
p1.map1 = make(map[string]string)
p1.map1["k1"] = "v1"
fmt.Println("p1=",p1)
type Person struct
Name string
Age int
map1 map[string]string
slice []int
ptr *int //指针
1.1、创建结构体变量和访问结构体变量
方式1:
var person Person
方式2:
var person Person = Person
例:
package main
import (
"fmt"
)
type Person struct
Name string
Age int
func main()
p := Person"mary",20
fmt.Println(p)
方式3:
var person *Person = new(Person)
(*person).Name="tom" //等价 person.Name="tom" 因为go编译器底层对person.Name做了转化(*person).Name
(*person).Age=30
fmt.Println(*person)
方式4:
var person *Person = &Person//var person *Person = &Person"tom",60
(*person).Name="tom" //等价 person.Name="tom" 因为go编译器底层对person.Name做了转化(*person).Name
person.Name="tom--"
(*person).Age=30
person.Age=10
fmt.Println(*person)
说明:
1.第三种和第四种方式返回的是 结构体指针。
2.结构体指针访问字段的标准方式应该是:(*结构体指针).字段名 如:(*person).Name ="tom"
3.但go做了一个简化,也支持结构体指针.字段名,比如 person.Name = "tom" 。
更加符合程序员使用的习惯,go编译器底层对person.Name做了转化(*person).Name
请问可以这样写吗?
*person.Name:不可以,会报错。因为.的运算符优先级比*高。
1.2、结构体的注意事项和使用细节
- 结构体的所有字段在内存中是连续的
- 结构体是用户单独定义的类型,和其它类型进行转换时需要有完全相同的字段(名字、个数和类型)
package main
import (
"fmt"
)
type A struct
Num int
type B struct
Num int
func main()
var a A
var b B
b = B(a)
fmt.Println(b)
- 结构体进行type重新定义(相当于取别名),Golang认为是新的数据类型,但是相互间可以强转
- struct的每个字段上,可以写上一个tag,该tag可以通过反射机制获取,常见使用场景就是序列化和反序列化。
import "encoding/json"
func Marshal(v interface) ([]byte, error):Marshal函数返回v的json编码。
======================================================
package main
import (
"fmt"
"encoding/json"
)
type C struct
Name string `json:"name"` //`json:"name"`结构体标签
Age int `json:"age"`
Skill string `json:"skill"`
func main()
c :=C"孙悟空",1000,"金箍棒"
cJson,err :=json.Marshal(c) //json.Marshal函数中使用到反射,把tag标签json名称替换掉本来的属性名
if err !=nil
fmt.Println("err=",err)
fmt.Println("cJson",string(cJson))
1.3、创建结构体变量时指定字段值
Golang在创建结构体实例(变量)时,可以直接指定字段的值。
package main
import (
"fmt"
)
type Student struct
Name string
Age int
func main()
var stu1 Student=Student"tom",10
stu2 := Student"tom",20
var stu3 Student=Student
Name:"tom",
Age:30,
stu4 :=Student
Name:"tom",
Age:40,
var stu5 *Student=&Student"stu5",50
var stu6 *Student=&Student
Name:"tom",
Age:60,
fmt.Println(stu1)
fmt.Println(stu2)
fmt.Println(stu3)
fmt.Println(stu4)
fmt.Println(stu5)
fmt.Println(stu6)
1.4、工厂模式
Golang的结构体没有构造函数,通常可以使用工厂模式来解决这个问题。
使用工厂模式实现跨包创建结构体实例(变量)
- 如果model包的 结构体变量首字母大写,引入后,直接使用,没有问题
- 如果model包的 结构体变量首字母小写,引入后,不能直接使用,可以使用工厂模式解决
package model
import (
_ "fmt"
)
type student struct
Name string
Score float64
func NewStudent(n string,s float64) *student
return &student
Name : n,
Score : s,
================================
package main
import (
"fmt"
"model" //model包下的代码我整体拷贝到GO安装环境目录下面,可调用
)
func main()
var stu = model.NewStudent("张无忌",98.0)
fmt.Println(stu) //&张无忌 98
//如果Score改为小写开头score,则在其他包不可以直接访问 //提供一个对外的方法
package model
import (
_ "fmt"
)
type student struct
Name string
score float64
func NewStudent(n string,s float64) *student
return &student
Name : n,
score : s,
//如果Score改为小写开头score,则在其他包不可以直接访问
//提供一个对外的方法
func (s *student) GetScore() float64
return s.score
-----------------
package main
import (
"fmt"
"model"
)
func main()
var stu = model.NewStudent("张无忌",98.0)
fmt.Println(stu.GetScore())//98
1.5、抽象
定义一个结构体的时候,实际上就是把一类事物的共有的属性(字段)和行为(方法)提取出来,形成一个物理模型(模板)。这种研究问题的方法称为抽象。
1.6、面向对象编程三大特性
Golang仍然有面向对象编程的继承,封装和多态的特性,只是实现方式和其他OOP语言不一样。
封装
封装(encapsulation)就是把抽象出的字段和对字段的操作封装在一起,数据被保护在内部,程序的其他包只有通过被授权的操作(方法),才能对字段进行操作。对电视机的操作就是典型的封装。
封装的理解和好处:
- 隐藏实现细节
- 可以对数据进行验证,保证安全合理
如何体现封装:
- 对结构体中的属性进行封装
- 通过方法,包 实现封装
封装的实现步骤
- 将结构体、字段(属性)的首字母小写(不能导出了,其他包不能使用,类似private)
- 给结构体所在包提供一个工厂模式的函数,首字母大写。类似一个构造函数
- 提供一个首字母大写的Set方法(类似其他语言的public),用于对属性判断并赋值
func (var 结构体类型名) SetXxx(参数列表) (返回值列表)
//添加验证逻辑
var.字段=参数
- 提供一个首字母大写的Get方法(类似其他语言的public),用于获取属性的值
func (var 结构体类型名) GetXxx()
return var.字段
- 在Golang开发中并没有特别强调封装,这点并不像Java。
继承
继承可以解决代码复用,让我们的编程更加靠近人类思维
当多个结构体存在相同的属性(字段)和方法时,可以从这些结构体中抽象出结构体,在该结构体中定义这些相同的属性和方法。
其他的结构体不需要重新定义这些属性和方法,只需要嵌套一个共通定义的匿名结构体即可。
也就是说,在Golang中,如果一个struct嵌套了另一个匿名结构体,那么这个结构体可以直接访问匿名结构体的字段和方法,从而实现了继承特性。
type Goods struct
Name string
Price int
type Book struct
Goods //这里就是嵌套匿名结构体Goods
Write string
- 结构体可以使用嵌套匿名结构体所有的字段和方法,即:首字母大写或者小写的字段、方法,都可以使用
- 匿名结构体访问可以简化
var book Book
book.Goods.Name="tom"
↓
book.Name="tom"
- 当结构体和匿名结构体有相同的字段或者方法时,编译器采用就近访问原则访问,如果希望访问匿名结构体的字段和方法,可以通过匿名结构体名来区分
- 结构体嵌入两个(或多个)匿名结构体,如两个匿名结构体有相同的字段或方法(同时结构体本身没有同名的字段和方法),在访问时,就必须明确指定匿名结构体名字,否则编译报错。
- 如果一个struct嵌套了一个有名结构体,这种模式就是组合,如果是组合关系,那么在访问组合的结构体的字段或方法时,必须带上结构体的名字。
- 嵌套匿名结构体后,也可以在创建结构体变量(实例)时,直接指定各个匿名结构体字段的值。
多态
变量(实例)具有多种形态。面向对象的第三大特征,在Go语言,多态特征是通过接口实现的。可以按照统一的接口来调用不同的实现。这时接口变量就呈现不同的形态。
参考下面接口中的Usb案例
类型断言
类型断言,由于接口是一般类型,不知道具体类型,如果要转成具体类型,就需要使用类型断言。
package main
import (
"fmt"
)
func main()
var t float32
var x interface
x=t
_,ok :=x.(float32)
if ok ==true
fmt.Println("success")
else
fmt.Println("fail")
2、方法
Golang中的方法是作用在指定的数据类型上的(即:和指定的数据类型绑定),因此自定义类型,都可以有方法,而不仅仅是struct。
方法的声明和调用
func (变量名 type) methodName(参数列表) (返回值列表)
方法体
return 返回值
1.变量名 type:表示这个方法和type这个类型进行绑定,或者该方法作用于type类型。
2.返回值列表:表示返回的值,可以多个
3.return 语句不是必须的
type A struct
Num int
func (a A) test()
fmt.Println(a.Num)
例:
var t A
t.test()
方法的调用和传参机制和函数基本一样,不一样的地方是方法调用时,会将调用方法的变量,当作实参也传递给方法。
package main
import (
"fmt"
)
type Circle struct
radius float64
func (c Circle) area() float64
return 3.14 * c.radius * c.radius
func main()
var c Circle
c.radius =2.0
res := c.area()
fmt.Println("res=",res)
2.1、方法注意事项和细节讨论
- 结构体类型是值类型,在方法调用中,遵守值类型的传递机制,是值拷贝传递方式
- 如程序员希望在方法中,修改结构体变量的值,可以通过结构体指针的方式来处理
- Golang中的方法作用在指定的数据类型上的(即:和指定的数据类型绑定),因此自定义类型,都可以有方法,而不仅仅是struct。比如:int,float32等也可以定义方法
- 方法的访问范围控制的规则,和函数一样。方法名首字母小写,只能在本包访问,方法首字母大写,可以在本包和其他包访问
- 如果一个变量实现了String()这个方法,那么fmt.Println默认会调用这个变量的String()进行输出
方法和函数的区别
- 调用方式不一样
- 函数的调用方式:函数名(实参列表)
- 方法的调用方式:变量.方法名(实参列表)
- 对于普通函数,接收者为值类型时,不能将指针类型的数据直接传递,反之亦然
- 对于方法(如struct的方法),接收者为值类型时,可以直接用指针类型的变量调用方法,反过来同样也可以
3、接口(interface)
USB插槽就是现实中的接口。
Golang中 多态特性主要是通过接口来体现的。
interface类型可以定义一组方法,但是这些不需要实现。并且interface不能包含任何变量。到某个自定义类型要使用的时候,再根据具体情况把这些方法写出来。
基本语法:
type 接口名 interface
method1(参数列表) 返回值列表
method2(参数列表) 返回值列表
...
- 接口里的所有方法都没有方法体,即接口的方法都是没有实现的方法。接口体现了程序设计的多态和高内聚低耦合的思想。
- Golang中的接口,不需要显式的实现。只要一个变量,含有接口类型中的所有方法,那么这个变量就实现这个接口。因此,Golang中没有implement这样的关键字。
- 实现了接口就是指实现了接口声明的所有方法
package main
import (
"fmt"
)
type Usb interface
Start()
Stop()
type Phone struct
func (phone Phone) Start()
fmt.Println("手机开始工作...")
func (phone Phone) Stop()
fmt.Println("手机停止工作...")
type Camera struct
func (camera Camera) Start()
fmt.Println("相机开始工作...")
func (camera Camera) Stop()
fmt.Println("相机停止工作...")
type Computer struct
//实现了Usb接口就是指实现了Usb接口声明的所有方法
func (computer Computer) working(usb Usb)
usb.Start()
usb.Stop()
func main()
computer :=Computer
phone :=Phone
camera := Camera
computer.working(phone)
computer.working(camera)
3.1、注意事项和细节
- 接口本身不能创建实例,但是可以指向一个实现了该接口的自定义类型的变量(实例)
- 接口中所有的方法都没有方法体,即都是没有实现的方法
- 在Golang中,一个自定义类型需要将某个接口的所有方法都实现,我们说这个自定义类型实现了该接口
- 一个自定义类型只有实现了某个接口,才能将该自定义类型的实例(变量)赋给接口类型。
- 只要是自定义数据类型,就可以实现接口,不仅仅是结构体类型。
- 一个自定义类型可以实现多个接口
- Golang接口中不能有任何变量
- 一个接口(比如A接口)可以继承多个别的接口(比如B、C接口),这时如果要实现A接口,也必须将B、C接口的方法也全部实现。
- interface类型默认是一个指针(引用类型),如果没有对interface初始化就使用,那么会输出nil
- 空接口interface没有任何方法,所以所有类型都实现了空接口
3.2、接口排序
import "sort"
func Sort(data Interface):Sort排序data,需要实现接口下的三个方法
type Interface interface
// Len方法返回集合中的元素个数
Len() int
// Less方法报告索引i的元素是否比索引j的元素小
Less(i, j int) bool
// Swap方法交换索引i和j的两个元素
Swap(i, j int)
=========================================
package main
import (
"fmt"
"sort"
)
func main()
var intSlice = []int0,-1,10,7,90,23
sort.Ints(intSlice)
fmt.Println(intSlice)
3.3、接口VS继承
- 实现接口可以看作是对继承的一种补充。
- 接口和继承解决的问题不同
- 继承的价值主要在于:解决代码的复用性和可维护性。
- 接口的价值在于:设计,设计好各种规范(方法),让其它自定义类型去实现这些方法
- 接口比继承更加灵活
- 接口比继承更加灵活,继承是满足 is - a的关系,而接口只需满足like - a的关系
- 接口在一定程度上实现代码解耦
干我们这行,啥时候懈怠,就意味着长进的停止,长进的停止就意味着被淘汰,只能往前冲,直到凤凰涅槃的一天!
Golang面向对象编程—方法
方法
基本介绍
在某些情况下,我们需要声明(定义)方法。比如Person结构体:除了有一些字段外,Person结构体还有一些行为比如:可以说话,跑步,通过学习,还可以做算术题。这是就要用方法才能完成。
Golang中的方法是作用在指定的数据类型上的(即:和指定的数据类型绑定),因此自定义类型都可以有方法,而不仅仅是struct。
方法的声明
func (receiver type)methodName(参数列表)(返回值列表)
方法体
return 返回值
说明:
- 参数列表:表示方法输入
- receiver type:表示这个方法和type这个类型进行绑定,或者说该方法作用于type.
- receiver type:type可以是结构体,也可以是其他的自定义类型。
- receiver:就是type类型的一个变量(实例),比如:Person结构体的一个变量。
- 返回值列表:表示返回的值,可以是多个。
- 方法主体:表示为了实现某一功能的代码块。
- return 语句不是必须的。
方法快速入门案例
package main
import(
"fmt"
)
type Person struct
name string
age int
//给Person结构体添加一个方法,输出个人基本信息
func (p Person) introduce()
fmt.Printf("大家好,我是%v,今年%v岁\\n",p.name,p.age)
//给Person结构体添加一个cal方法,可以接受一个数n,计算1+2+3+...+n的值
func (p Person) cal(n int)
res := 0
for i := 1; i <= n; i++
res += i
fmt.Println(p.name,"计算的结果 =", res)
//给person结构体添加getSum方法,可以计算两个数的和,并返回结果
func (p Person) getSum(n1 int, n2 int) int
return n1 + n2
func main()
var p Person
p.name = "Casey"
p.age = 21
//方法的调用
p.introduce()
p.cal(100)
res := p.getSum(10,30)
fmt.Println("res =", res)
运行结果:
方法的调用和传参机制原理
方法的调用和传参机制和函数基本一样,不一样的地方是方法调用时,会将调用方法的变量,当做实参也传递给方法。下面举例说明:
案例1:
getSum方法的执行过程和说明。
//给person结构体添加getSum方法,可以计算两个数的和,并返回结果
func (p Person) getSum(n1 int, n2 int) int
return n1 + n2
func main()
var p Person
n1 := 10
n2 := 20
res := p.getSum(n1,n2)
fmt.Println("res =", res)
说明:
- 在通过一个变量去调用方法时,其调用机制和函数一样。
- 不一样的地方是,变量调用方法时,该变量本身也会作为一个参数传递到方法(如果变量时值类型,则进行值拷贝,如果是引用类型,则进行地址拷贝)
方法和函数的区别
- 调用方式不一样。函数的调用方式:函数名(实参列表)。方法的调用方式:变量.方法名(实参列表)
- 对于普通函数,接收者为值类型时,不能讲指针类型的数据直接传递,反之亦然。
//不能接收指针类型的数据
func test1(p Person)
fmt.Println(p.name)
func test2(p *Person)
fmt.Println(p.name)
func main()
p := Person
name : "Tome",
age : 18,
test1(p)
test2(&p)
- 对于方法(如struct的方法),接收者为值类型是,可以直接用指针类型的变量调用方法。
//不能接收指针类型的数据
func (p Person)test1()
fmt.Println(p.name)
func (p *Person) test2()
fmt.Println(p.name)
func main()
p := Person
name : "Tom",
age : 18,
p.test1()
p.test2()
//从形式上是传入地址,但本质上仍然是值拷贝
(&p).test1()
(&p).test2()
方法使用的注意事项
- 结构体类型是值类型,在方法调用中,遵守值类型的传递机制,是值拷贝传递方法。
- Golang中的方法作用在指定的数据类型上(即:和指定的数据类型绑定),因此自定义类型,都可以有方法,而不仅仅是struct,比如int float32等都可以有方法。
type integer int
func (i integer) print()
fmt.Println("i =",i)
func (i *integer) change()
*i = *i + 1
func main()
var i integer = 10
i.print()
i.change()
fmt.Println("i =",i)
运行结果:
- 方法的访问范围控制的规则,和函数一样。方法名首字母小写,只能在本包访问,方法首字母大写,可以在本包和其它包访问。
- 如果一个类型实现了String()这个方法,那么fmt.Prinfln默认会调用这个变量的String()进行输出。
package main
import(
"fmt"
)
type Person struct
name string
age int
func (p Person) String()string
str := fmt.Sprintf("name = %v,age = %v\\n",p.name,p.age)
return str
func main()
p := Person
name : "casey",
age : 21,
fmt.Println(p)
运行结果:
以上是关于Golang学习+深入-面向“对象“编程的主要内容,如果未能解决你的问题,请参考以下文章