Decorating a method ...?

hi guys,

I’m trying to (better) understand how to decorate things in Go … I’ve got some basic examples working, however, I’m struggling getting my head around how I’d decorate a method (as generic as possible) so that I can create one, say, timer decorator, that can be re-used for several unrelated methods…

Example that I’m currently using:

package main

import (
	"fmt"
)

type somedata struct {
	s string
}

func (d *somedata) printSomething() {
	fmt.Println(d.s)
}

func main() {
	d := somedata{s: "yolo"}
	d.printSomething()
}

Any pointers / clues would be appreciated!

Thanks
Alex

What specific problem do you need to solve?

1 Like

To elaborate on my previous reply, Design Patterns were created for OOP languages where the structure of your code is heavily based on type hierarchies and relationships. Java is most “famous” in this regard, often called “the kingdom of nouns”. Go is different. In Go, it is easier to think in verbs rather than in nouns. It is more about what the code shall do, rather than what it is.

For this reason, mapping OOP Design Patterns to Go is often not a good idea. Indeed, many Go programmers frown up on Design Patterns as they do not naturally fit into Go.

This is why I asked for the problem that you want to solve. If you say that you want Decorators in Go just because, then that’s ok, but if you have a real-world problem to solve, then most probably there is a more “idiomatic” way in Go that requires no Design Patterns.

2 Likes

Thanks for the reply! Nothing concrete to solve right now, it’s more an exercise for me for now … (so hints / suggestions what / how to think about it or some doc links would be great, no necessarily giving it all away :slight_smile: )

… what I was thinking of is something along the lines of having multiple methods, not necessarily related, but want to use the same decorator to, for example, time how long they take, be that a DB insert to mysql or a http call to some api …

Cheers!

I’m not sure this is what you’re looking for, but it’s common to use first class functions (including closures and method expressions) for that sort of thing:

https://play.golang.org/p/eZrfR0qRNc

For things that are more involved than a func() (e.g., the measure function has some requirements on what to measure) you would use interfaces.

https://play.golang.org/p/eiN0q4upaz

2 Likes

Thanks Jakob! Those examples really helps :+1: … so decorating a method is actually not much different to any other function … maybe I was getting confused by not having to pass a argument to it :slight_smile: … anyway - thanks very much!

For the specific case of performance measurement, writing a benchmark test or using pprof would seem the way to go.

For the general case, I found this Medium post that demonstrates the Decorator pattern for the specific case of HTTP middleware, but I think this can be applied to other cases, too.

2 Likes

Thanks Christoph, yep, thanks, I’ve “read” that medium post (and others), however most/all examples are always around HTTP middleware rather than more “generic” examples … and to me they’re quickly becoming a bit difficult / complex to adapt and use in simpler scenarios … for example, to wrap my head around the decorator pattern in general, I don’t actually need to necessarily understand the whole Interface stuff, at least not to begin with :wink:

Thanks all!

Did you also see this post?

It is basically the HTTP.Handler example generalized to any functions.

In a nutshell:

Consider you want to decorate this function:

func Do(a, b int) bool {
    fmt.Println("Do")
	return a == b
}

Do decorate this function, first define a function type that has Do’s signature.

type DoFunc func(int, int) bool

Now you can write a decorator function…

func decorate(f DoFunc) DoFunc {
    return func(a, b int) bool {
        fmt.Println("pre processing")
        res := f(a, b)
        fmt.Println("post processing")
        return res
    }
}

…and use it in place of Do:

decoratedDo := decorate(Do)
decoratedDo(4, 7)

(Note that the orignal post seems to have accidentally renamed their decorator function from decoratedLike to likeStats at this point, which makes their example a bit confusing.)

In a similar way, you can also decorate interface functions.

Consider io.Writer, which is an interface:

// definition from the standard library
type Writer interface {
    Write(p []byte) (n int, err error)
}

An interface provides no implementation for its functions. Any type that has a method that implements all functions of an interface is said to satisfy the interface.

Consider, for example, this function that takes an io.Writer:

func Save(name []byte, w io.Writer) {
    n, err := w.Write(name)
    // do the error handling
}

*os.Stdout implements io.Write() and therefore is an io.Writer; hence we can pass an *os.Stdout to Save:

Save("Gopher", os.Stdout)

You can easily decorate any interface. First, create a struct that contains that interface…

type DecoratedWriter struct {
    w io.Writer
}

…and then implement all functions of that interface as wrapper functions:

func (d *DecoratedWriter) Write (p []byte) (int, error) {
    fmt.Println("pre processing")
    n, err := d.w.Write(p)
    fmt.Println("post processing")
	return n, err
}

Finally, create a New... function that takes any type that implements the interface and returns an instance of your struct that wraps this type.

func NewDecoratedWriter(w io.Writer) *DecoratedWriter {
    return &DecoratedWriter{w}
}

With this, you now can pass a decorated writer to Save, in place of the *os.File used before:

dw := NewDecoratedWriter(os.Stdout)

Save("Gopher", dw)

Runnable code in the playground.

3 Likes

Oh cool thanks very much!!

1 Like

Hi All -

I’ve been playing with this for a while and tried to build up a series of “easy” decorator things - please have a look and let me know what you think (if you have the time) : https://github.com/alex-leonhardt/go-decorator-pattern

Very much appreciated!

Thanks!
Alex

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.