Hey there!
I've been studying a little bit on design patterns in Go since it's the last language that I've been learning.
So I think that the best way to learn as much as possible is to write about it in my own words. So without further ado, here is an example of Factory Functions, Factory Generators, and Prototype Factory.
Factory Functions
First, we are going to build our Post struct and a PostFactory
type Post struct {
Content, Website, Author string
}
type PostFactory struct {
Website, Author string
}
Now we are going to create a function which in turn will return another function. This way we are going to have multiple types of factories to create different types of objects
func NewPostFactory(website string, author string) func(content string) *Post {
return func(content string) *Post {
return &Post{content, website, author}
}
}
This function receives two strings as parameters and returns a function that receives another string and finally returns a pointer to a Post. Let's see how this works on the main function.
Let's assume I want to write a Post and cross-post it to Dev.to, Hackernews.com and medium.com
func main() {
devPostFactory := NewPostFactory("DEV.to", "Tomassirio")
mediumPostFactory := NewPostFactory("Medium.com", "Tomassirio")
hackerNewsPostFactory := NewPostFactory("Hackernews.com", "Tomassirio")
content := "This is what I'm Cross-Posting today!"
devPost := devPostFactory(content)
mediumPost := mediumPostFactory(content)
hackerNewsPost := hackerNewsPostFactory(content)
fmt.Println(devPost)
fmt.Println(mediumPost)
fmt.Println(hackerNewsPost)
}
In the first three lines, we are initializing the PostFactory's we are going to use to create our cross-posts. As you can see after creating the brief post, we use these factories as functions that return us the Posts.
go run factoriesDev.go
&{This is what I'm Cross-Posting today! DEV.to Tomassirio}
&{This is what I'm Cross-Posting today! Medium.com Tomassirio}
&{This is what I'm Cross-Posting today! Hackernews.com Tomassirio}
Factory Generators
We are using the same structs examples as before. However, this time we are declaring a CreatePost function with a PostFactory as a receiver.
func NewPostFactory(website string, author string) *PostFactory {
return &PostFactory{website, author}
}
func (f *PostFactory) CreatePost(content string) *Post {
return &Post{content, f.Website, f.Author}
}
This method is a little bit more versatile than the function factory since I can keep adding functions with a PostFactory as a receiver.
func main() {
devPostFactory := NewPostFactory("DEV.to", "Tomassirio")
mediumPostFactory := NewPostFactory("Medium.com", "Tomassirio")
hackerNewsPostFactory := NewPostFactory("Hackernews.com", "Tomassirio")
content := "This is what I'm Cross-Posting today!"
devPost := devPostFactory.CreatePost(content)
mediumPost := mediumPostFactory.CreatePost(content)
hackerNewsPost := hackerNewsPostFactory.CreatePost(content)
fmt.Println(devPost)
fmt.Println(mediumPost)
fmt.Println(hackerNewsPost)
}
Prototype Factory
I didn't really like this method but you have to learn things before you can dislike them.
In this example, we are defining a type Website, which is actually a string, and an enum below where we declare the Websites were I'll be posting this (wink!)
type Website string
const (
DEV Website = "Dev.to"
MEDIUM = "Medium.com"
HACKER_NEWS = "Hackernews.com"
)
We then declare the function where we'll create the post. There's a default case where we create an empty Post.
func NewPost(website Website) *Post {
author := "Tomassirio"
switch website {
case DEV:
return &Post{"", string(website), author}
case MEDIUM:
return &Post{"", string(website), author}
case HACKER_NEWS:
return &Post{"", string(website), author}
default:
return &Post{"", "", ""}
}
}
Finally, we apply these functions to the main function
func main() {
content := "This is what I'm Cross-Posting today!"
devPost := NewPost(DEV)
mediumPost := NewPost(MEDIUM)
hackerNewsPost := NewPost(HACKER_NEWS)
devPost.Content = content
mediumPost.Content = content
hackerNewsPost.Content = content
fmt.Println(devPost)
fmt.Println(mediumPost)
fmt.Println(hackerNewsPost)
}
The results are going to be always the same.
I hope you liked this post, I'll be trying to create another one with the Design Patterns that I'm going to be learning in the next few days.
Happy Coding!
Top comments (2)
Factory functions do have their place in Go, however it's wise not to reach for an abstraction before you need it. This typically results in code that has needless indirection, and makes it harder for you, the developer, to read. When writing Go code I like to keep in mind the Go proverb "Clear is better than clever"[1].
From my experience, I've found factory functions in Go to be of use when I'm working with an interface that has multiple implementations and I need an easy way of bootstrapping the interface. Typically using factory functions for simple structs is a pointless thing to do.
Go encourages a closer relationship with the code and the data that flows through your program, this is something that should be embraced.
[1] - go-proverbs.github.io/
This is great advice. Thanks!