Fork me on GitHub

Golang之旅36-方法和工厂模式

方法和工厂模式

方法介绍

在创建了一个结构体之后,结构体可能还具有一些行为,比如Person结构体还有说话、跑步、学习等,此时通过方法才能完成。

Golang中的方法是作用在指定数据类型上的,是自定义类型。

1
2
3
4
5
6
7
type A struct{
Num int
}

func (a A) test(){ // A结构体有种方法叫做test()
fmt.Println(a.Num)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main
import "fmt"

type Person struct{
Name string
}

func (p Person) test(){ // 定义方法test()
p.Name = "jack"
fmt.Println("test() name=", p.Name)
}

func main(){
var p Person // 创建结构体实例
p.Name = "xiaoming"
p.test() // 调用方法
fmt.Println("main() p.Name=", p.Name) // 输出是xiaoming
}
  • 方法test和结构体Person进行绑定;只能通过Person类型的变量来进行调用
  • p这个名字是任意指定的,一般是结构体名字的小写

方法声明

1
2
3
4
func (receiver type) methodName (参数列表)(返回值列表){
方法体
return 返回值
}
  • 参数列表:表示方法输入
  • receiver type:type是结构体,该方法和type结构体进行绑定
  • receiver :是type类型的一个变量实例
  • return语句不是必须的
  • 返回值列表可以有多个

方法案例

  • 方法中不接收参数
  • 方法中接收一个参数
  • 方法中接收多个参数并有返回值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package main
import "fmt"

type Person struct{
Name string
}

func (p Person) speak(){ // 定义方法speak():最直接
fmt.Println(p.Name, "是一个好人!")
}

func (p Person) jisuan(){ // 方法中没有参数
res := 0
for i := 1;i <= 1000;i++{
res += i
}
fmt.Println(p.Name, "计算的结果是=", res)
}

func (p Person) jisuan2(n int){ // 方法中接受一个参数
res := 0
for i := 1;i <= n;i++{
res += i
}
fmt.Println(p.Name, "计算的结果是=", res)
}

func (p Person) getSum(n1, n2 int) int{ // 方法中接受多个参数和返回值
return n1 + n2
}

func main(){
var p Person // 创建结构体实例
p.Name = "Tom"
p.speak() // 调用方法
p.jisuan()
p.jisuan2()

res := p.getSum(10,20) // 调用方法,并用res接受结果
fmt.Println(res)
}

方法的调用和传参机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main
import "fmt"

// 定义结构体
type Person struct{
Name string
}

// 定义一个方法
func (p Person) getSum(n1, n2 int) int{ // 方法中接受多个参数和返回值
return n1 + n2
}

func main(){
var p Person // 创建结构体实例 p
n1 := 10
n2 := 20
res := p.getSum(n1, n2) // 调用方法,并用res接受结果;调用的时候创建独立的栈
fmt.Println(res)
}

Mayyf1.png

  • 通过一个变量调用方法时,调用机制和函数类似
  • 不同之处:调用方法时,变量本身也会作为一个参数传递到方法中
  • 如果变量是值类型,进行值拷贝;如果变量是引用类型,进行地址拷贝
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package main
import "fmt"

type Circle struct{
radius float64
}

// 声明一个方法area和Circle进行绑定,返回的值面积
func (c Circle) area() float64{
return 3.14 * c.radius * c.radius
}

// 为了提高效率,通常将方法和结构体的指针类型进行绑定
func (c *Circle) area2() float64{
// 标准写法:c是指针,(*c).radius
//return 3.14 * (*c).radius * (*c).radius
fmt.Println("c 是 *Circle 指向的地址", c) // c本身就是指针
c.radius = 10
return 3.14 * c.radius * c.radius
}

func main(){
var c Circle // 声明结构体
c.radius = 4.0 // 半径
res := c.area() // 调用方法
fmt.Println(res)

var c Circle
fmt.Println("main c 结构体变量的地址 = %p", &c) // & 表示的是取地址
c.radius = 5.0
res2 := (&c).area2()
// 编译器底层优化:(&c).area2() 等价于 c.area2()
fmt.Println("面积=", res2)
fmt.Println("c.radius = ", c.radius) // 10
}
  • Golang中的方法作用在指定的数据类型上,因此是自定义数据类型,都可以有方法,不仅仅是结构体,比如int 、float32
  • 如果一个类型实现了String()方法,fmt.Println()默认会调用这个变量的String()进行输出。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package main
import "fmt"

type integer int

func (i integer) print(){
fmt.Println(i)
}

func (i *integer) change(){
*i = *i + 1
}

// 定义结构体
type Student struct{
Name string
Age int
}

// 定义方法
func (stu *Student) String() string{
str := fmt.Sprintf("Name=[%v] Age=[%v]", stu.Name, stu.Age)
return str
}

func main(){
var i integer = 10
i.print() // 10
i.change()
fmt.Println(i) // 11,通过指针的引用传递进行修改

stu := Student{
Name := "tom",
Age := 20,
}
fmt.Println(&stu) // 传入的是地址
}

课堂练习题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package main
import "fmt"

type MethodUtils struct{
// 结构体中可以没有任何字段
}

// 方法中没有参数和返回值:打印10 * 8的星号
func (mu MethodUtlis) Print(){
for i:= 1;i <= 10;i++{
for j:=1; j <= 8;j++{
fmt.Print("*")
}
fmt.Println() // 换行作用
}
}

// 方法中带有参数和返回值:求面积
func (mu MethodUtlis) area(lenght, width float64) (float64){
return length * width
}

// 判断奇偶数
func (mu *MehtdoUtlis) JudgeNum(n int){
if n % == 0{
fmt.Println("偶数")
}else{
fmt.Println("奇数")
}
}

// 根据输入的行数和列数,打印符号
func (mu *MethodUtlis) PrintStr(m int, n int, key string){
for i := 1;i<=m;i++{
for j := 1;j<=n;j++{
fmt.Println(key)
}
fmt.Println() // 换行操作
}
}

func main(){
var mu MethodUtlis // 创建结构体实例和调用方法
mu.Print()

res := mu.area(2.5, 8.4)
fmt.Println(res)

mu.JudgeNum(10)

mu.PrintStr(5,7,"*")
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package main
import "fmt"

// 实现简易计算器

type Calcuator struct{
Num1 float64
Num2 float64
}

// 方式1:写出4种方法单独实现
func (cal *Calcuator) getSum() float64{
// 标注形式:return (*cal).Num1 +(*cal).Num2
return cal.Num1 + cal.Num2
}

// 方式2:用一个方法实现
func (cal * Calcuator) getRes(operator byte) float64{
res := 0.0
switch operator{
case '+':
res = cal.Num1 + cal.Num2
case '-':
res = cal.Num1 - cal.Num2
default:
fmt.Println("运算符有问题")
}
return res
}

func main(){
var cal Calcuator

// 方式1
cal.Num1 = 1.2
cal.Num2 = 2.3
fmt.Printf("sum=%v \n", fmt.Sprintf("%.2f", cal.getSum()))

// 方式2
res := cal.getRes("+") // 调用方法,传入参数
fmt.Println("res",res)
}

方法和函数的区别

  • 调用方式不同
    • 函数:函数名+实参列表
    • 方法:变量.方法名(实参列表)
  • 对于普通函数,接收者是值类型,不能将指针类型的数据直接传递
  • 对于方法,接收者是值类型时,可以直接使用指针类型的变量调用方法,反之亦然
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package main
import "fmt"

type Person struct{
Name string
}

// 普通函数
func test01(p Person){
fmt.Println(p.Name)
}

func test02(p *Person){
fmt.Println(p.Name)
}

// 方法
func (p Person) test03(){
p.Name = 'jack'
fmt.Println("test03() = ",p.Name) // jack
}

func (p *Person) test04(){
p.Name = "mary"
fmt.Println(p.Name) // mary
}


func main(){
p := Person("tom")
test01(p) // 必须传入值类型,和上面的方法必须保持一致
test02(&p) // 必须传入指针类型

// 比较3和4的区别
p.test03()
fmt.Println("main() p.name=", p.name) // tom
(&p).test03() // 编译器内部处理;形式上传入的地址,仍然是值拷贝
fmt.Println("main() p.name=", p.name) // tom

(&p).test04()
fmt.Println("main() p.Name=", p.Name) // mary
p.test04() // 形式上传入的值类型,仍然是地址拷贝
fmt.Println("main() p.Name=", p.Name) // mary
}

总结:到底是哪种拷贝方式,主要是看方法中传递是值类型还是指针类型。注意观察和方法绑定的是什么类型。

OOP实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package main
import "fmt"

// 1. 先定义结构体
type Student struct{
name string
gender string
age int
id int
score float64
}

type Box struct{
length float64
width float64
height float64
}

// 2. 根据结构体写出方法
func (s *Student) say() string{
infoStr := fmt.Sprintf("name=[%v] gender=[%v] age=[%v] id=[%v] score=[%v]", s.name, s.gender, s.age, s.id, s.score)
return infoStr
}

func (box *Box) getVolumn() float64{
return box.length * box.width * box.height
}

func main(){
// 3. 创建方法的实例,传入具体信息
var s = Student{
name: "tom",
gender: "male",
age: 18,
id: 100,
score: 90,
}
fmt.Println(s.say())

// 求体积
var box Box // 创建一个实例,同时定义3个属性值
box.length = 1.1
box.width = 2.2
box.height = 2.0
volumn := box.getVolumn() // 调用方法
fmt.Println("体积为=%.2f", volumn)
}

结构体创建方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package main
import "fmt"

type Stu struct{
Name string
Age int
}

func main(){
// 要注意顺序
var stu1 = Stu{"小明", 20}
stu2 := Stu{"小明", 20}

// 顺序无关
var stu3 = Stu {
Name: "小明"
Age: 20,
}

stu4 := Stu{
Age: 20,
Name: "小明"
}
fmt.Println(stu1, stu2, stu3, stu4)

// 方式2:返回结构体的指针类型
var stu5 = &Stu{"小明", 20}
stu6 := &Stu{"小明", 20}
var stu7 = &Stu {
Name: "小明"
Age: 20,
}

stu8 := &Stu{
Age: 20,
Name: "小明"
}
fmt.Println(*stu5, *stu6, *stu7, *stu8)
}

工厂模式

当创建的结构体首字母是大写的,外部可以直接调用

MdaazF.png

Mdasd1.png

但是一般是小写的,那么如何使用呢?此时使用到的是工厂模式

MdBD1K.png

MdBakR.png

  • stu实际上返回结构体的指针

  • 如果字段是小写的,该如何进行访问?

  • package model
    
    // 定义一个结构体Student
    type student struct {
    	Name string
    	age int   // 结构体中的字段改成小写,如何在外部访问?
    }
    
    func NewStudent(n string, a int) *student{
    	return &student{
    		Name: n,
    		age: a,
    	}
    }
    
    // 通过另外写一个方法进行访问,且方法名字必须是大写的,才能进行外部访问
    func (s *student) GetAge() int{    // GetAge() 要大写,保证对外公开
    	return s.age
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16



    ```go
    package main

    import (
    "code/char26-factory/model" // 导入外部包
    "fmt"
    )

    func main(){
    var stu = model.NewStudent("tom", 40)
    fmt.Println(*stu)
    fmt.Println(stu.Name, stu.GetAge()) // GetaAge()是大写的
    }

本文标题:Golang之旅36-方法和工厂模式

发布时间:2019年11月15日 - 23:11

原始链接:http://www.renpeter.cn/2019/11/15/Golang%E4%B9%8B%E6%97%8536-%E6%96%B9%E6%B3%95%E5%92%8C%E5%B7%A5%E5%8E%82%E6%A8%A1%E5%BC%8F.html

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

Coffee or Tea