Frage

I'm writing a little GUI system for my game in Go. So far my structure is kind of like this:

type Component interface {
    Update()
    Render(ctx)

    Translate()

    GetComponent() []Component
    AddComponent(c Component)

    AddInput()
    GetInput()
}

And then I would have say a Panel which implements these, and a button, etc. One issue I'm facing is it's generally quite messy and confusing to get your head around the structure of this thing. I don't know how to write idiomatic Go code, so I'm kind of writing it like it's Java or something.

Can anyone recommend a better approach that will keep my sanity intact? Thanks

To elaborate, this is particularly tedious:

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

I have to do that for every component I make, as well as I have to write

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

I feel like I'm repeating myself a lot!

War es hilfreich?

Lösung

You might want to extract common functionality of those interface methods to separate classes/functions and only implement handlers for logic specific to your components.

A slightly simplified example:

type Component interface {
    OnRender(ctx RenderContext)

    GetComponents() []Component
    AddComponent(Component)
}

Approach #0: Inherit methods that are implemented the same everywhere using anonymous fields

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

Approach #1: Extract the common logic to a free function

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

Approach #2: Extract the common logic to a dedicated "class" (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)
    }
}

Example for a final component:

type Button struct {
    BaseComponent
    // button specific fields
}

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

Addendum for Approach #2: If you want, you can declare ComponentRenderer as an interface and implement specific renderers for different platforms (e.g. one for Windows, one for Linux, one for a web page, one for an ingame GUI, ...).

You can mix and match these different approaches for different purposes.

Note: You could try to implement a Render method on BaseComponent - but be aware that there is no virtual dispatch and no method overriding on structs in Go (so the Render method needs to somehow get the information about which handler to call from somewhere else). So I would only recommend Approach #0 for cases where every implementation uses the exact same logic.

Lizenziert unter: CC-BY-SA mit Zuschreibung
scroll top