Home  >  Article  >  Backend Development  >  How to avoid code duplication that requires dynamic selection of types?

How to avoid code duplication that requires dynamic selection of types?

王林
王林forward
2024-02-10 14:06:071112browse

How to avoid code duplication that requires dynamic selection of types?

When writing code, we often encounter situations where we need to choose different types of code based on different conditions. In this case, without proper handling, the code may become verbose and repetitive. So, how to avoid this code duplication? PHP editor Baicao has brought you some simple and effective solutions, let us take a look!

Question content

The following code is a simplified example of a video stream parser. The input is binary data containing video and audio frames. Each frame consists of the following parts:

  1. Frame type flag, indicating whether it is a video frame or an audio frame
  2. title
  3. Payload

The goal is to parse the stream, extracting fields from headers and payload.

So, the first method is:

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)
    }
}

It works, but I don't like the code duplication in the switch case. Essentially, we have to repeat the same code, just because the frame type is different.

One way to try and avoid duplication is:

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)
    }
}

That is, we implement dynamic type selection by introducing a TMap mapping that allows the creation of instances of the correct structure based on the frame type. However, this solution comes at the cost of repeating the GetLength() method for each frame type.

What I find very disturbing is that there seems to be no way to completely avoid duplication. Am I missing something, or is it just a limitation of the language?

This is a related question (actually triggered by the same problem), however, its premise ignores the need for dynamic type selection, so the accepted solution (using generics) doesn't help.

Workaround

King's answer requires that it be repeated for each integer type used to encode the length. Mondarin's answer Use the terrible reflect package. Here is the solution to avoid both problems. This answer is based on King's answer.

Use the GetLength() method to declare a generic type.

type Length[T uint8 | uint16 | uint32 | uint64] struct { Length T }

func (l Length[T]) GetLength() int { return int(l.Length) }

Remove GetLength method from each header type. Embed a common length type in each header type:

type HeaderVideo struct {
    Width  uint16
    Height uint16
    Length[uint32]
}

type HeaderAudio struct {
    SampleRate uint16
    Length[uint16]
}

State TMap as in the question. The GetLength method is provided by the embedded field.

var TMap = map[Type]func() Header{
    Video: func() Header { return &HeaderVideo{} },
    Audio: func() Header { return &HeaderAudio{} },
}

https://www.php.cn/link/ceb9f6b8ffa77c49b6b4570ea19c76bf

(Like the code in the question, this answer uses the reflect package indirectly through the binary.Read function. The reflect package is a great tool for keeping your code dry .)

The above is the detailed content of How to avoid code duplication that requires dynamic selection of types?. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:stackoverflow.com. If there is any infringement, please contact admin@php.cn delete