面向对象编程
基本步骤对比python
- 声明结构体,确定结构体名称 //确认一个类名称: Class xxx():
- 确定结构体的字段 //确认类的属性: def init
- 编写结构体方法 //确认类的方法: def xx():
最简单示例:写一个球员结构体,包含name string,gender string,age int,num int,ability float64字段;编写一个方法,调用后返回球员所有的信息
package main
import "fmt"
type player struct {
name string
gender string
age int
rolenum int
ability int
}
func (p *player) callback() string {
res := fmt.Sprintf("定义球员信息如下:\n姓名:%v\n性别:%v\n年龄:%d\n球衣号码:%d\n能力值:%d\n",p.name,p.gender,p.age,p.rolenum,p.ability) //定义字符串打印的可以传递变量
return res
}
func main() {
var p1 *player = &player{"durant","male",30,35,94} //后续工程中优秀做法是把结构体指针返还给变量,因为指针传递值很小,效率高
res := p1.callback()
fmt.Println(*p1) //由于var是一个指针,所以当查看实例的时候前面会加一个&符,去掉只需要打印*p1取值即可
fmt.Println(res)
}
Golang中没有构造函数,使用工厂模式解决问题
一个非main包中定义了一个struct,但是首字母小写,struct字段也是小写,想在main()中调用这个结构体和字段,一般无法完成,选择工厂模式解决。
写一个常规方式无法导入的包:
package utils //创建一个utils包
type students struct { //这个结构体是小写,无法用于其他包导入
Name string
Age int
score float64 //这个结构体的字段是小写,无法用于其他包导入
}
func Student(n string,a int) *students { //写一个方法,装饰一下,方法本身首字母大写,所以可以被其他包导入,解决结构体小写问题
return &students{ //传递参数和结构体字段一模一样,返回值是结构体指针变量,存放在堆中,共享的,大家都可以使用
Name : n,
Age : a ,
}
func (s *students) Getscore() { //写一个方法,装饰一下,方法本身首字母大写,所以可以被其他包导入,解决结构体字段小写问题
return (*s).score //直接将utils函数中的score返回给一个首字母大写函数,在main包中再调用这个函数获得值
}
在main函数中导入utils包:
package main
import (
"fmt"
"utils"
)
func main() {
var stu = utils.Student("zhaolu",20) //使用函数传递两个参数,stu接收函数的返回值也是一个指针
fmt.Println(*stu) //为了查看字段值,需要用取值符取得变量。
fmt.Println(stu.Getscore()) //这样就可以访问私有字段了
}
面向对象编程思想——抽象
- 先来说说自己的观点,如果学过k8s一定发现整个k8s的精髓就是抽象(Abstraction),资源池中抽象出各种类型的资源–>Kind,谷歌家的产品,自然体现在Golang。
- 把一类事物的共有的属性和方法提取出来形成一个物理模型,这种研究问题的方法叫做抽象。
- 创建模型:开发一个银行账户系统,属性:姓名,账号,余额,方法:存取款,转账,查询余额等。
- 面向对象的三大特性:封装,继承,多态,Golang实现方式不同,对方式进行了弱化,不要和其他OOP语言进行强制比较。
封装 encapsulation:
- 把抽象出的字段和字段的操作封装在一起,数据被保护在内部,程序的其他包只有通过被授权的操作,才能对字段进行操作。
- 封装可以隐藏实现细节,对数据进行验证,保证安全合理性。
- Golang的封装就是上述体现,包中的结构体字段就小写,通过小写控制其他包import后对字段的工厂模式后提高操作控制安全性。
封装的实现步骤
- 将结构体,字段的首字母小写(不能导出了,其他包不可以import使用,类似java的private)。
- 给结构体所在包提供一个工厂模式的首字母大写的函数,类似一个构造函数(针对结构体的被其他包调用的权限设置)。
- 提供一个首字母大写的Set方法(类似java的public),用于对属性判断并赋值(针对构造体字段被其他包调用的权限设置)。
第三条重点说明下,在增加Set方法时,可以对被调用的构造体字段进行业务逻辑和数值范围限制,其他包再调用结构体字段时必须遵循Set方法条件限制。
或者增加Get方法,return需要的字段或者值,需要main()中相应的变量接收。
举例说明:可以做数据脱敏,建立一个Person结构体,保证不能直接访问age,首先建立一个demo包
package demo
import "fmt"
type person struct {
Name string
age int
}
func Person(n string) *person { //因为Name字段为大写,所以只需要通过工厂模式提供的Person函数就直接访问
return &person{
Name : n,
}
}
func (p *person) SetAge(age int) { //因为age字段为小写,所以需要构造方法,提供设置和访问方法,不可以直接使用<引入包结构体变量>.<结构体字段>的方式使用
if age > 0 || age < 150 {
p.age = age
} else {
fmt.Println("输入的年龄不符合要求,默认为18岁")
p.age = 18
}
}
func (p *person) GetAge() int {
return p.age
}
main()中使用set和get方法进行设置和调用,而无法直接使用<引入包结构体变量>.<结构体字段>的方式使用
package main
import (
"demo"
"fmt"
)
func main() {
var p = demo.Person("马云")
p.SetAge(50)
fmt.Println(p.GetAge())
fmt.Println(*p)
fmt.Println(p.Name)
}
继承:
-
提取不同结构体的共同属性和方法,写成一个新的结构体定义相同的属性和方法,Golang中不存在父类子类。
-
通过嵌入一个匿名结构体,完成松散耦合。
-
继承的优点:代码的复用性提高了;代码的扩展性和可维护性提高了。
举例说明共有属性和引用:
type Students struct { //定义一个共有结构体属性:学生,学生一定都有姓名,年龄和学号 Name string Age int SerialNo int } type Xiaoxuesheng struct { Students //定义一个结构体是小学生,引入共有结构体属性,也就拥有了姓名,年龄和学号 angery string //小学生有一个独有属性:小学生之怒,如果需要引入其他结构体,除了共有属性之外,自由定义独有属性 }
举例说明共有方法和引用:
func (stu *Students) <方法名>() {…,stu.Name,stu.Age} //这样在其他结构体嵌入Students结构体后也可以使用本方法了。
为共有结构体属性中的字段赋值:
<新结构体名称>.<共有属性结构体>.<共有属性结构体字段> //例如为小学生定义名字,Xiaoxuesheng.Students.Name = “春哥”
使用共有结构体的方法:
<新结构体名称>.<共有属性结构体>.<共有属性方法名称>() //例如增加一个共有方法考试,examing(), 小学生考试为 Xiaoxuesheng.Students.examing()
继承的深入讨论
-
新结构体可以使用嵌套匿名结构体所有的字段和方法,即首字母大写或者小写的字段,方法,都可以使用,只限于一个.go文件中,import其他的go中没有尝试。
-
简化写法,例如定义字段时候和使用方法时,可以直接省略共有属性结构体的层级:
<新结构体名称>.<共有属性结构体>.<共有属性结构体字段> == <新结构体名称>.<共有属性结构体字段> // Xiaoxuesheng.Name = “春哥”
<新结构体名称>.<共有属性结构体>.<共有属性方法名称>() == <新结构体名称>.<共有属性方法名称>() // Xiaoxuesheng.plusbuff() = “霸气护体” -
如果新结构体中有字段名称和共有结构体字段相同的名称,那么就不会采用<新结构体名称>.<共有属性结构体字段>,而是<新结构体名称>.<新结构体字段>,所以按照匹配规则,一旦找到指定字段,一定选择最外层符合条件的第一个字段名称,即就近原则,有符合A.<字段>就不会寻找A.B.<字段>。
-
同理,如果一个新结构体嵌入两个共有结构体,两个共有结构体有名称相同的字段,则新结构体使用C.<共有字段>时,编译会报错,这时候必须指定匿名结构体名称来表明赋值,C.A.Name = “狗狗” C.B.Name = “猫猫” ,直接使用C.Name = "xx"必然报错,对于方法同理。
-
理解匿名结构体和组合结构体的关系:如果是组合结构体,那么就不是继承了,这时候就不能选择简化写法了,而必须使用B.A.<字段> = “xx”
举例说明:
(1) type A struct {…} type B struct { A …} : 匿名结构体,可以使用简化,B.A.<A字段> == B.<A字段> b.A.Name == b.Name
(2) type A struct {…} type B struct { a A …} : 组合结构体,不可以使用简化,B.A.<A字段> != B.<A字段> b.a.Name = “xx” -
对于继承的或者组合的,在定义变量时可以直接使用嵌套赋值,如果C需要嵌套A,B两个匿名结构体时,可以指针传递 type C struct {A\nB},这样传递效率更高。
如果取值时,一定要写*c.A.<字段>取整个地址。 -
多重继承:不推荐使用,多重继承如果不同匿名结构体有相同字段,则一定要指定<匿名结构体名称>.<字段名称>这种层级关系,相当于使用不同的namespace提供相同的字段。
多态
学习完接口再说,多态大多是通过接口 Interface来实现