封装 - 工厂模式

  • golang的结构体没有构造函数,通常可以使用工厂模式来解决这个问题。
  • 如果包里面的结构体变量首字母小写,引入后,不能直接使用,可以工厂模式解决。
  • 只关心结果,不关心实现过程,因为过程是由封闭的工厂来实现的。

package demo

//定义一个结构体
type student struct{
    Name string
    score float64
}
// *student返回结构体的指针
func NewStudent(name string, score float64) *student {
    //外包引用不了,但本包是可以引用的
    stu := student{
        Name : name,
        score : score,
    }
    return &stu
}
// 结构体中的score字段也是小写所以需要写一个方法返回
func (s *student) GetScore() float64 {
    return s.score
}

func (s *student) SetScore(score float64) {
    s.score = score
}
package main

import (
    "fmt"
    "go-demo/demo"
)

func main() {
    s := demo.NewStudent("Tony", 60.5)
    fmt.Println(s)
    fmt.Println(s.GetScore())
    s.SetScore(80)
    fmt.Println(s)
    // &{Tony 60.5}
    // 60.5
    // &{Tony 80}
}

继承 - struct嵌套

  • 在golang中,采用匿名结构体字段来模拟继承关系。
  • 这个时候,可以说Student是继承自Person。
package main

import (
  "fmt"
)

type Person struct {
  name string
  age int
  sex string
}
func (Person) SayHello(){
  fmt.Println("this is from Person")
}

type Student struct {
  Person
  school string
}

func main() {
  stu := Student{school:"middle"}
  stu.name = "leo"
  stu.age = 30
  fmt.Println(stu.name)
  stu.SayHello()
}

多态 - Golang接口的定义

1. Golang中的接口

  • 在Go语言中接口(interface)是一种类型,一种抽象的类型。

  • 接口(interface)定义了一个对象的行为规范,只定义规范不实现,由具体的对象来实现规范的细节。

  • 实现接口的条件:

    • 一个对象只要全部实现了接口中的方法,那么就实现了这个接口。
    • 换句话说,接口就是一个需要实现的方法列表。

2. 为什么要使用接口

  • 下面的代码中定义了猫和狗,然后它们都会叫,你会发现main函数中明显有重复的代码。
  • 如果我们后续再加上猪、青蛙等动物的话,我们的代码还会一直重复下去。
  • 那我们能不能把它们当成“能叫的动物”来处理呢?
package main
import (
    "fmt"
)

type Cat struct {
  Name string
}
func (c Cat) Say() string { return c.Name + ":喵喵喵" }
type Dog struct {
  Name string
}
func (d Dog) Say() string { return d.Name + ": 汪汪汪" }

func main() {
    c := Cat{Name: "小白猫"} 
    fmt.Println(c.Say())
    d := Dog{"阿黄"}
    fmt.Println(d.Say())
}
// 小白猫:喵喵喵
// 阿黄: 汪汪汪

3. 定义一个Usber接口

  • 定义一个 Usber 接口让 Phone 和 Computer 结构体实现这个接口
package main

import "fmt"

// 1.接口是一个规范
type Usber interface {
  getName() string
}
// 2.如果接口里面有方法的话,必须要通过结构体或者通过自定义类型实现这个接口
type Phone struct {
  Name string
}

type Computer struct {
  Brand string
}
func (c *Computer) getName() string {
  return c.Brand
}
// 3.手机要实现usb接口的话必须得实现usb接口中的所有方法
func (p *Phone) getName() string {
  return p.Name
}

func main() {
  p := &Phone{
    Name: "华为手机",
  }
  c := &Computer{
    Brand: "联想电脑",
  }
  var p1 Usber // golang中接口就是一个数据类型
  p1 = p // 表示手机实现Usb接口
  fmt.Println(p1.getName())
  
  // 接口使用场景,处理相同类型的数据
  newName := transData(p)
  newName1 := transData(c)
  fmt.Println(newName, newName1)
}

func transData(usber Usber) string {
  name := usber.getName()
  return fmt.Sprintf("%s%s", name, "处理后")
}

空接口

1. 空接口说明

  • Golang中空接口也可以直接当做类型来使用,可以表示任意类型(泛型概念)。
  • Golang 中的接口可以不定义任何方法,没有定义任何方法的接口就是空接口。
  • 空接口表示没有任何约束,因此任何类型变量都可以实现空接口。
  • 空接口在实际项目中用得非常多,用空接口可以表示任意数据类型。

2. 空接口作为函数的参数

package main
import "fmt"

// 空接口作为函数的参数
func show(a interface{}) {
  fmt.Printf("值:%v 类型:%T\n", a, a)
}

func main() {
    show(20)               // 值:20 类型:int
    show("你好golang")      // 值:你好golang 类型:string
    slice := []int{1, 2, 34, 4}
    show(slice)            // 值:[1 2 34 4] 类型:[]int
}

3. 切片实现空接口

package main
import "fmt"

func main() {
  var slice = []interface{}{"张三", 20, true, 32.2}
  fmt.Println(slice)  // [张三 20 true 32.2]
}

4. map 的值实现空接口

package main
import "fmt"

func main() {
    // 空接口作为 map 值
    var studentInfo = make(map[string]interface{})
    studentInfo["name"] = "张三"
    studentInfo["age"] = 18
    studentInfo["married"] = false
    fmt.Println(studentInfo) // map[age:18 married:false name:张三]
}

类型断言

  • 一个接口的值(简称接口值)是由一个具体类型和具体类型的值两部分组成的。

  • 这两部分分别称为接口的动态类型和动态值。

  • 如果我们想要判断空接口中值的类型,那么这个时候就可以使用类型断言。

  • 其语法格式: x.(T)

    • x : 表示类型为 interface{}的变量
    • T : 表示断言 x 可能是的类型
package main
import "fmt"

func main() {
    var x interface{}
    x = "Hello golang"
    v, ok := x.(string)
    if ok {
        fmt.Println(v)
    } else {
        fmt.Println("非字符串类型")
    }
}

值接收者和指针接收者

1. 值接收者

  • 当方法作用于值类型接收者时,Go语言会在代码运行时将接收者的值复制一份。
  • 在值类型接收者的方法中可以获取接收者的成员值,但修改操作只是针对副本,无法修改接收者变量本身。
package main

import "fmt"

type Usb interface {
    Start()
    Stop()
}

type Phone struct {
    Name string
}

func (p Phone) Start() {
    fmt.Println(p.Name, "开始工作")
}

func (p Phone) Stop() {
    fmt.Println(p.Name, "停止工作")
}

func main() {
    phone1 := Phone{
        Name: "小米手机",
    }
    var p1 Usb = phone1 // phone1 实现了 Usb 接口
    p1.Start()          // 输出: 小米手机 开始工作

    phone2 := &Phone{
        Name: "苹果手机",
    }
    var p2 Usb = phone2 // phone2 实现了 Usb 接口
    p2.Start()          // 输出: 苹果手机 开始工作
}

2. 指针接收者

  • 指针类型的接收者由一个结构体的指针组成。
  • 由于指针的特性,调用方法时修改接收者指针的任意成员变量,在方法结束后,修改都是有效的。
  • 这种方式就十分接近于其他语言中面向对象中的 this 或者 self。
  • 例如我们为 Person 添加一个 SetAge 方法,来修改实例变量的年龄。

3. 指针类型接收者 使用时机

  • 并不是所有情况下都希望修改数据:
    • 需要修改接收者中的值
    • 接收者是拷贝代价比较大的大对象
    • 保证一致性,如果有某个方法使用了指针接收者,那么其他的方法也应该使用指针接收者。