Go: Factories I
What are factories? Well, factories make things. That's it. I could tell you a bit more in detail the OOP theory behind it but let's be honest: You're a gopher. You don't need to know that. You want practical stuff! Let's look at some code. We have an interface Animal and two structs implementing that interface Cat and Dog.
package main import ( "fmt" ) type Animal interface { Sound() string } type Cat struct { } func (_ *Cat) Sound() string { return "Meow" } type Dog struct { } func (_ *Dog) Sound() string { return "Woof" } func main() { a := Animal(&Dog{}) fmt.Println(a.Sound()) }
Fuck it. No, no, no! This just looks like OOP theory examples. I'm not programming cats and dogs and probably neither are you. Let's look at an actual example of something useful!
package main import ( "fmt" ) type Renderer interface { Render() string } type BoldRenderer struct { Text string } func (br *BoldRenderer) Render() string { return "[b]" + br.Text + "[/b]" } func main() { r := Renderer(&BoldRenderer{"Hello, world!"}) fmt.Println(r.Render()) }
Better. What are we looking at here? Well, we are attempting to write some software that "renders" strings in some way or the other. From here we'll think about the following: What if we want to not just render BB-Code but maybe also Markdown? We need two different BoldRenderers then! Ok, let's do it:
import ( "fmt" ) type Renderer interface { Render() string } type BoldRendererBBCode struct { Text string } type BoldRendererMd struct { Text string } func (br *BoldRendererBBCode) Render() string { return "[b]" + br.Text + "[/b]" } func (br *BoldRendererMd) Render() string { return "**" + br.Text + "**" } func main() { r := Renderer(&BoldRendererMd{"Hello, world!"}) fmt.Println(r.Render()) }
Now we obviously want to have some configuration flag that tells us whether we want to render to BBCode or to Markdown. Depending on this configuration option we'd have to create a BoldRendererMd or a BoldRendererBBCode. But what are we actually trying to do in reality? Suppose we have some input structure that we try to convert to either Markdown or BBCode. Let's assume we're trying to convert some SGML like structure:
type Tag struct { Name string Value string }
Our output format is a bit restrictive. How do we render emph and b? Well, let's just render them both as Bold using a BoldRenderer. Let's create methods that create and return a renderer for us depending on the Tag and the configuration flag. We end up with something like this:
package main import ( "fmt" ) type Renderer interface { Render(string) string } type Tag struct { Name string Value string } type BoldRendererBBCode struct { } type BoldRendererMd struct { } func (br *BoldRendererBBCode) Render(text string) string { return "[b]" + text + "[/b]" } func (br *BoldRendererMd) Render(text string) string { return "**" + text + "**" } func NewBoldRenderer(markdown bool, value string) Renderer { if markdown { return &BoldRendererMd{} } else { return &BoldRendererBBCode{} } } func RendererForTag(tag *Tag, markdown bool) Renderer { switch tag.Name { case "emph", "b": return NewBoldRenderer(markdown, tag.Value) default: panic("Unknown tag name!") } } func main() { markdown := true t := &Tag{Name: "emph", Value: "Hi!"} r := RendererForTag(t, markdown) fmt.Println(r.Render(t.Value)) }
Are we happy with this? No. BoldRendererMd and BoldRendererBBCode look surprisingly similar. Surely we can refactor something. We can create a more generic renderer that just prefixes and suffixes whatever we feed into it:
type PrefixSuffixRenderer struct { Prefix string Suffix string } func (r *PrefixSuffixRenderer) Render(text string) string { return r.Prefix + text + r.Suffix }
And we rewrite the NewBoldRenderer function:
func NewBoldRenderer(markdown bool) Renderer { if markdown { return &PrefixSuffixRenderer{ Prefix: "**", Suffix: "**"} } else { return &PrefixSuffixRenderer{ Prefix: "[b]", Suffix: "[/b]"} } } func RendererForTag(tag *Tag, markdown bool) Renderer { switch tag.Name { case "emph", "b": return NewBoldRenderer(markdown) default: panic("Unknown tag name!") } }
But where's our factory now? Ah... good question. The answer is surprisingly simple: the functions that create renderers are the factories (specifically NewBoldRenderer and RendererForTag). There are more things that are also refered to as factories as they also have the same purpose of creating things. We can for example pass a reference to a function creating things. Or we can create an interface that has functions to create things that have functions to create more things. You will quickly learn that you can never have enough factories. No, I'm kidding. Obviously you'll need to learn which kind of factory and/or factories make the most sense. But we will look into those in part II!