This article by Mario Castro Contreras, author of the book Go Design Patterns, introduces you to the Creational design patterns that are explained in the book. As the title implies, this article groups common practices for creating objects. Creational patterns try to give ready-to-use objects to users instead of asking for their input, which, in some cases, could be complex and will couple your code with the concrete implementations of the functionality that should be defined in an interface.
(For more resources related to this topic, see here.)
Have you ever done interviews for software engineers? It's interesting that when you ask them about design patterns, more than 80% will start saying Singleton design pattern. Why is that? Maybe it's because it is one of the most used design patterns out there or one of the easiest to grasp. We will start our journey on creational design patterns because of the latter reason.
Singleton pattern is easy to remember. As the name implies, it will provide you a single instance of an object, and guarantee that there are no duplicates.
At the first call to use the instance, it is created and then reused between all the parts in the application that need to use that particular behavior.
You'll use Singleton pattern in many different situations. For example:
The possibilities are endless, and we have just mentioned some of them.
Finally, we have to implement the Singleton pattern. You'll usually write a static method and instance to retrieve the Singleton instance. In Go, we don't have the keyword static, but we can achieve the same result by using the scope of the package. First, we create a structure that contains the object which we want to guarantee to be a Singleton during the execution of the program:
package creational
type singleton struct{
count int
}
var instance *singleton
func GetInstance() *singleton {
if instance == nil {
instance = new(singleton)
}
return instance
}
func (s *singleton) AddOne() int {
s.count++
return s.count
}
We must pay close attention to this piece of code. In languages like Java or C++, the variable instance would be initialized to NULL at the beginning of the program. In Go, you can initialize a pointer to a structure as nil, but you cannot initialize a structure to nil (the equivalent of NULL). So the var instance *singleton line defines a pointer to a structure of type Singleton as nil, and the variable called instance.
We created a GetInstance method that checks if the instance has not been initialized already (instance == nil), and creates an instance in the space already allocated in the line instance = new(singleton). Remember, when we use the keyword new, we are creating a pointer to the type between the parentheses.
The AddOne method will take the count of the variable instance, raise it by one, and return the current value of the counter.
Lets run now our unit tests again:
$ go test -v -run=GetInstance
=== RUN TestGetInstance
--- PASS: TestGetInstance (0.00s)
PASS
ok
The Factory method pattern (or simply, Factory) is probably the second-best known and used design pattern in the industry. Its purpose is to abstract the user from the knowledge of the structure it needs to achieve a specific purpose. By delegating this decision to a Factory, this Factory can provide the object that best fits the user needs or the most updated version. It can also ease the process of downgrading or upgrading of the implementation of an object if needed.
When using the Factory method design pattern, we gain an extra layer of encapsulation so that our program can grow in a controlled environment. With the Factory method, we delegate the creation of families of objects to a different package or object to abstract us from the knowledge of the pool of possible objects we could use. Imagine that you have two ways to access some specific resource: by HTTP or FTP. For us, the specific implementation of this access should be invisible. Maybe, we just know that the resource is in HTTP or in FTP, and we just want a connection that uses one of these protocols. Instead of implementing the connection by ourselves, we can use the Factory method to ask for the specific connection. With this approach, we can grow easily in the future if we need to add an HTTPS object.
After the previous description, the following objectives of the Factory Method design pattern must be clear to you:
We will start with the GetPaymentMethod method. It must receive an integer that matches with one of the defined constants of the same file to know which implementation it should return.
package creational
import (
"errors"
"fmt"
)
type PaymentMethod interface {
Pay(amount float32) string
}
const (
Cash = 1
DebitCard = 2
)
func GetPaymentMethod(m int) (PaymentMethod, error) {
switch m {
case Cash:
return new(CashPM), nilcase DebitCard:
return new(DebitCardPM), nil default:
return nil, errors.New(fmt.Sprintf("Payment method %d not
recognizedn", m))
}
}
We use a plain switch to check the contents of the argument m (method). If it matches any of the known methods—cash or debit card, it returns a new instance of them. Otherwise, it will return a nil and an error indicating that the payment method has not been recognized. Now we can run our tests again to check the second part of the unit tests:
$go test -v -run=GetPaymentMethod .
=== RUN TestGetPaymentMethodCash
--- FAIL: TestGetPaymentMethodCash (0.00s)
factory_test.go:16: The cash payment method message wasn't correct
factory_test.go:18: LOG:
=== RUN TestGetPaymentMethodDebitCard
--- FAIL: TestGetPaymentMethodDebitCard (0.00s)
factory_test.go:28: The debit card payment method message wasn't correct
factory_test.go:30: LOG:
=== RUN TestGetPaymentMethodNonExistent
--- PASS: TestGetPaymentMethodNonExistent (0.00s)
factory_test.go:38: LOG: Payment method 20 not recognized
FAIL
exit status 1
FAIL
Now we do not get the errors saying it couldn't find the type of payment methods. Instead, we receive a message not correct error when it tries to use any of the methods that it covers. We also got rid of the Not implemented message that was being returned when we asked for an unknown payment method. Lets implement the structures now:
type CashPM struct{}
type DebitCardPM struct{}
func (c *CashPM) Pay(amount float32) string {
return fmt.Sprintf("%0.2f paid using cashn", amount)
}
func (c *DebitCardPM) Pay(amount float32) string {
return fmt.Sprintf("%#0.2f paid using debit cardn", amount)
}
We just get the amount, printing it in a nice formatted message. With this implementation, the tests will all passing now:
$ go test -v -run=GetPaymentMethod .
=== RUN TestGetPaymentMethodCash
--- PASS: TestGetPaymentMethodCash (0.00s)
factory_test.go:18: LOG: 10.30 paid using cash
=== RUN TestGetPaymentMethodDebitCard
--- PASS: TestGetPaymentMethodDebitCard (0.00s)
factory_test.go:30: LOG: 22.30 paid using debit card
=== RUN TestGetPaymentMethodNonExistent
--- PASS: TestGetPaymentMethodNonExistent (0.00s)
factory_test.go:38: LOG: Payment method 20 not recognized
PASS
ok
Do you see the LOG: messages? They aren't errors—we just print some information that we receive when using the package under test. These messages can be omitted unless you pass the -v flag to the test command:
$ go test -run=GetPaymentMethod .
ok
After learning about the factory design pattern is when we grouped a family of related objects in our case payment methods, one can be quick to think: what if I group families of objects in a more structured hierarchy of families?
The Abstract Factory design pattern is a new layer of grouping to achieve a bigger (and more complex) composite object, which is used through its interfaces. The idea behind grouping objects in families and grouping families is to have big factories that can be interchangeable and can grow more easily. In the early stages of development, it is also easier to work with factories and abstract factories than to wait until all concrete implementations are done to start your code. Also, you won't write an Abstract Factory from the beginning unless you know that your object's inventory for a particular field is going to be very large and it could be easily grouped into families.
Grouping related families of objects is very convenient when your object number is growing so much that creating a unique point to get them all seems the only way to gain flexibility of the runtime object creation. Following objectives of the Abstract Factory method must be clear to you:
The implementation of every factory is already done for the sake of brevity. They are very similar to the factory method with the only difference being that in the factory method, we don't use an instance of the factory, because we use the package functions directly. The implementation of the vehicle factory is as follows:
func GetVehicleFactory(f int) (VehicleFactory, error) {
switch f {
case CarFactoryType:
return new(CarFactory), nil
case MotorbikeFactoryType:
return new(MotorbikeFactory), nil
default:
return nil, errors.New(fmt.Sprintf("Factory with id %d not
recognizedn", f))
}
}
Like in any factory, we switched between the factory possibilities to return the one that was demanded. As we have already implemented all concrete vehicles, the tests must be run too:
go test -v -run=Factory -cover .
=== RUN TestMotorbikeFactory
--- PASS: TestMotorbikeFactory (0.00s)
vehicle_factory_test.go:16: Motorbike vehicle has 2 wheels
vehicle_factory_test.go:22: Sport motorbike has type 1
=== RUN TestCarFactory
--- PASS: TestCarFactory (0.00s)
vehicle_factory_test.go:36: Car vehicle has 4 seats
vehicle_factory_test.go:42: Luxury car has 4 doors.
PASS
coverage: 45.8% of statements
ok
All of them passed. Take a close look and note that we have used the -cover flag when running the tests to return a coverage percentage of the package 45.8%. What this tells us is that 45.8% of the lines are covered by the tests we have written, but 54.2% is still not under the tests. This is because we haven't covered the cruise motorbike and the Family car with tests. If you write those tests, the result should rise to around 70.8%.
The last pattern we will see in this article is the Prototype pattern. Like all creational patterns, this too comes in handy when creating objects and it is very common to see the Prototype pattern surrounded by more patterns.
The aim of the Prototype pattern is to have an object or a set of objects that are already created at compilation time, but which you can clone as many times as you want at runtime. This is useful, for example, as a default template for a user who has just registered with your webpage or a default pricing plan in some service. The key difference between this and a Builder pattern is that objects are cloned for the user instead of building them at runtime. You can also build a cache-like solution, storing information using a prototype.
We will start with the GetClone method. This method should return an item of the specified type:
type ShirtsCache struct {}
func (s *ShirtsCache)GetClone(m int) (ItemInfoGetter, error) {
switch m {
case White:
newItem := *whitePrototype
return &newItem, nil
case Black:
newItem := *blackPrototype
return &newItem, nil
case Blue:
newItem := *bluePrototype
return &newItem, nil
default:
return nil, errors.New("Shirt model not recognized")
}
}
The Shirt structure also needs a GetInfo implementation to print the contents of the instances.
type ShirtColor byte
type Shirt struct {
Price float32
SKU string
Color ShirtColor
}
func (s *Shirt) GetInfo() string {
return fmt.Sprintf("Shirt with SKU '%s' and Color id %d that
costs %fn", s.SKU, s.Color, s.Price)
}
Finally, lets run the tests to see that everything is now working:
go test -run=TestClone -v .
=== RUN TestClone
--- PASS: TestClone (0.00s)
prototype_test.go:41: LOG: Shirt with SKU 'abbcc' and Color id 1 that costs 15.000000
prototype_test.go:42: LOG: Shirt with SKU 'empty' and Color id 1 that costs 15.000000
prototype_test.go:44: LOG: The memory positions of the shirts are different 0xc42002c038 != 0xc42002c040
PASS
ok
In the log (remember to set the -v flag when running the tests), you can check that shirt1 and shirt2 have different SKUs. Also, we can see the memory positions of both objects. Take into account that the positions shown on your computer will probably be different.
We have seen the creational design patterns commonly used in the software industry. Their purpose is to abstract the user from the creation of objects for handling complexity or maintainability purposes. Design patterns have been the foundation of thousands of applications and libraries since the nineties, and most of the software we use today has many of these creational patterns under the hood.
Further resources on this subject: