需要动态选择类型时如何避免代码重复?

问题描述 投票:0回答:1

以下代码是视频流解析器的简化示例。输入是包含视频和音频帧的二进制数据。每个框架由以下部分组成:

  1. 帧类型标志,指示是视频帧还是音频帧
  2. 标题
  3. 有效负载

目标是解析流,从标头和有效负载中提取字段。

所以,第一种方法是:

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()
方法。

我觉得很令人不安的是,似乎没有办法完全避免重复。 我错过了什么,还是只是语言的限制?

这里有一个相关问题(实际上是由同一个问题触发的),但是,它的前提忽略了动态类型选择的需要,因此接受的解决方案(使用泛型)没有帮助。

go dry
1个回答
0
投票

您可以将流处理的细节包装到自定义类型的方法中:

package main

import (
    "bytes"
    "encoding/binary"
    "fmt"
    "io"
    "log"
)

type Type byte

const (
    Video Type = 0xFC
    Audio Type = 0xFA
)

var HMap = map[Type]string{
    Video: "Video",
    Audio: "Audio",
}

type CommonHeader struct {
    Type Type
}

func (ch CommonHeader) GetMediaHeader() MediaHeader {
    switch ch.Type {
    case Video:
        return &HeaderVideo{}
    case Audio:
        return &HeaderAudio{}
    default:
        panic("Unsupported media type")
    }
}

func (ch *CommonHeader) Fill(r io.Reader, o binary.ByteOrder) error {
    return binary.Read(r, o, ch)
}

func (ch CommonHeader) String() string {
    return HMap[ch.Type]
}

type MediaHeader interface {
    GetLength() int
    Fill(io.Reader, binary.ByteOrder) error
}

type HeaderVideo struct {
    Width  uint16
    Height uint16
    Length uint32
}

// Fill implements MediaHeader.
func (hv *HeaderVideo) Fill(r io.Reader, o binary.ByteOrder) error {
    return binary.Read(r, o, hv)
}

func (hv HeaderVideo) GetLength() int {
    return int(hv.Length)
}

func (hv HeaderVideo) String() string {
    return fmt.Sprintf("%#v", hv)
}

var _ MediaHeader = &HeaderVideo{}

type HeaderAudio struct {
    SampleRate uint16
    Length     uint16
}

func (ha *HeaderAudio) Fill(r io.Reader, o binary.ByteOrder) error {
    return binary.Read(r, o, ha)
}

func (ha HeaderAudio) GetLength() int {
    return int(ha.Length)
}

func (ha HeaderAudio) String() string {
    return fmt.Sprintf("%#v", ha)
}

var _ MediaHeader = &HeaderAudio{}

func main() {
    data := bytes.NewReader([]byte{0xFC, 0x80, 0x07, 0x38, 0x04, 0x02, 0x00, 0x00, 0x00, 0xFF, 0xAF, 0xFA, 0x10, 0x00, 0x01, 0x00, 0xFF})
    cHeader := &CommonHeader{}
    var dataLength int
    for {
        err := cHeader.Fill(data, binary.LittleEndian)
        if err != nil {
            log.Println(err)
            break
        }
        fmt.Println("cHeader: ", cHeader)
        mediaHeader := cHeader.GetMediaHeader()
        err = mediaHeader.Fill(data, binary.LittleEndian)
        if err != nil {
            log.Println(err)
            break
        }
        dataLength = mediaHeader.GetLength()
        fmt.Println("Media header: ", mediaHeader)

        payload := make([]byte, dataLength)
        data.Read(payload)
        fmt.Println("Payload: ", payload)
    }
}

关键特征是

cHeader.GetMediaHeader()
工厂方法。它知道流类型并生成正确的标头类型。

每种媒体标头类型都知道如何解析流(方法

Fill
)以及如何提取有效负载长度。有了接口
MediaHeader
main
代码精简,没有
switch
和重复的业务逻辑。

© www.soinside.com 2019 - 2024. All rights reserved.