Go核心开发学习笔记(廿三) —— 面向对象编程,工厂模式

面向对象编程

基本步骤对比python

  1. 声明结构体,确定结构体名称 //确认一个类名称: Class xxx():
  2. 确定结构体的字段 //确认类的属性: def init
  3. 编写结构体方法 //确认类的方法: 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())             //这样就可以访问私有字段了
}

面向对象编程思想——抽象

  1. 先来说说自己的观点,如果学过k8s一定发现整个k8s的精髓就是抽象(Abstraction),资源池中抽象出各种类型的资源–>Kind,谷歌家的产品,自然体现在Golang。
  2. 把一类事物的共有的属性和方法提取出来形成一个物理模型,这种研究问题的方法叫做抽象。
  3. 创建模型:开发一个银行账户系统,属性:姓名,账号,余额,方法:存取款,转账,查询余额等。
  4. 面向对象的三大特性:封装,继承,多态,Golang实现方式不同,对方式进行了弱化,不要和其他OOP语言进行强制比较。

封装 encapsulation:

  1. 把抽象出的字段和字段的操作封装在一起,数据被保护在内部,程序的其他包只有通过被授权的操作,才能对字段进行操作。
  2. 封装可以隐藏实现细节,对数据进行验证,保证安全合理性。
  3. Golang的封装就是上述体现,包中的结构体字段就小写,通过小写控制其他包import后对字段的工厂模式后提高操作控制安全性。

封装的实现步骤

  1. 将结构体,字段的首字母小写(不能导出了,其他包不可以import使用,类似java的private)。
  2. 给结构体所在包提供一个工厂模式的首字母大写的函数,类似一个构造函数(针对结构体的被其他包调用的权限设置)。
  3. 提供一个首字母大写的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)
}

继承:

  1. 提取不同结构体的共同属性和方法,写成一个新的结构体定义相同的属性和方法,Golang中不存在父类子类。

  2. 通过嵌入一个匿名结构体,完成松散耦合。

  3. 继承的优点:代码的复用性提高了;代码的扩展性和可维护性提高了。

    举例说明共有属性和引用:

    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()

继承的深入讨论

  1. 新结构体可以使用嵌套匿名结构体所有的字段和方法,即首字母大写或者小写的字段,方法,都可以使用,只限于一个.go文件中,import其他的go中没有尝试。

  2. 简化写法,例如定义字段时候和使用方法时,可以直接省略共有属性结构体的层级:
    <新结构体名称>.<共有属性结构体>.<共有属性结构体字段> == <新结构体名称>.<共有属性结构体字段> // Xiaoxuesheng.Name = “春哥”
    <新结构体名称>.<共有属性结构体>.<共有属性方法名称>() == <新结构体名称>.<共有属性方法名称>() // Xiaoxuesheng.plusbuff() = “霸气护体”

  3. 如果新结构体中有字段名称和共有结构体字段相同的名称,那么就不会采用<新结构体名称>.<共有属性结构体字段>,而是<新结构体名称>.<新结构体字段>,所以按照匹配规则,一旦找到指定字段,一定选择最外层符合条件的第一个字段名称,即就近原则,有符合A.<字段>就不会寻找A.B.<字段>。

  4. 同理,如果一个新结构体嵌入两个共有结构体,两个共有结构体有名称相同的字段,则新结构体使用C.<共有字段>时,编译会报错,这时候必须指定匿名结构体名称来表明赋值,C.A.Name = “狗狗” C.B.Name = “猫猫” ,直接使用C.Name = "xx"必然报错,对于方法同理。

  5. 理解匿名结构体和组合结构体的关系:如果是组合结构体,那么就不是继承了,这时候就不能选择简化写法了,而必须使用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”

  6. 对于继承的或者组合的,在定义变量时可以直接使用嵌套赋值,如果C需要嵌套A,B两个匿名结构体时,可以指针传递 type C struct {A\nB},这样传递效率更高。
    如果取值时,一定要写*c.A.<字段>取整个地址。

  7. 多重继承:不推荐使用,多重继承如果不同匿名结构体有相同字段,则一定要指定<匿名结构体名称>.<字段名称>这种层级关系,相当于使用不同的namespace提供相同的字段。

多态
学习完接口再说,多态大多是通过接口 Interface来实现

发布了49 篇原创文章 · 获赞 18 · 访问量 4004

猜你喜欢

转载自blog.csdn.net/weixin_41047549/article/details/90169862