ALex
ALex

Reputation: 25

different structs reusing same method

Below is simplified Go code.

As you can see, it wrote twice String(), is there any way to write just once?

type A struct {
    Name   string
}
func (u A) String() string {
    out, err := json.MarshalIndent(u, "", "\t")

    return fmt.Sprintf("A:\n" + string(out))
}

type B struct {
    Name   string
}
func (u B) String() string {
    out, err := json.MarshalIndent(u, "", "\t")

    return fmt.Sprintf("B:\n" + string(out))
}

Something like to implement a struct Base, which has a method did(),

then struct A and struct B implement struct Base, so they can call did() without need to implement did() itself again.

==============

Edited:

The previous sample code is not very clear, now I changed it.

The struct A and struct B have different fields, and how can we write String() just once, then apply to two structs?

type A struct {
    Name     string
    Status   string
}
func (u A) String() string {
    out, err := json.MarshalIndent(u, "", "\t")
    return fmt.Sprintf("A:\n" + string(out))
}

type B struct {
    ID   int
    Logo   string
}
func (u B) String() string {
    out, err := json.MarshalIndent(u, "", "\t")
    return fmt.Sprintf("B:\n" + string(out))
}

Upvotes: 1

Views: 1030

Answers (3)

Jonathan Hall
Jonathan Hall

Reputation: 79516

Is there any way to write just once?

TL;DR Probably not.

In your exact example, there are two approaches that will work, but neither is very useful in real code, and I expect neither will address your actual need.

  1. The most obvious is to have just a single type, since they're identical anyway. Or make your second type a copy of the first:

    type A struct {
        Name   string
    }
    func (u A) String() string {
        out, err := json.MarshalIndent(u, "", "\t")
    
        return fmt.Sprintf("A:\n" + string(out))
    }
    
    type B A
    
  2. You can use struct embedding. This works because your structs contain exactly the same fields, so they can both embed a common struct:

    type common struct  {
        Name string
    }
    
    func (u common) String() string {
        out, err := json.MarshalIndent(u, "", "\t")
        return fmt.Sprintf("A:\n" + string(out))
    }
    
    type A struct {
        common
    }
    
    type B struct {
        common
    }
    

However, I expect your two structs also contain more fields that are not shared. Which means that both of these approaches won't work. As in example 2, you can embed a struct with the common values, but then your String() method will only have access to the common fields. See an example in the playground

A final approach, which might apply to you, but absolutely will not apply in all cases, is to write a custom String() method on each of the outer types, which in turn calls an embedded String() function to calculate the common part. Whether this works for you or not, of course, depends a lot on what format you want. I discuss a similar approach for JSON marshaling with embedded structs on my blog, and in this SO answer. Since your String() method is actually producing JSON, it may apply to you, as well.

It might look something like this:

type common struct  {
    Name string
}

func (u common) String() string {
    out, err := json.MarshalIndent(u, "", "\t")
    return fmt.Sprintf("A:\n" + string(out))
}

type A struct {
    common
    Age int
}

func (u A) String() string {
    return fmt.Sprintf("%s Age: %d", u.common.String(), u.Age)
}

Upvotes: 1

phonaputer
phonaputer

Reputation: 1530

Yes, there is a way to do this in Go. But it may or may not work depending on your exact use case. You can use an embedded struct.

That would look like this:

Go Playground

type Stringer struct {
   Name string
}

func (u Stringer) String() string {
    out, err := json.MarshalIndent(u, "", "\t")

    return fmt.Sprintf(string(out))
}

type A struct {
   Stringer
}

type B struct {
   Stringer
}

Note that any fields which are in A or B but not in Stringer will not be included in the result of String() string. Thus this may not work for your case if you require any such fields.

Upvotes: 0

poWar
poWar

Reputation: 825

You can use a embedded struct like so which implements the String method

package main

import (
    "encoding/json"
    "fmt"
)

type A struct {
    embd
}

type B struct {
    embd
}

type embd struct {
    Name string
    typ  string
}

func (e embd) String() string {
    out, err := json.MarshalIndent(e, "", "\t")
    if err != nil {
        panic(err)
    }

    return fmt.Sprintf(e.typ + ":\n" + string(out))
}

func main() {
    a := A{embd{"Foo", "A"}}
    fmt.Println(a.String())

    b := B{embd{"Bar", "B"}}
    fmt.Println(b.String())
}

Upvotes: 0

Related Questions