# day06 接口error、包package、go module

# 内容回顾

# 接口

接口是一种类型,是一种抽象的类型

// Student 是我们定义的一个具体类型
type Student struct {
  Name string
  Age uint8
}

接口是抽象的类型,基于能做什么抽象出来的一种类型。

type Dreamer interface{
  Dream()
}

引申

强类型的语言一直都在强调类型

# 接口的知识点

  • 为什么要使用接口

    面向接口

    解耦

    抽象

    现有支付宝支付后面扩展微信支付的例子

  • 接口的定义

    type Runner interface{
      run()  // 方法签名
      say()
    }
    
  • 实现接口的条件

    一个类型只要拥有了接口中规定的所有方法,那么它就实现了这个接口

    type Myint int
    type Func func()string
    type Dog struct{}
    
  • 接口值

    接口类型默认零值是 nil

    一个接口类型的值能够存储任意满足该接口类型的变量

  • 指针接收者实现接口与值接收者实现接口的区别

    • 使用值接收者实现接口,值和指针都能赋值给接口变量
    • 使用指针接收者实现接口,只有指针都能赋值给接口变量

    字面量:"jade"、10,字面量是无法取地址的。

    变量:name := "jade",变量可以取地址:&name

  • 结构体与接口类型的关系

    一个结构体可以实现多个接口

    多个结构体可以实现一个接口

    结构体可以内嵌接口类型

  • 接口组合

    type Closer interface {
      Close()
    }
    
    type Reader interface{
      Read()
    }
    
    type ReadCloser interface{
      Reader  // 嵌入另外一个接口类型
      Close()
    }
    
  • 空接口

    没有要求任何方法的接口类型,反过来任意类型都实现了空接口

    type Any interface {
    }
    

    通常简写为interface{}

    var x interface{}  // 声明一个空接口类型的变量x
    
    var m0 = map[string]int{
      "linda": 18,
      "tom": 20,
      "catherine": 19,
    }
    
    // 字面量初始化
    var m = map[string]interface{}{
      "name": "linda",
      "age": 20,
      "married": false,
    }
    
    // 此时map需要初始化
    var m2 map[string]interface{}
    m2 = make(map[string]interface{}, 8)
    m2["name"] = "linda"
    m2["age"] = 20
    
  • 类型断言

    接口变量能存储任意类型的前提条件是,接口分为动态值和动态类型两部分

    接口值示例

    想要获取到原始的类型和值,可以使用类型断言

    类型断言两种写法:

    • 接口变量.(T)

      var x interface{}
      x = 10
      v, ok := x.(string)  // ok=false v=""
      
      x = "linda"
      v, ok := x.(string)  // ok=true  v="linda"
      
    • switch x.(type)

      var x interface{}
      x = 100
      
      switch v := x.(type){
        case int:
        // v = 100
        case string:
        // ....
      }
      

# fmt包

熟悉常用函数的用法

# 文件操作

熟悉常用函数的用法

# 作业

使用接口实现一个简单的日志库

image-20220220110339978

# 接口error

Go语言中把错误当成一种特殊的值来处理

# 接口error

type error interface {
	Error() string
}
  • error接口只包含一个方法Error,返回一个描述错误信息的字符串
  • 当一个函数或方法需要返回错误时,我们通常把错误作为最后一个返回值,如
func Open(name string)(*File, error){
  return OpenFile(name string, O_RDONLY, 0)
}
  • 由于 error 是一个接口类型,默认零值为nil,通常与nil进行比较判断是否异常,如
file, err := Open("./file.text")
if err != nil {
  fmt.Println("打开文件失败, err:", err)
  return
}
  • fmt打印err时,自动调用Error方法,也就是打印错误的描述信息

# errors.New 本质

  • 源码开路
package errors

// New 构造函数,首字母大写,对外暴露
func New(text string) error {
	return &errorString{text}
}

// errorString 结构体关注是什么,保存数据,首字母小写
type errorString struct {
	s string
}

// 指针类型接收者,分析参数时类似self,(e *errorString)更加明确要处理的数据源
func (e *errorString) Error() string {
	return e.s
}

// 定义error接口类型
type error interface {
	Error() string
}

# 最佳实践

// 自定义error类型
var ErrInvalidOrderId = errors.New("无效订单ID")
var ErrServerConnection = errors.New("服务端连接异常")

type Order struct {
	Id   int64
	Name string
}

func getOrderById(id int64) (*Order, error) {
	if s := "服务端连接异常"; len(s) == 0 {
		return nil, ErrServerConnection
	}
	if s := "无效订单ID"; len(s) > 0 {
		return nil, ErrInvalidOrderId
	}

	return &Order{
		Id:   1001,
		Name: "Order1001",
	}, nil
}

func main() {
	order, err := getOrderById(1001)
	// 1. 直接判断
	if err == ErrServerConnection {
		// 类似Sprintf,格式化动词%w,返回一个字符串
		fmt.Println(fmt.Errorf("服务端连接异常: err%w", err))
		return
	}
	// 2. errors.Is走起
	if ok := errors.Is(err, ErrInvalidOrderId); ok {
		fmt.Println("无效订单ID")
		return
	}
	fmt.Println(order)
}

# 包 package

  • project :项目 ---> 一个VsCode窗口 或者 一个GoLand窗口 打开一个项目(project)
  • package:包 --> 一个project 可以由多个 package 组成
  • .go文件:源码文件

# 定义包 package

package packagename
  • 包名为main的包是应用程序的入口包,这种包编译后会得到一个可执行文件,编译不包含main包的源代码则不会得到可执行文件

# 标识符的可见性

  • Go语言中是通过标识符的首字母大/小写来控制标识符的对外可见(public)/不可见(private)
package demo

import "fmt"

// 包级别标识符的可见性

// num 定义一个全局整型变量
// 首字母小写,对外不可见(只能在当前包内使用)
var num = 100

// Mode 定义一个常量
// 首字母大写,对外可见(可在其它包中使用)
const Mode = 1

// person 定义一个代表人的结构体
// 首字母小写,对外不可见(只能在当前包内使用)
type person struct {
	name string
	Age  int
}

// Add 返回两个整数和的函数
// 首字母大写,对外可见(可在其它包中使用)
func Add(x, y int) int {
	return x + y
}

// sayHi 打招呼的函数
// 首字母小写,对外不可见(只能在当前包内使用)
func sayHi() {
	var myName = "七米" // 函数局部变量,只能在当前函数内使用
	fmt.Println(myName)
}

标识符的首字母 什么时候大写,什么时候小写 都是有考量的!!!

# 包的引入

import importname "path/to/package"
  • importname:引入的包名,通常都省略。默认值为引入包的包名
  • path/to/package:引入包的路径名称,必须使用双引号包裹起来
import (
    "fmt"
  	"net/http"
    "os"
)

// alias 别名
import alias "fmt"

// 匿名引入
import _ "github.com/go-sql-driver/mysql"
  • 匿名引入主要为了加载这个包,使得包资源得以初始化,init函数被执行一遍

# 初始化函数 init

// 无输入参数和返回值
func init(){
}
  • 多包引入时,执行顺序如下
package main

import "fmt"

var x int8 = 10

const pi = 3.14

func init() {
  fmt.Println("x:", x)
  fmt.Println("pi:", pi)
  sayHi()
}

func sayHi() {
	fmt.Println("Hello World!")
}

func main() {
	fmt.Println("你好,世界!")
}
  • 先初始化变量,再默认依次执行 init 和 main

# 依赖包管理go module

Go源码的依赖包管理方案go mudule,1.16版本默认开启

# GOPROXY

go env -w GOPROXY=https://goproxy.cn,direct

# 下载依赖 go get

  • 默认下载最新版本
go get github.com/ni-ning/achilles latest
  • 下载指定版本
go get github.com/ni-ning/achilles@v1.0.0
  • 默认下载到 $gopath/pkg/mod/目录下
  • 导入的路径名由被引入的包的 module 定义
  • go.mod 文件中记录了当前项目中所有依赖包的相关信息
module main

go 1.19

// require 声明依赖的关键字
// 依赖包的版本号
//   latest:最新版本
//   v1.0.0:详细版本号
//   commit hash:指定某次commit hash
require github.com/ni-ning/achilles v1.0.0
  • 直接引入即可
package main

import "github.com/ni-ning/achilles"

func main() {
	achilles.SayHello()
}
  • 不同于npm和pypi等,Go并没有提供一个中央仓库来管理所有依赖包,而是采用分布式的方式来管理包,为了防止依赖包被非法篡改,Go module 引入了go.sum机制来对依赖包进行校验。

# 发布依赖 module

  • 见示例 https://github.com/ni-ning/achilles
上次更新: 9/10/2022, 5:57:39 PM