11 min read

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.)

Singleton design pattern – Having a unique instance of an object in the entire program

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.

Description

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.

Objective of the Singleton pattern

You’ll use Singleton pattern in many different situations. For example:

  • When you want to use the same connection to a database to make every query
  • When you open a Secure Shell (SSH) connection to a server to do a few tasks, and don’t want to reopen the connection for each task
  • If you need to limit the access to some variable or space, you use a Singleton as the door to this variable.
  • If you need to limit the number of calls to some places, you create a Singleton instance to make the calls in the accepted window

The possibilities are endless, and we have just mentioned some of them.

Implementation

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

Factory method – Delegating the creation of different types of payments

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.

Description

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.

Objective of the Factory method

After the previous description, the following objectives of the Factory Method design pattern must be clear to you:

  • Delegating the creation of new instances of structures to a different part of the program
  • Working at the interface level instead of with concrete implementations
  • Grouping families of objects to obtain a family object creator

Implementation

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

Abstract Factory – A factory of factories

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?

Description

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.

The objective

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:

  • Provide a new layer of encapsulation for Factory methods that returns a common interface for all factories
  • Group common factories into a super Factory (also called factory of factories)

Implementation

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%.

Prototype design pattern

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.

Description

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.

Objective

  • Maintain a set of objects that will be cloned to create new instances
  • Free CPU of complex object initialization to take more memory resources

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.

Summary

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.

Resources for Article:


Further resources on this subject:



Subscribe to the weekly Packt Hub newsletter. We'll send you the results of our AI Now Survey, featuring data and insights from across the tech landscape.

* indicates required