来自流的字符串,用于多种对象类型

I'm used to Java, and setting first steps in google go. I have a tree of objects with child objects etc... This tree is recursively dumped to an io.Writer. Output might be huge, so I don't want to create a string for each object, and concatenate the result in memory..

For debugging purposes, i want to fmt.Printf parts of this tree. Thus, I want to create a generic String() function on each object in which calls the ToStream function, returning the result as a string. In Java, this is easy: create the method on the base class. How do I do this in GO, without creating a custom String method for each kind of object.

See the code for what I want, specifically the line marked ERROR

package main

import (
"io"
"fmt"
"bytes"
)

//Base is an interface for bulk output
type Base interface {
    ToStream(io.Writer)
}

//Impl1 has interface Base
type Impl1 struct{
    stuff int
}

func (Impl1) ToStream(w io.Writer) {
    fmt.Fprintf(w, "A lot of stuff")
}

//Impl2 has interface Base
type Impl2 struct{
    otherstuff int
}

func (Impl2) ToStream(w io.Writer) {
    fmt.Fprintf(w, "A lot of other stuff")
}

//I want to convert any base to a sting for debug output
//This should happen by the ToStream method

func (v Base) String() string {//ERROR here: Invalid receiver type Base (Base is an interface type)
//func (v Impl1) String() string {//This works, but requires re-implementation for every struct Impl1,Impl2,...
    var buffer bytes.Buffer
    v.ToStream(&buffer)
    return string(buffer.Bytes())
}

func main(){
    aBase:= new(Impl1)
    fmt.Printf("%s
",aBase)
}

You can wrap around a Base to add the necessary String() function. Here is one approach:

type StreamerToStringer struct {
    Base
}

func (s StreamerToStringer) String() string {
    var buffer bytes.Buffer
    s.Base.ToStream(&buffer)
    return string(buffer.Bytes())
}

With this, you can augment any Base instance so it has a String() method.

func main() {
    aBase1 := StreamerToStringer{new(Impl1)}
    aBase2 := StreamerToStringer{new(Impl2)}
    fmt.Printf("%s
", aBase1)
    fmt.Printf("%s
", aBase2)

    // These wrapped values still support ToStream().
    var buffer bytes.Buffer
    aBase1.ToStream(&buffer)
    fmt.Println(buffer.Bytes())
}

It is easier to first call:

fmt.Printf("%+v
", yourProject)

See if the information printed are enough for a start: the fmt package mentions

when printing structs, the plus flag (%+v) adds field names

If that is not enough, then you would have to use reflection, as I mentioned in "Golang - How to print struct variables in console?".

Or you can have a look at the project davecgh/go-spew (mentioned in "Go-spew: A Journey into Dumping Go Data Structures")

Go-spew implements a deep pretty printer for Go data structures to aid in debugging

spew.Dump(myVar1, myVar2, ...)
spew.Fdump(someWriter, myVar1, myVar2, ...)
str := spew.Sdump(myVar1, myVar2, ...)

That would print something like:

(main.Foo) {
 unexportedField: (*main.Bar)(0xf84002e210)({
  flag: (main.Flag) flagTwo,
  data: (uintptr) <nil>
 }),
 ExportedField: (map[interface {}]interface {}) {
  (string) "one": (bool) true
 }
}
([]uint8) {
 00000000  11 12 13 14 15 16 17 18  19 1a 1b 1c 1d 1e 1f 20  |............... |
 00000010  21 22 23 24 25 26 27 28  29 2a 2b 2c 2d 2e 2f 30  |!"#$%&'()*+,-./0|
 00000020  31 32                                             |12|
}

Seems like Java thinking blocked you here :-)

While Java has methods only Go does have functions. And of course you cannot have methods on an interface but you can make a plain function taking a Base and doing stuff:

func Base2String(b Base) string {
    var buffer bytes.Buffer
    b.ToStream(&buffer)
    return string(buffer.Bytes())
}

Now if you rename Base to something Go-ish (remember there is no type hierarchy in Go) you have some nice code.