结构体、工厂模式、继承封装

[toc]

😶‍🌫️go语言官方编程指南:https://pkg.go.dev/stdopen in new window

go语言的官方文档学习笔记很全,推荐去官网学习

😶‍🌫️我的学习笔记:github: https://github.com/3293172751/golang-rearnopen in new window


区块链技术(也称之为分布式账本技术),是一种互联网数据库技术,其特点是去中心化,公开透明,让每一个人均可参与的数据库记录

❤️💕💕关于区块链技术,可以关注我,共同学习更多的区块链技术。博客http://nsddd.topopen in new window


面对对象编程

面对对象的编程步骤:

  1. 声明结构体,确定结构体名
  2. 编写结构体字段
  3. 编写结构体方法

结构体

结构体创建💡简单的一个案例如下:
/*
 * @Description:结构体
 * @Author: xiongxinwei 3293172751nss@gmail.com
 * @Date: 2022-10-04 21:37:41
 * @LastEditTime: 2022-10-24 18:44:31
 * @FilePath: \code\go-super\20-main.go
 * @Github_Address: https://github.com/3293172751/cs-awesome-Block_Chain
 * Copyright (c) 2022 by xiongxinwei 3293172751nss@gmail.com, All Rights Reserved. @blog: http://nsddd.top
 */
package main

import (
	"fmt"
)

type Person struct {
	Name  string
	Age   int
	Email string
}

func main() {
	var p1 Person
	p1.Name = "Tom"
	p1.Age = 20
	p1.Email = "3293172751@qq.com"
	fmt.Println("p1=", p1)

	p2 := Person{"Jack", 30, "xiongxinwei@mail.com"}
	fmt.Println("p2=", p2)

	p3 := &Person{Name: "Mary", Age: 40, Email: "cub@nsddd.top"}
	fmt.Println("p3=", p3)

	p4 := new(Person)
	p4.Name = "Mike"
	p4.Age = 50
	p4.Email = "xxw@nsddd.top"
	fmt.Println("p4=", p4)

	fmt.Println("p1.Email =", p1.Email)
	fmt.Println("p2.Email =", p2.Email)
	fmt.Println("p3.Email =", p3.Email)
	fmt.Println("p4.Email =", p4.Email)
}

🚀 编译结果如下:

[Running] go run "d:\文档\最近的\awesome-golang\docs\code\go-super\20-main.go"
p1= {Tom 20 3293172751@qq.com}
p2= {Jack 30 xiongxinwei@mail.com}
p3= &{Mary 40 cub@nsddd.top}
p4= &{Mike 50 xxw@nsddd.top}
p1.Email = 3293172751@qq.com
p2.Email = xiongxinwei@mail.com
p3.Email = cub@nsddd.top
p4.Email = xxw@nsddd.top

结构体的值改变:

	p1.Email = "x@nsddd.top" //修改 p1 的 Email
	fmt.Println("p1.Email =", p1.Email) //p1.Email = x@nsddd.top

案例联系:

package main

import (
	"fmt"	
)

/*
学生案例:
编写一个Student结构体,包含name、gender、age、id、score字段,分别为string、string、int、int、float64类型。
结构体中声明一个say方法,返回string类型,方法返回信息中包含所有字段值。
在main方法中,创建Student结构体实例(变量),并访问say方法,并将调用结果打印输出。
*/
type Student struct {
	name string
	gender string
	age int
	id int
	score float64
}

func (student *Student) say()  string {

	infoStr := fmt.Sprintf("student的信息 name=[%v] gender=[%v], age=[%v] id=[%v] score=[%v]",
		student.name, student.gender, student.age, student.id, student.score)

	return infoStr
}

/*
1)编程创建一个Box结构体,在其中声明三个字段表示一个立方体的长、宽和高,长宽高要从终端获取
2)声明一个方法获取立方体的体积。
3)创建一个Box结构体变量,打印给定尺寸的立方体的体积
*/
type Box struct {
	len float64
	width float64
	height float64
}

//声明一个方法获取立方体的体积
func (box *Box) getVolumn() float64 {
	return box.len * box.width * box.height
}


// 景区门票案例
// 一个景区根据游人的年龄收取不同价格的门票,比如年龄大于18,收费20元,其它情况门票免费.
// 请编写Visitor结构体,根据年龄段决定能够购买的门票价格并输出

type Visitor struct {
	Name string
	Age int
}

func (visitor *Visitor) showPrice() {
	if visitor.Age >= 90 || visitor.Age <=8 {
		fmt.Println("考虑到安全,就不要玩了")
		return 
	}
	if visitor.Age > 18 {
		fmt.Printf("游客的名字为 %v 年龄为 %v 收费20元 \n", visitor.Name, visitor.Age)
	} else {
		fmt.Printf("游客的名字为 %v 年龄为 %v 免费 \n", visitor.Name, visitor.Age)
	}
}



func main() {
	//测试
	//创建一个Student实例变量
	var stu = Student{
		name : "tom",
		gender : "male",
		age : 18,
		id : 1000,
		score : 99.98,
	}
	fmt.Println(stu.say())

	//测试代码
	var box Box
	box.len = 1.1
	box.width = 2.0
	box.height = 3.0
	volumn := box.getVolumn()
	fmt.Printf("体积为=%.2f\n", volumn)
    /*格式化输出,保留两位小数点*/

	//测试
	var v Visitor
	for {
		fmt.Println("请输入你的名字")
		fmt.Scanln(&v.Name)
		if v.Name == "n" {
			fmt.Println("退出程序....")
			break
		}
		fmt.Println("请输入你的年龄")
		fmt.Scanln(&v.Age)
		v.showPrice()

	}
}

🚀 编译结果如下:

[root@mail golang]# go build -o main main.go 
[root@mail golang]# ./main
student的信息 name=[tom] gender=[male], age=[18] id=[1000] score=[99.98]
体积为=6.60请输入你的名字
张三
请输入你的年龄
19
游客的名字为 张三 年龄为 19 收费20元 
请输入你的名字
李四
请输入你的年龄
8
考虑到安全,就不要玩了
请输入你的名字
n
退出程序....

指定变量值

Golang在创建结构体时候,可以直接指定字段值

package main

import (
	"fmt"	
)
type Stu struct {
	Name string
	Age int
}
func main() {
    /* 方法 */
}

1. 在创建结构体变量时,把字段名和字段值写在一起, 这种写法,就不依赖字段的定义顺序

var stu3 = Stu{
		Name :"jack",
		Age : 20,
	}
stu4 := Stu{
	Age : 30,
	Name : "mary",
}
fmt.Println(stu1, stu2, stu3, stu4)

2. 在创建结构体变量时,就直接指定字段的值,顺序不可颠倒

var stu1 = Stu{"小明", 19} // stu1---> 结构体数据空间
stu2 := Stu{"小明~", 20}

✍️ 可以使用结构体指针(重要)

返回的是一种指针类型

3. 返回结构体的指针类型(!!!)

var stu5 *Stu = &Stu{"小王", 29}  
/*或*/
stu6 := &Stu{"小王~", 39}

在结构体中 stu5--> 地址 ---> 结构体数据[xxxx,xxx]

4. 创建结构体指针变量时,把字段名和字段值写在一起, 这种写法,就不依赖字段的定义顺序

var stu7 = &Stu{
	Name : "小李",
	Age :49,
}
stu8 := &Stu{
	Age :59,
	Name : "小李~",
}

🚀 编译结果如下:

fmt.Println(*stu5, *stu6, *stu7, *stu8)   //取值
fmt.Println(stu5, stu6, stu7, stu8)   //取地址

结构体方法

结构体是值类型

💡简单的一个案例如下:

/*
 * @Description:结构体的使用
 * @Author: xiongxinwei 3293172751nss@gmail.com
 * @Date: 2022-10-04 21:37:41
 * @LastEditTime: 2022-10-24 18:53:16
 * @FilePath: \code\go-super\21-main.go
 * @Github_Address: https://github.com/3293172751/cs-awesome-Block_Chain
 * Copyright (c) 2022 by xiongxinwei 3293172751nss@gmail.com, All Rights Reserved. @blog: http://nsddd.top
 */
package main

import "fmt"

type Person struct {
	Name string
	Age  int
}

type PersonList struct {
	Persons []Person
}

func main() {
	person := Person{
		Name: "Tom",
		Age:  20,
	}
	fmt.Println("person=", person)
	persone2 := person
	persone2.Name = "Jack"
	fmt.Println("person=", persone2)
}

🚀 编译结果如下:

[Running] go run "d:\文档\最近的\awesome-golang\docs\code\go-super\21-main.go"
person= {Tom 20}
person= {Jack 20}

工厂模式

Golang中没有构造函数,通常可以用工厂模式来解决问题

当我们的结构体首字母是大写的可以在其他包使用这个结构体,那么如果我们希望小写的也能在其他包使用,此时就需要工厂模式来解决,使用工厂模式实现挎包访问结构体实例

我先使用vim查看下文件路径

image-20221003164439481

使用大写字母直接访问包中的结构体

image-20221003164451431

如果student结构体首字母是小写的,只能在model中使用,此时通过工厂模式解决

在model包中创建一个方法,返回指针类型

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

在main包中使用

func main(){
	//首字母小写使用方法
	var stu = model.NewStudent("tom",88.8)
    /*stu是指向结构体的指针*/
	fmt.Println(*stu)
    fmt.Println("name=",stu.Name,"Score=",stu.Score)
}

这种方法就被称为工厂模式

如果 score 是一个小写的,在其他包不可以直接访问,怎么样访问它呢?

我们可以再加入提供一个方法

func (s *student) GetScore() float64{
    return (*s.score)   //ok,*可以省略
}
/*main访问*/
fmt.Println(stu.GetScore())

抽象

面对对象的思想可以简化为一种抽象的模型,把一类事物的共有属性(字段)和方法提取出来,形成一个物理模型(模板),这种研究问题的方法称之为抽象

银行存取款

package main

import (
	"fmt"
)
//定义一个结构体Account
type Account struct {
	AccountNo string
	Pwd string
	Balance float64
}

//1. 方法 == 存款
func (account *Account) Deposite(money float64, pwd string)  {
	//看下输入的密码是否正确
	if pwd != account.Pwd {
		fmt.Println("你输入的密码不正确")
		return 
	}

	//看看存款金额是否正确
    if money <= 0 {
		fmt.Println("你输入的金额不正确")
		return 
	}
	account.Balance += money
	fmt.Println("存款成功~~")
}

//取款
func (account *Account) WithDraw(money float64, pwd string)  {
	//看下输入的密码是否正确
	if pwd != account.Pwd {
		fmt.Println("你输入的密码不正确")
		return 
	}
	//看看取款金额是否正确
	if money <= 0  || money > account.Balance {
        /* 或者money大于你的余额,,没办法取出*/
		fmt.Println("你输入的金额不正确")
		return 
	}
	account.Balance -= money
	fmt.Println("取款成功~~")
}

//查询余额query
func (account *Account) Query(pwd string)  {
	//看下输入的密码是否正确
	if pwd != account.Pwd {
		fmt.Println("你输入的密码不正确")
		return 
	}
	fmt.Printf("你的账号为=%v 余额=%v \n", account.AccountNo, account.Balance)

}
func main() {
	account := Account{
		AccountNo : "gs1111111",
		Pwd : "666666",
		Balance : 100.0,
	}
	//这里可以做的更加灵活,就是让用户通过控制台来输入命令...
	//菜单....
	account.Query("666666")
	account.Deposite(200.0, "666666")
	account.Query("666666")
	account.WithDraw(150.0, "666666")
	account.Query("666666")
}

🚀 编译结果如下:

[root@mail golang]# go run Account.go 
你的账号为=gs1111111 余额=100 
存款成功~~
你的账号为=gs1111111 余额=300 
取款成功~~
你的账号为=gs1111111 余额=150 

面对对象特征

封装:把抽象出的字段和对字段的操作,封装在一起,数据保存在内部

比如说上面取款过程,保证了数据合理性:

func (account *Account) WithDraw(money float64, pwd string)  {

	//看下输入的密码是否正确
	if pwd != account.Pwd {
		fmt.Println("你输入的密码不正确")
		return 
	}
	//看看取款金额是否正确
	if money <= 0  || money > account.Balance {
        /* 或者money大于你的余额,,没办法取出*/
		fmt.Println("你输入的金额不正确")
		return 
	}
	account.Balance -= money
	fmt.Println("取款成功~~")
}

封装

main包

package main
import (
	"fmt"
	"/c/golang/chapter11/encapsulate/model"
)

func main() {
	p := model.NewPerson("smith")   //工厂模式
	p.SetAge(18)    //年龄方法
	p.SetSal(5000)  //薪水
	fmt.Println(p)   
	fmt.Println(p.Name, " age =", p.GetAge(), " sal = ", p.GetSal()) //年龄需要用到方法
}

madel包

package model
import "fmt"

type person struct {     //小写,不能访问person
	Name string
	age int   			//其它包不能直接访问..
	sal float64
}

//写一个工厂模式的函数,相当于构造函数  -- 访问person
func NewPerson(name string) *person {
    /*if....*/
	return &person{
		Name : name,
	}
}

//为了访问age 和 sal 我们编写一对SetXxx的方法和GetXxx的方法
func (p *person) SetAge(age int) {
	if age >0 && age <150 {
		p.age = age
	} else {
		fmt.Println("年龄范围不正确..")
		//给程序员给一个默认值
	}
}

func (p *person) GetAge() int {
	return p.age
}


func (p *person) SetSal(sal float64) {
	if sal >= 3000 && sal <= 30000 {
		p.sal = sal
	} else {
		fmt.Println("薪水范围不正确..")	
	}
}

func (p *person) GetSal() float64 {
	return p.sal
}

提示

可以看到Go语言的工厂模式和get、set方法和Java的面对对象很接近

继承

面对对象的特性可以解决代码的复用

对小学生考试成绩的设置

package main
import "fmt"
type Pupil struct{
    Name string
    Age int
    Score int
}

/*显示成绩  - - 方法*/
func(p *Pupil) showInfo(){
    fmt.Printf("学生名 = %v, 年龄 = %v 成绩 = %v",p.Name,p.Age,p.Score)
}

/*录入分数*/
func(p *Pupil) SetScore(score int){
    if score > 100 ||score < 10{
        fmt.Println("请输入正确的范围")
    	return
    }
    p.Score = score
}

/*显示状态*/
func (p *Pupil) tesing(){
    fmt.Println("小学生正在考试")
}

func main(){
    p := Pupil{
        Name : "tom",
        Age : 10,
    }
    p.tesing()
    p.SetScore(100)
    p.showInfo()
}

🚀 编译结果如下:

[root@mail golang]# go run Account.go 
小学生正在考试
请输入小学生成绩:
102
请输入正确的范围
学生名 = tom, 年龄 = 10 成绩 = 0小学生正在考试

此时如果还有大学生的话,我们需要再创建一个结构体Graduate,复制一份方法出来,就会出现大量的代码冗余,此时需要用到继承。

继承可以解决代码复用问题,使编程更加靠近人类的思考思维。

在Golang中通过了匿名结构体来实现了继承特性。

image-20220114145510709

也就是说,在Golang中,如果一个struct嵌套了另一个匿名结构体,那么这个结构体可以直接访问你们结构体的字段和方法,从而实现了继承特性

type Good struct{
	Name string
	Price int
}
type Book struct{
	Goods //这里就是嵌套匿名结构体Goods
	Writer string
}

我们对上面学生的案例进行继承改进

package main
import "fmt"

//改写共有的结构体
type Student struct{
    Name string
    Age int
    Score int
}

//将Pupil和Graduate方法绑定到*student
func (stu *Student) ShowInfo{
     fmt.Printf("学生名 = %v, 年龄 = %v 成绩 = %v",stu.Name,stu.Age,stu.Score)
}

/*录入分数*/
func(p *Student) SetScore(score int){
    if score > 100 ||score < 10{
        fmt.Println("请输入正确的范围")
    	return
    }
    p.Score = score
}

/*显示状态   --  大学生和小学生不一样 -- 保留  -- 大学生*/
func (p *Graduate) testing(){
    fmt.Println("小学生正在考试")
}

type Pupil struct{
	Student       //嵌入了student的匿名结构体   - -  继承
}

type Graduate struct{
	Student       //嵌入了student的匿名结构体   - -  继承
}
/*显示状态*/
func (p *Pupil) tesing(){
    fmt.Println("小学生正在考试")
}

func main(){
    p := &Pupil{}
    P.Student.Name  = "lihua"
	p.Student.Age = 8
    p.testing()
    p.Student.SetScore(100)
    p.Student.showInfo()
}

继承的深入讨论

提示

因为在Golang中是没有多态的,Java中的多态恰好是非常难的,在Golang中也是可以很好的实现

💡简单的一个案例如下:

package main
import "fmt"
type A struct{
	Name string
	age int
}
func (a *A) Sayok(){   //大写方法
     fmt.Println("A Sayok",a.Name)
}
func (a *A) hello(){   //小写方法
    fmt.Println("A hello",a.Name)
}
type B struct{
    A
}
func main(){
    var b B
    b.A.Name = "tom"
    //b.Name = "tom"  -- 不可以
    b.A.age = 19
    b.A.Sayok()
    b.Sayok()
    b.A.hello()
    fmt.Println(b)
}

🚀 编译结果如下:

[root@mail golang]# go run Account.go 
A Sayok tom
A hello tom
{{tom 19}}

**由此可见,私有的可以继承使用,而且方法hello和Sayok方法无论是私有的还是公有的都可以直接使用,首字母大写或者小写均可以 **

在编译的时候可以把A去掉,进行简化,编译器也可以识别(编译器会自己找)

    var b B
    b.Name = "tom"
    b.age = 19
    b.Sayok()
    b.hello()

当匿名结构体和结构体中的变量重复时候,编译器会采用就近原则

type A struct{
	Name string
	age int
}
type B struct{
    A
    Name string
}

func main(){
	b.Name = "jack"   //此时找的是自身的
    /* 如果要给A的Name赋值,就必须要使用*/
    b.A.Name = "lisa"
    b.age = 20      //找的是A
    b.Sayok()      //找的是自身的Name和A的Age
}

结构体嵌入了两个或者多个匿名结构体,如果两个匿名结构体有相同的字段和方法(同时结构体本身没有相同的字段和方法),在访问时,就必须要指定匿名结构体的名字,否则编译会报错

type A struct{
	Name string    //相同字段
	age int
}
type B struct{
    Name string    //相同字段
    score int
}
type C struct{
	A
	B
/*如果本身有Name则不会报错,就近原则*/
}
func main(){
    var c C
    c.Name = "tom"      //会报错!!!!!
    c.A.Name = "lisa"   //指定A
    c.B.Name = "jack"   //指定B   
}

这种情况也被称为多重继承,为了保证代码简洁性,建议尽量不使用多重继承。

嵌套匿名结构体后,可以在创建结构变量时,直接指定各个匿名结构体字段的值

func main(){
	c := C{
        B{"张三",1000},
        A{"李四",19},
    }
}

注意后面需要有,

结构体中可以只写类型

package main
import "fmt"
type A struct{
	Name string    //相同字段
	age int
}
type C struct{
	A
	int   //表示的是匿名字段
}
/*使用int方法*/
func main(){
    var c C
    c.Name = "lihua"
    c.age = 100
    c.int = 20
    fmt.Println("int=",c.int)
    fmt.Println("c= ",c)
}

🚀 编译结果如下:

[root@mail golang]# go run main.go 
int= 20
c= {{lihua 100} 20}

END 链接