在编写代码时,我们经常会遇到需要根据不同条件选择不同类型的代码的情况。这种情况下,如果没有合适的处理方式,代码可能会变得冗长而重复。那么,如何避免这种代码重复呢?php小编百草为大家带来了一些简单而有效的解决方案,让我们一起来看看吧!
问题内容
以下代码是视频流解析器的简化示例。输入是包含视频和音频帧的二进制数据。每个框架由以下部分组成:
- 帧类型标志,指示是视频帧还是音频帧
- 标题
- 有效负载
目标是解析流,从标头和有效负载中提取字段。
所以,第一种方法是:
package main
import (
"fmt"
"encoding/binary"
"bytes"
)
type Type byte
const (
Video Type = 0xFC
Audio Type = 0xFA
)
var HMap = map[Type]string {
Video: "Video",
Audio: "Audio",
}
type CommonHeader struct {
Type Type
}
type HeaderVideo struct {
Width uint16
Height uint16
Length uint32
}
type HeaderAudio struct {
SampleRate uint16
Length uint16
}
func main() {
data := bytes.NewReader([]byte{0xFC, 0x80, 0x07, 0x38, 0x04, 0x02, 0x00, 0x00, 0x00, 0xFF, 0xAF, 0xFA, 0x10, 0x00, 0x01, 0x00, 0xFF})
var cHeader CommonHeader
var dataLength int
for {
err := binary.Read(data, binary.LittleEndian, &cHeader)
if err != nil {
break
}
fmt.Println(HMap[cHeader.Type])
switch cHeader.Type {
case Video:
var info HeaderVideo
binary.Read(data, binary.LittleEndian, &info)
dataLength = int(info.Length)
fmt.Println(info)
case Audio:
var info HeaderAudio
binary.Read(data, binary.LittleEndian, &info)
dataLength = int(info.Length)
fmt.Println(info)
}
payload := make([]byte, dataLength)
data.Read(payload)
fmt.Println(payload)
}
}
它有效,但我不喜欢 switch
案例中的代码重复。本质上,我们必须重复相同的代码,只是因为帧类型不同。
尝试避免重复的一种方法是:
package main
import (
"fmt"
"encoding/binary"
"bytes"
)
type Type byte
const (
Video Type = 0xFC
Audio Type = 0xFA
)
var HMap = map[Type]string {
Video: "Video",
Audio: "Audio",
}
type CommonHeader struct {
Type Type
}
type Header interface {
GetLength() int
}
type HeaderVideo struct {
Width uint16
Height uint16
Length uint32
}
func (h HeaderVideo) GetLength() int {
return int(h.Length)
}
type HeaderAudio struct {
SampleRate uint16
Length uint16
}
func (h HeaderAudio) GetLength() int {
return int(h.Length)
}
var TMap = map[Type]func() Header {
Video: func() Header { return &HeaderVideo{} },
Audio: func() Header { return &HeaderAudio{} },
}
func main() {
data := bytes.NewReader([]byte{0xFC, 0x80, 0x07, 0x38, 0x04, 0x02, 0x00, 0x00, 0x00, 0xFF, 0xAF, 0xFA, 0x10, 0x00, 0x01, 0x00, 0xFF})
var cHeader CommonHeader
for {
err := binary.Read(data, binary.LittleEndian, &cHeader)
if err != nil {
break
}
fmt.Println(HMap[cHeader.Type])
info := TMap[cHeader.Type]()
binary.Read(data, binary.LittleEndian, info)
fmt.Println(info)
payload := make([]byte, info.GetLength())
data.Read(payload)
fmt.Println(payload)
}
}
也就是说,我们通过引入 TMap
映射来实现动态类型选择,该映射允许根据帧类型创建正确结构的实例。但是,此解决方案的代价是对每种帧类型重复 GetLength()
方法。
我觉得很令人不安的是,似乎没有办法完全避免重复。 我是否遗漏了什么,或者只是语言的限制?
这是一个相关的问题(实际上是由同一个问题触发的),但是,它的前提忽略了动态类型选择的需要,因此公认的解决方案(使用泛型)没有帮助。
解决方法
King 的答案要求对用于编码长度的每个整数类型进行重复。 Mondarin 的答案 使用可怕的 reflect
包。这是避免这两个问题的解决方案。这个答案是基于国王的答案。
使用 GetLength() 方法声明泛型类型。
type Length[T uint8 | uint16 | uint32 | uint64] struct { Length T }
func (l Length[T]) GetLength() int { return int(l.Length) }
从每个标头类型中删除 GetLength 方法。在每个标头类型中嵌入通用长度类型:
type HeaderVideo struct {
Width uint16
Height uint16
Length[uint32]
}
type HeaderAudio struct {
SampleRate uint16
Length[uint16]
}
在问题中声明 TMap
as。 GetLength
方法由嵌入字段提供。
var TMap = map[Type]func() Header{
Video: func() Header { return &HeaderVideo{} },
Audio: func() Header { return &HeaderAudio{} },
}
https://www.php.cn/link/ceb9f6b8ffa77c49b6b4570ea19c76bf
(与问题中的代码一样,此答案通过 binary.Read
函数间接使用 reflect
包。reflect
包是保持代码干燥的好工具。)
以上就是如何避免需要动态选择类型的代码重复?的详细内容,更多请关注编程网其它相关文章!