Modo idiomatico di scrivere un sistema GUI in Go?

6

Sto scrivendo un piccolo sistema GUI per il mio gioco in Go. Finora la mia struttura è un po 'come questa:

type Component interface {
    Update()
    Render(ctx)

    Translate()

    GetComponent() []Component
    AddComponent(c Component)

    AddInput()
    GetInput()
}

E poi direi un pannello che implementa questi e un pulsante, ecc. Un problema che sto affrontando è che è generalmente piuttosto confuso e confuso per comprendere la struttura di questa cosa. Non so come scrivere codice Go idiomatico, quindi sto scrivendo come se fosse Java o qualcosa del genere.

Qualcuno può raccomandare un approccio migliore che mantenga intatto il mio equilibrio mentale? Grazie

Per elaborare, questo è particolarmente noioso:

func (f *Button) Update() {
    for _, c := range f.components {
        c.Update()
    }
}

Devo farlo per ogni componente che creo, così come devo scrivere

func (f Button) AddComponent(c Component) {
    f.components = append(f.components, c)
}

Mi sento come se stessi ripetendo molto!

    
posta flooblebit 24.11.2016 - 11:19
fonte

1 risposta

3

Potresti voler estrarre le funzionalità comuni di quei metodi di interfaccia per separare classi / funzioni e implementare solo gestori per la logica specifica dei componenti.

Un esempio leggermente semplificato:

type Component interface {
    OnRender(ctx RenderContext)

    GetComponents() []Component
    AddComponent(Component)
}

Approccio n. 0: metodi di ereditarietà implementati uguali ovunque utilizzando i campi anonimi

type BaseComponent struct {
    components []Component
}

func(c BaseComponent) GetComponents() []Component {
    return c.components
}

func(c *BaseComponent) AddComponent(a Component) {
    c.components = append(c.components, a)
}

Metodo 1: estrai la logica comune a una funzione libera

func Render(c Component, ctx RenderContext) {
    // implement common render logic here

    // call component specific handler
    c.OnRender(ctx)

    // update children
    for _, child := range c.GetComponents() {
        Render(child, ctx)
    }
}

Approccio n. 2: estrai la logica comune a una "classe" dedicata (struct)

type ComponentRenderer struct {
    // store common information required for rendering
}

func(cr *ComponentRenderer) Render(c Component, ctx RenderContext) {
    // implement common render logic here

    // call component specific handler
    c.OnRender(ctx)

    // update children
    for _, child := range c.GetComponents() {
        cr.Render(child, ctx)
    }
}

Esempio per un componente finale:

type Button struct {
    BaseComponent
    // button specific fields
}

func(b *Button) OnRender(ctx RenderContext) {
    // implement button specific rendering code here
}

Addendum for Approach # 2: Se vuoi, puoi dichiarare ComponentRenderer come interfaccia e implementare specifici renderer per piattaforme diverse (es. uno per Windows, uno per Linux, uno per una pagina web, uno per una GUI in gioco , ...).

Puoi combinare questi diversi approcci per scopi diversi.

Nota: potresti provare a implementare un metodo Render su BaseComponent , ma tieni presente che non esiste alcun dispatch virtuale e nessun metodo che sovrascrive le strutture in Go (quindi il metodo Render deve in qualche modo ottenere le informazioni su quale handler chiamare da qualche altra parte). Quindi consiglierei l'approccio # 0 solo nei casi in cui l'implementazione ogni utilizza la stessa logica.

    
risposta data 25.11.2016 - 07:24
fonte

Leggi altre domande sui tag