Go:接口

接口既约定

Go 语言中接口是抽象类型 ,与具体类型不同 ,不暴露数据布局、内部结构及基本操作 ,仅提供一些方法 ,拿到接口类型的值 ,只能知道它能做什么 ,即提供了哪些方法 。

func Fprintf(w io.Writer, format string, args ...interface{}) (int, error)func Printf(format string, args ...interface{}) (int, error) {return Fprintf(os.Stdout, format, args...)
}func Sprintf(format string, args ...interface{}) string {var buf bytes.BufferFprintf(&buf, format, args...)return buf.String()
}
  • fmt.Fprintf函数用于格式化输出 ,其第一个形参是io.Writer接口类型 。io.Writer接口封装了基础写入方法 ,定义了Write方法 ,要求将数据写入底层数据流 ,并返回实际写入字节数等 。
package io// Writer接口封装了基础的写入方法
type Writer interface {// Write 从 p 向底层数据流写入 len(p) 个字节的数据// 返回实际写入的字节数 (0 <= n <= len(p))// 如果没有写完,那么会返回遇到的错误// 在 Write 返回 n < len(p) 时,err 必须为非 nil// Write 不允许修改 p 的数据,即使是临时修改// 实现时不允许残留 p 的引用Write(p []byte) (n int, err error)
}
  • 这一接口定义了fmt.Fprintf和调用者之间的约定 ,调用者提供的具体类型(如*os.File*bytes.Buffer )需包含与Write方法签名和行为一致的方法 ,保证fmt.Fprintf能使用满足该接口的参数 ,体现了可取代性 ,即只要满足接口 ,具体类型可相互替换 。
type ByteCounter intfunc (c *ByteCounter) Write(p []byte) (int, error) {*c += ByteCounter(len(p))return len(p), nil
}
  • ByteCounter类型为例 ,它实现了Write方法 ,满足io.Writer接口约定 ,可在fmt.Fprintf中使用 。
package fmt// 在字符串格式化时如果需要一个字符串
// 那么就调用这个方法来把当前值转化为字符串
// Print 这种不带格式化参数的输出方式也是调用这个方法
type Stringer interface {String() string
}
  • fmt包还有fmt.Stringer接口 ,定义了String方法 。类型实现该方法 ,就能在字符串格式化时将自身转化为字符串输出 ,如之前的Celsius*IntSet类型添加String方法后 ,可满足该接口 ,实现特定格式输出 。

接口类型

接口类型定义了一套方法 ,具体类型要实现某接口 ,必须实现该接口定义的所有方法 。如io.Writer接口抽象了所有可写入字节的类型 ,像文件、内存缓冲区等 ,具体类型若要符合io.Writer ,就得实现其Write方法 。

package iotype Reader interface {Read(p []byte) (n int, err error)
}type Closer interface {Close() error
}
  • Reader接口:抽象了所有可读取字节的类型 ,定义了Read方法 ,用于从类型中读取数据 。
  • Closer接口:抽象了所有可关闭的类型 ,如文件、网络连接等 ,定义了Close方法 。
type ReadWriter interface {ReaderWriter
}type ReadWriteCloser interface {ReaderWriterCloser
}type ReadWriter interface {Read(p []byte) (n int, err error)Write(p []byte) (n int, err error)
}type ReadWriter interface {Read(p []byte) (n int, err error)Writer
}
  • 可通过组合已有接口得到新接口 ,如ReadWriter接口由ReaderWriter接口组合而成 ,ReadWriteCloser接口由ReaderWriterCloser接口组合而成 ,这种方式称为嵌入式接口 ,类似嵌入式结构 ,可直接使用组合后的接口 ,无需逐一写出其包含的方法 。
  • 三种声明效果一致 ,方法定义顺序无意义 ,关键是接口的方法集合 。

实现接口

  • 具体类型实现接口需实现接口定义的所有方法 ,如*os.File实现了io.ReaderWriterCloserReaderWriter接口 ,*bytes.Buffer实现了ReaderWriterReaderWriter接口 。
var w io.Writer
w = os.Stdout         // OK: *os.File有Write方法
w = new(bytes.Buffer) // OK: *bytes.Buffer有Write方法
w = time.Second       // 编译错误: time.Duration缺少Write方法var rwc io.ReadWriteCloser
rwc = os.Stdout         // OK: *os.File有Read、Write、Close方法
rwc = new(bytes.Buffer) // 编译错误: *bytes.Buffer缺少Close方法// 当右侧表达式也是一个接口时,该规则也有效:
w = rwc                 // OK: io.ReadWriteCloser有Write方法
rwc = w                 // 编译错误: io.Writer 缺少Close方法
  • 接口赋值规则 :当表达式实现接口时可赋值给对应接口类型变量 ,若右侧表达式也是接口 ,要满足接口间方法包含关系 。如io.ReadWriteCloser接口包含io.Writer接口方法 ,实现前者的类型也实现了后者 。
type IntSet struct { /*... */ }
func (*IntSet) String() stringvar _ = IntSet{}.String() // 编译错误: String 方法需要*IntSet 接收者var s IntSet
var _ = s.String() // OK: s 是一个变量,&s有 String 方法var _ fmt.Stringer = &s // OK
var _ fmt.Stringer = s // 编译错误: IntSet缺少String 方法
  • 类型方法与接口实现的关系:类型的方法接收者有值类型和指针类型 ,编译器可隐式处理值类型变量调用指针方法(前提变量可变 ) 。如IntSet类型String方法接收者为指针类型 ,不能从无地址的IntSet值调用 ,但可从IntSet变量调用 ,且*IntSet实现了fmt.Stringer接口 。
var any interface{}
any = true
any = 12.34
any = "hello"
any = map[string]int{"one": 1}
any = new(bytes.Buffer)
  • interface{}为空接口类型 ,对实现类型无方法要求 ,可把任何值赋给它 ,如fmt.Printlnerrorf等函数能接受任意类型参数就是利用了空接口 。但不能直接使用空接口值 ,需通过类型断言等方法还原实际值 。

隐式声明

  • 编译器可隐式判断类型实现接口 ,如*bytes.Buffer的任意值(包括nil )都实现了io.Writer接口 ,可简化变量声明 。非空接口常由指针类型实现 ,但也有其他引用类型可实现接口 ,如slicemap 、函数类型等 ,且基本类型也能通过定义方法实现接口 ,如time.Duration实现了fmt.Stringer
type Text interface {Pages() intWords() intPageSize() int
}type Audio interface {Stream() (io.ReadCloser, error)RunningTime() time.DurationFormat() string // 比如 "MP3"、"WAV"
}type Video interface {Stream() (io.ReadCloser, error)RunningTime() time.DurationFormat() string // 比如 "MP4"、"WMV"Resolution() (x, y int)
}type Streamer interface {Stream() (io.ReadCloser, error)RunningTime() time.DurationFormat() string
}
  • 接口用于抽象分组:以管理或销售数字文化商品的程序为例 ,定义多种具体类型(如AlbumBook等 ) ,可针对不同属性定义接口(如ArtifactsPagesAudioVideo ) ,还可进一步组合接口(如Streamer ) 。Go 语言可按需定义抽象和分组 ,不用修改原有类型定义 ,从具体类型提取共性用接口表示 。

使用 flag.Value 来解析参数

var period = flag.Duration("period", 1*time.Second, "sleep period")func main() {flag.Parse()fmt.Printf("Sleeping for %v...", *period)time.Sleep(*period)fmt.Println()
}

以实现睡眠指定时间功能的程序为例 ,通过flag.Duration函数创建time.Duration类型的标志变量period ,用户可用友好方式指定时长 ,如50ms2m30s等 ,程序进入睡眠前输出睡眠时长 。

package flag// Value 接口代表了存储在标志内的值
type Value interface {String() stringSet(string) error
}

flag.Value接口定义了StringSet方法 。String方法用于格式化标志对应的值 ,输出命令行帮助消息 ,实现该接口的类型也是fmt.StringerSet方法用于解析传入的字符串参数并更新标志值 ,是String方法的逆操作 。

自定义实现flag.Value接口

// *celsiusFlag 满足 flag.Value 接口
type celsiusFlag struct{ Celsius }func (f *celsiusFlag) Set(s string) error {var unit stringvar value float64fmt.Sscanf(s, "%f%s", &value, &unit) // 无须检查错误switch unit {case "C", "°C":f.Celsius = Celsius(value)return nilcase "F", "°F":f.Celsius = FToC(Fahrenheit(value))return nil}return fmt.Errorf("invalid temperature %q", s)
}
  • 定义celsiusFlag类型 ,内嵌Celsius类型 ,因已有String方法 ,只需实现Set方法来满足flag.Value接口 。Set方法中 ,使用fmt.Sscanf从输入字符串解析浮点值和单位 ,根据单位(CF )进行摄氏温度和华氏温度转换 ,若输入无效则返回错误 。
func CelsiusFlag(name string, value Celsius, usage string) *Celsius {f := celsiusFlag{value}flag.CommandLine.Var(&f, name, usage)return &f.Celsius
}
  • CelsiusFlag函数封装相关逻辑 ,返回指向内嵌Celsius字段的指针 ,并通过flag.CommandLine.Var方法将标志加入命令行标记集合 。

接口值

接口值由具体类型(动态类型 )和该类型对应的值(动态值 )两部分组成 。在 Go 语言中 ,类型是编译时概念 ,不是值 ,接口值的类型部分用类型描述符表示 。

var w io.Writer
w = os.Stdout
w = new(bytes.Buffer)
w = nil
  • 初始化:接口变量初始化为零值 ,即动态类型和动态值都为nil ,此时为nil接口值 ,调用其方法会导致程序崩溃 。

image.png

  • *os.File类型的os.Stdout赋给io.Writer接口变量w ,会隐式转换 ,动态类型设为*os.File ,动态值为os.Stdout副本 ,调用w.Write实际调用(*os.File).Write

image.png

  • *bytes.Buffer类型的new(bytes.Buffer)赋给w ,动态类型变为*bytes.Buffer ,动态值为新分配缓冲区指针 ,调用w.Write会追加内容到缓冲区 。

  • 再将nil赋给w ,动态类型和动态值又变回nil

  • 比较:接口值可使用==!=操作符比较 ,两个接口值nil或动态类型完全一致且动态值相等时相等 。但当动态类型一致 ,动态值不可比较(如 slice )时 ,比较会导致崩溃 。接口值可作为map的键和switch语句操作数 ,但需注意动态值的可比较性 。

  • 获取动态类型:处理错误或调试时 ,可使用fmt包的%T格式化动词获取接口值的动态类型 ,其内部通过反射实现 。

注意:含有空指针的非空接口

image.png

空接口值不包含任何信息 ,动态类型和动态值均为nil ;而仅动态值为nil ,动态类型非nil的接口值 ,是含有空指针的非空接口 ,二者存在微妙区别 ,容易造成编程陷阱 。

const debug = truefunc main() {var buf *bytes.Bufferif debug {buf = new(bytes.Buffer) // 启用输出收集}f(buf) // 注意: 微妙的错误if debug {//...使用 buf...}
}// 如果 out 不是 nil, 那么会向其写入输出的数据
func f(out io.Writer) {//...其他代码...if out!= nil {out.Write([]byte("done!\n"))}
}
  • 示例:程序中当debugtrue时 ,主函数创建*bytes.Buffer指针buf ,并在debugtrue时初始化为new(bytes.Buffer) ,然后调用函数ff接收io.Writer接口类型参数out ,在outnil时向其写入数据 。
  • 分析:当debugfalse时 ,bufnil ,将其传给f ,此时out的动态类型是*bytes.Buffer ,动态值为nil ,是含有空指针的非空接口 。调用out.Write时 ,由于*bytes.Buffer类型的Write方法要求接收者非空 ,会导致程序崩溃 。这是因为虽指针拥有的方法满足接口 ,但违背了方法隐式前置条件 。
var buf io.Writer
if debug {buf = new(bytes.Buffer) // 启用输出收集
}
f(buf) // OK
  • 解决:将main函数中buf类型修改为io.Writer ,这样在debugfalse时 ,bufnil ,符合io.Writer接口的零值状态 ,调用f时可避免将功能不完整的值传给接口 ,防止程序崩溃 。

使用 sort.Interface 来排序

package sorttype Interface interface {Len() intLess(i, j int) bool // i, j 是序列元素的下标Swap(i, j int)
}
  • 作用sort包提供通用排序功能 ,sort.Interface接口用于指定通用排序算法与具体序列类型间的协议 ,使排序算法不依赖序列和元素具体布局 ,实现灵活排序 。
  • 定义:该接口有Len(返回序列长度 )、Less(比较元素大小 ,返回bool )、Swap(交换元素 )三个方法 。
type StringSlice []stringfunc (p StringSlice) Len() int {return len(p)
}
func (p StringSlice) Less(i, j int) bool {return p[i] < p[j]
}
func (p StringSlice) Swap(i, j int) {p[i], p[j] = p[j], p[i]
}sort.Sort(StringSlice(names))
  • 示例:定义StringSlice类型 ,实现sort.Interface接口的三个方法 ,通过sort.Sort(StringSlice(names))可对字符串切片names排序 。sort包还提供StringSlice类型和Strings函数 ,简化为sort.Strings(names) ,且这种技术可复用 ,添加额外逻辑实现不同排序方式 。

复杂数据结构排序示例

type Track struct {Title  stringArtist stringAlbum  stringYear   intLength time.Duration
}var tracks = []*Track{{"Go", "Delilah", "From the Roots Up", 2012, length("3m38s")},{"Go Ahead", "Alicia Keys", "As I Am", 2007, length("4m36s")},{"Ready 2 Go", "Martin Solveig", "Smash", 2011, length("4m24s")},{"Go", "Moby", "Moby", 1992, length("3m37s")},
}func length(s string) time.Duration {d, err := time.ParseDuration(s)if err!= nil {panic(s)}return d
}func printTracks(tracks []*Track) {const format = "%v\t%v\t%v\t%v\t%v\n"tw := tabwriter.NewWriter(os.Stdout, 0, 8, 2,'', 0)fmt.Fprintf(tw, format, "Title", "Artist", "Album", "Year", "Length")fmt.Fprintf(tw, format, "----", "------", "-----", "----", "------")for _, t := range tracks {fmt.Fprintf(tw, format, t.Title, t.Artist, t.Album, t.Year, t.Length)}tw.Flush() // 计算各列宽度并输出表格
}type byArtist []*Trackfunc (x byArtist) Len() int {return len(x)
}
func (x byArtist) Less(i, j int) bool {return x[i].Artist < x[j].Artist
}
func (x byArtist) Swap(i, j int) {x[i], x[j] = x[j], x[i]
}sort.Sort(byArtist(tracks))type reverse struct{ i interface{} }func (r reverse) Less(i, j int) bool {return r.i.(Interface).Less(j, i)
}
func Reverse(data Interface) Interface {return reverse{data}
}sort.Reverse(byArtist(tracks))
  • 音乐播放列表排序:定义Track结构体表示音乐曲目 ,tracks*Track指针切片 。要按Artist字段排序 ,定义byArtist类型实现sort.Interface接口 ,通过sort.Sort(byArtist(tracks))排序 。若需反向排序 ,使用sort.Reverse函数 ,其内部基于嵌入sort.Interfacereverse类型实现 。
type byYear []*Trackfunc (x byYear) Len() int {return len(x)
}
func (x byYear) Less(i, j int) bool {return x[i].Year < x[j].Year
}
func (x byYear) Swap(i, j int) {x[i], x[j] = x[j], x[i]
}sort.Sort(byYear(tracks))type customSort struct {t    []*Trackless func(x, y *Track) bool
}func (x customSort) Len() int {return len(x.t)
}
func (x customSort) Less(i, j int) bool {return x.less(x.t[i], x.t[j])
}
func (x customSort) Swap(i, j int) {x.t[i], x.t[j] = x.t[j], x.t[i]
}sort.Sort(customSort{tracks, func(x, y *Track) bool {if x.Title!= y.Title {return x.Title < y.Title}if x.Year!= y.Year {return x.Year < y.Year}if x.Length!= y.Length {return x.Length < y.Length}return false
}})func IntsAreSorted(values []int) bool {for i := 1; i < len(values); i++ {if values[i] < values[i-1] {return false}}return true
}
  • 按其他字段排序:如按Year字段排序 ,定义byYear类型实现接口方法 ,调用sort.Sort(byYear(tracks))customSort结构体类型 ,组合slice和函数 ,只需定义比较函数就能实现新排序 。

http.Handler 接口

package httptype Handler interface {ServeHTTP(w ResponseWriter, r *Request)
}func ListenAndServe(address string, h Handler) error

http.Handler接口定义了ServeHTTP方法 ,接收http.ResponseWriter*http.Request作为参数 。ListenAndServe函数用于启动服务器 ,接收服务器地址和Handler接口实例 ,持续运行处理请求 ,直到出错。

error 接口

type error interface {Error() string
}

error是一个接口类型 ,定义了Error()方法 ,返回类型为string ,用于返回错误消息 。

创建error实例的方法

package errorsfunc New(text string) error { return &errorString{text} }type errorString struct { text string }func (e *errorString) Error() string { return e.text }
  • errors.New函数:构造error最简单的方式 ,传入指定错误消息 ,返回包含该消息的error实例 。error包中New函数通过创建errorString结构体指针实现 ,这样可避免布局变更问题 ,且保证每次创建的error实例不相等 。
func Errorf(format string, args...interface{}) error {return errors.New(Sprintf(format, args...))
}
  • fmt.Errorf函数:更常用的封装函数 ,除创建error实例外 ,还提供字符串格式化功能 ,其内部调用errors.New

不同的error类型实现

  • errorString类型:满足error接口的最基本类型 ,通过结构体指针实现 。
  • syscall.Errno类型syscall包定义的数字类型 ,在 UNIX 平台上 ,其Error方法从字符串表格中查询错误消息 ,是系统调用错误的高效表示 ,也满足error接口 。

类型断言

类型断言是作用于接口值的操作 ,形式为x.(T)x是接口类型表达式 ,T是断言类型 ,用于检查操作数的动态类型是否满足指定断言类型 。

var w io.Writer
w = os.Stdout
f := w.(*os.File)    // 成功: f == os.Stdout
c := w.(*bytes.Buffer) // 崩溃: 接口持有的是 *os.File,不是 *bytes.Buffer
  • 断言类型为具体类型:若T是具体类型 ,类型断言检查x的动态类型是否为T 。检查成功 ,结果为x的动态值 ,类型为T ;检查失败 ,操作崩溃 。如w.(*os.File) ,当w动态类型是*os.File时成功 ,否则崩溃 。
var w io.Writer
w = os.Stdout
rw := w.(io.ReadWriter) // 成功: *os.File 有 Read 和 Write 方法
w = new(ByteCounter)
rw = w.(io.ReadWriter) // 崩溃: *ByteCounter 没有 Read 方法
  • 断言类型为接口类型:若T是接口类型 ,检查x的动态类型是否满足T 。成功则结果仍是接口值 ,动态类型和值不变 ,但接口方法集可能变化 。如w.(io.ReadWriter) ,若w动态类型满足该接口 ,可获取更多方法 。
w = rw               // io.ReadWriter 可以赋给 io.Writer
w = rw.(io.Writer) // 仅当 rw == nil 时失败var w io.Writer = os.Stdout
f, ok := w.(*os.File)    // 成功: ok, f == os.Stdout
b, ok := w.(*bytes.Buffer) // 失败:!ok, b == nil
  • 空接口值:操作数为空接口值时 ,类型断言失败 。一般很少从接口类型向更宽松类型做断言 ,多数情况与赋值一致 ,仅在操作nil时有区别 。
if w, ok := w.(*os.File); ok {//...use w...
}
  • 双返回值形式:在需检测断言是否成功的场景 ,类型断言可返回两个值 ,第二个布尔值表示断言是否成功 。如f, ok := w.(*os.File)oktrue时 ,f为断言成功后的值 ,否则f为断言类型零值 ,常用于if表达式中进行条件操作 。 当操作数是变量时 ,可能出现返回值覆盖原有值的情况 。

使用类型断言来识别错误

package osfunc IsExist(err error) bool
func IsNotExist(err error) bool
func IsPermission(err error) boolfunc IsNotExist(err error) bool {// 注意: 不健壮return strings.Contains(err.Error(), "file does not exist")
}

os包中文件操作返回的错误通常因文件已存储、文件没找到、权限不足三类原因产生 。os包提供IsExistIsNotExistIsPermission等函数对错误分类 。简单通过检查错误消息字符串判断错误的方法不可靠 ,因不同平台错误消息可能不同 ,在生产级代码中不够健壮 。

结构化错误类型

package os// PathError 记录了错误以及错误相关的操作和文件路径
type PathError struct {Op   stringPath stringErr  error
}func (e *PathError) Error() string {return e.Op + " " + e.Path + ": " + e.Err.Error()
}

os包定义PathError类型表示与文件路径相关操作的错误 ,包含Op(操作 )、Path(路径 )、Err(底层错误 )字段 ,还有类似的LinkErrorPathErrorError方法拼接字段返回错误字符串 ,其结构保留底层信息 。很多客户端忽略PathError ,直接调用Error方法处理错误 ,但对于需区分错误的客户端 ,可使用类型断言检查错误类型 。

在错误识别中的应用

var ErrNotExist = errors.New("file does not exist")// IsNotExist 返回一个布尔值,该值表明错误是否代表文件或目录不存在
// report that a file or directory does not exist. It is satisfied by
// ErrNotExist 和其他一些系统调用错误会返回 true
func IsNotExist(err error) bool {if pe, ok := err.(*PathError); ok {err = pe.Err}return err == syscall.ENOENT || err == ErrNotExist
}// 实际使用
_, err := os.Open("/no/such/file")
fmt.Println(os.IsNotExist(err)) // "true"

IsNotExist函数为例 ,通过类型断言err.(*PathError)判断错误是否为PathError类型 ,若成功 ,进一步判断底层错误是否为syscall.ENOENT或等于自定义的ErrNotExist ,以此确定错误是否代表文件或目录不存在 ,展示了类型断言在准确识别错误类型中的作用 。 错误识别应在失败操作发生时处理 ,避免错误信息合并导致结构信息丢失 。

通过接口类型断言来查询特性

性能优化场景引入

func writeHeader(w io.Writer, contentType string) error {if _, err := w.Write([]byte("Content-Type: ")); err!= nil {return err}if _, err := w.Write([]byte(contentType)); err!= nil {return err}//...
}// writeString 将 s 写入 w
// 如果 w 有 WriteString 方法,那么将直接调用该方法
func writeString(w io.Writer, s string) (n int, err error) {type stringWriter interface {WriteString(string) (n int, err error)}if sw, ok := w.(stringWriter); ok {return sw.WriteString(s) // 避免了内存复制}return w.Write([]byte(s)) // 分配了临时内存
}func writeHeader(w io.Writer, contentType string) error {if _, err := writeString(w, "Content-Type: "); err!= nil {return err}if _, err := writeString(w, contentType); err!= nil {return err}//...
}

在类似 Web 服务器向客户端响应 HTTP 头字段的场景中 ,io.Writer用于写入响应内容 。因Write方法需字节切片 ,将字符串转换为字节切片会有内存分配和复制开销 ,影响性能 。而很多实现io.Writer的类型(如*bytes.Buffer*os.File等 )有WriteString方法 ,可避免临时内存分配 。

利用接口类型断言优化

interface {io.WriterWriteString(s string) (n int, err error)
}

定义stringWriter接口 ,仅包含WriteString方法 。通过类型断言w.(stringWriter)判断io.Writer接口变量w的动态类型是否满足该接口 。若满足 ,直接调用WriteString方法避免内存复制 ;若不满足 ,再使用Write方法 。将检查逻辑封装在writeString工具函数 ,并在writeHeader等函数中调用 ,避免代码重复 。

接口特性约定与应用拓展

这种方式依赖于一种隐式约定 ,即若类型满足特定接口 ,其WriteString方法与Write([]byte(s))等效 。此技术不仅适用于io包相关接口 ,在fmt.Printf内部 ,也通过类型断言从通用类型interface{}中识别errorfmt.Stringer接口 ,确定格式化方法 ,若不满足则用反射处理其他类型 。

类型分支

接口的两种风格

  • 第一种风格:像io.Readerio.Writer等接口 ,突出满足接口的具体类型之间的相似性 ,隐藏具体类型的布局和特有功能 ,强调接口方法 。
  • 第二种风格:将接口作为具体类型的联合 ,利用接口值容纳多种具体类型的能力 ,运行时通过类型断言区分类型并处理 ,强调具体类型 ,不注重信息隐藏 ,这种风格称为可识别联合 。、
import "database/sql"func listTracks(db sql.DB, artist string, minYear, maxYear int) {result, err := db.Exec("SELECT * FROM tracks WHERE artist =? AND? <= year AND year <=?",artist, minYear, maxYear)//...
}func sqlQuote(x interface{}) string {if x == nil {return "NULL"} else if _, ok := x.(int); ok {return fmt.Sprintf("%d", x)} else if _, ok := x.(uint); ok {return fmt.Sprintf("%d", x)} else if b, ok := x.(bool); ok {if b {return "TRUE"}return "FALSE"} else if s, ok := x.(string); ok {return sqlQuoteString(s) // (not shown)} else {panic(fmt.Sprintf("unexpected type %T: %v", x, x))}
}// 优化
func sqlQuote(x interface{}) string {switch x := x.(type) {case nil:return "NULL"case int, uint:return fmt.Sprintf("%d", x) // 这里 x 类型为 interface{}case bool:if x {return "TRUE"}return "FALSE"case string:return sqlQuoteString(x) // (未显示具体代码)default:panic(fmt.Sprintf("unexpected type %T: %v", x, x))}
}

示例:以数据库 SQL 查询 API 为例 ,sqlQuote函数将参数值转为 SQL 字面量 ,原代码使用一系列类型断言的if - else语句 ,可通过类型分支(type switch )简化 。类型分支语句switch x.(type) ,操作数为接口值 ,分支基于接口值的动态类型判定 ,nil分支需x == nildefault分支在其他分支不满足时执行 ,且不允许使用fallthrough 。类型分支还有扩展形式switch x := x.(type) ,能将提取的原始值绑定到新变量 ,使代码更清晰 ,如改写后的sqlQuote函数 。 类型分支可方便处理多种类型 ,但传入类型不匹配时会崩溃 。

一些建议

避免不必要的接口抽象

新手设计新包时 ,常先创建大量接口 ,再定义其具体实现 ,但当接口只有一个实现时 ,这种抽象多余且有运行时成本 。可利用导出机制控制类型的方法或字段对外可见性 ,仅在有多个具体类型需按统一方式处理时才使用接口 。

  • 特例:若接口和类型实现因依赖关系不能在同一包 ,即便接口只有一个具体实现 ,也可用接口解耦不同包 。
  • 原则:接口因抽象多个类型实现细节而存在 ,好的接口设计应简单 ,方法少 ,如io.Writerfmt.Stringer 。设计新类型时 ,越小的接口越易满足 ,建议仅定义必要的接口 。

参考资料:《Go程序设计语言》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/76833.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

一、Appium环境安装

找了一圈操作手机的工具或软件&#xff0c;踩了好多坑&#xff0c;最后决定用这个工具(影刀RPA手机用的也是这个)&#xff0c;目前最新的版本是v2.17.1&#xff0c;是基于nodejs环境的&#xff0c;有两种方式&#xff0c;我只试了第一种方式&#xff0c;第二种方式应该是比较简…

【玩转全栈】—— Django 连接 vue3 保姆级教程,前后端分离式项目2025年4月最新!!!

本文基于之前的一个旅游网站&#xff0c;实现 Django 连接 vue3&#xff0c;使 vue3 能携带 CSRF Token 发送 axios 请求给后端&#xff0c;后端再响应数据给前端。想要源码直接滑倒底部。 目录 实现效果 解决跨域 获取 csrf-token 什么是 csrf-token &#xff1f; CSRF攻击的…

dify部署,ollama部署,拉取模型,创建ai聊天应用

dify下载安装 dify1.0.1 windos安装包百度云盘地址 通过网盘分享的文件&#xff1a;dify-1.0.1.zip 链接: 百度网盘 请输入提取码 提取码: 1234 dify安装包 linux安装包百度云盘地址 通过网盘分享的文件&#xff1a;dify-1.0.1.tar.gz 链接: 百度网盘 请输入提取码 提取码…

docx文档转为pdf文件响应前端

1、转换文件&#xff08;docx~pdf&#xff09; 1.引入pom依赖 <dependency><groupId>com.aspose</groupId><artifactId>aspose-words</artifactId><version>20.12.0</version> </dependency>2.读取docx文档数据-转换 // 初…

网络安全中信息收集需要收集哪些信息了?汇总

目录 1. 域名信息 2. IP地址与网络信息 3. 备案与注册信息 4. Web应用与中间件信息 5. 操作系统与服务器信息 6. 敏感文件与配置文件 7. 社交工程信息 8. 证书与加密信息 9. API与接口信息 10. 外部威胁情报 11. 历史数据与缓存 常用工具与技术&#xff1a; 在网络…

【锂电池SOH预测】PSO-BP锂电池健康状态预测,锂电池SOH预测(Matlab完整源码和数据)

预测效果 基于PSO-BP算法的锂电池健康状态预测研究 一、引言 1.1 研究背景与意义 在当今社会&#xff0c;锂电池凭借其高能量密度、长寿命及环境友好等特性&#xff0c;在现代能源系统中占据着举足轻重的地位。从消费电子领域如智能手机、笔记本电脑&#xff0c;到动力领域中…

智能车摄像头开源—9 动态权、模糊PID、速度决策、路径优化

目录 一、前言 二、动态权 1.概述 2.偏差值加动态权 三、模糊PID 四、速度决策 1.曲率计算 2.速度拟合 3.速度控制 五、路径 六、国赛视频 一、前言 在前中期通过识别直道、弯道等元素可进行加减速操作实现速度的控制&#xff0c;可进一步缩减一圈的运行速度&#xff…

过往记录系列 篇五:市场黑天鹅事件历史梳理

文章目录 系列文章文章地址文章摘要文章预览系列文章 过往记录系列 篇一:牛市板块轮动顺序梳理 过往记录系列 篇二:新年1月份(至春节前)行情历史梳理 过往记录系列 篇三:春节行情历史梳理 过往记录系列 篇四:年报月行情历史梳理 文章地址 原文审核不通过(理由:“违反…

Mysql--基础知识点--85.1--Innodb自适应哈希索引

1. 自适应哈希索引的用途 InnoDB 的自适应哈希索引&#xff08;Adaptive Hash Index, AHI&#xff09;是 MySQL 数据库引擎中一项智能优化查询性能的功能。其核心作用如下&#xff1a; 加速等值查询 哈希索引通过哈希函数将键映射到固定位置&#xff0c;实现 O(1) 时间复杂度的…

SQL优化技术分享:从 321 秒到 0.2 秒的性能飞跃 —— 基于 PawSQL 的 TPCH 查询优化实战

在数据库性能优化领域&#xff0c;TPC-H 测试集是一个经典的基准测试工具&#xff0c;常用于评估数据库系统的查询性能。本文将基于 TPCH 测试集中的第 20个查询&#xff0c;结合 PawSQL 自动化优化工具&#xff0c;详细分析如何通过 SQL 重写和索引设计&#xff0c;将查询性能…

SpringBoot3-web开发笔记(下)

内容协商 实现&#xff1a;一套系统适配多端数据返回 多端内容适配&#xff1a; 1. 默认规则 SpringBoot 多端内容适配。 基于请求头内容协商&#xff1a;&#xff08;默认开启&#xff09; 客户端向服务端发送请求&#xff0c;携带HTTP标准的Accept请求头。 Accept: applica…

Graylog 索引配置详解与优化建议

Graylog 索引配置详解与优化建议 &#x1f680; 前言一、索引集基础信息 &#x1f4da;二、分片&#xff08;Shards&#xff09;与副本&#xff08;Replicas&#xff09;设置 ⚙️1. 分片 (Shards)2. 副本 (Replicas) 三、 字段类型刷新间隔&#xff08;Field Type Refresh Int…

数据结构*包装类泛型

包装类 什么是包装类 在讲基本数据类型的时候&#xff0c;有提到过包装类。 基本数据类型包装类byteByteshortShortintIntegerlongLongfloatFloatdoubleDoublecharCharacterbooleanBoolean 我们知道&#xff1a;基本数据类型并不是对象&#xff0c;没有对象所具有的方法和属…

【JDBC-54.1】MySQL JDBC连接字符串常用参数详解

在Java应用程序中连接MySQL数据库时&#xff0c;JDBC连接字符串是建立连接的关键。一个配置得当的连接字符串不仅能确保连接成功&#xff0c;还能优化性能、增强安全性并处理各种连接场景。本文将深入探讨MySQL JDBC连接字符串的常用参数及其最佳实践。 1. 基本连接字符串格式…

[ctfshow web入门] web37

信息收集 题目有了变化&#xff0c;include$c if(isset($_GET[c])){$c $_GET[c];if(!preg_match("/flag/i", $c)){include($c);echo $flag;}}else{highlight_file(__FILE__); }解题 通过协议解题 参考[ctfshow web入门] web31 同样是include&#xff0c;之前的方…

Linux 调试代码工具:gdb

文章目录 一、debug vs release&#xff1a;两种程序形态的本质差异1. 什么是 debug 与 release&#xff1f;2. 核心差异对比 二、为什么需要 debug&#xff1a;从项目生命周期看调试价值1. 项目开发流程中的调试闭环&#xff08;流程图示意&#xff09;2. Debug 的核心意义与目…

Python设计模式:命令模式

1. 什么是命令模式&#xff1f; 命令模式是一种行为设计模式&#xff0c;它将请求封装为一个对象&#xff0c;从而使您能够使用不同的请求、队列或日志请求&#xff0c;以及支持可撤销操作。 命令模式的核心思想是将请求的发送者与请求的接收者解耦&#xff0c;使得两者之间的…

nlp面试重点

深度学习基本原理&#xff1a;梯度下降公式&#xff0c;将损失函数越来越小&#xff0c;最终预测值和实际值误差比较小。 交叉熵&#xff1a;-p(x)logq(x)&#xff0c;p(x)是one-hot形式。如果不使用softmax计算交叉熵&#xff0c;是不行的。损失函数可能会非常大&#xff0c;…

Leetcode:二叉树

94. 二叉树的中序遍历 class Solution {public List<Integer> inorderTraversal(TreeNode root) {TreeNode cur root;Stack<TreeNode> stack new Stack<>();List<Integer> list new ArrayList<>();while (!stack.isEmpty() || cur ! null) {…

SQL:Constraint(约束)

目录 &#x1f3af; 什么是 Constraint&#xff1f; MySQL 中常见的约束类型&#xff1a; 1. PRIMARY KEY 2. FOREIGN KEY 3. UNIQUE 4. NOT NULL 5. DEFAULT 6. CHECK&#xff08;MySQL 8.0&#xff09; 7. AUTO_INCREMENT &#x1f3af; 什么是 Constraint&#xf…