Chapter 2. Creational Patterns - Singleton, Builder, Factory, Prototype, and Abstract Factory Design Patterns

We have defined two types of cars-luxury and family. The car Factory will have to return The first groups of design patterns that we are going to cover are the Creational patterns. As the name implies, it groups common practices for creating objects, so object creation is more encapsulated from the users that need those objects. Mainly, creational patterns try to give ready-to-use objects to users instead of asking for their creation, which, in some cases, could be complex, or which would couple your code with the concrete implementations of the functionality that should be defined in an interface.

Singleton design pattern - having a unique instance of a type 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 mention 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

The Singleton pattern is easy to remember. As the name implies, it will provide you with 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 the 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 (we'll see in the following chapters that this is more achievable in Go using channels anyway)
  • 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.

Objectives

As a general guide, we consider using the Singleton pattern when the following rule applies:

  • We need a single, shared value, of some particular type.
  • We need to restrict object creation of some type to a single unit along the entire program.

Example - a unique counter

As an example of an object of which we must ensure that there is only one instance, we will write a counter that holds the number of times it has been called during program execution. It shouldn't matter how many instances we have of the counter, all of them must count the same value and it must be consistent between the instances.

Requirements and acceptance criteria

There are some requirements and acceptance criteria to write the described single counter. They are as follows:

  • When no counter has been created before, a new one is created with the value 0
  • If a counter has already been created, return this instance that holds the actual count
  • If we call the method AddOne, the count must be incremented by 1

We have a scenario with three tests to check in our unit tests.

Writing unit tests first

Go's implementation of this pattern is slightly different from what you'll find in pure object-oriented languages such as Java or C++, where you have static members. In Go, there's nothing like static members, but we have package scope to deliver a similar result.

To set up our project, we must create a new folder within our $GOPATH/src directory. The general rule as we mentioned in the Chapter 1, Ready... Steady... Go!, is to create a subfolder with the VCS provider (such as GitHub), the username, and the name of the project.

For example, in my case, I use GitHub as my VCS and my username is sayden, so I will create the path $GOPATH/src/github.com/sayden/go-design-patterns/creational/singleton. The go-design-patterns instance in the path is the project name, the creational subfolder will also be our library name, and singleton the name of this particular package and subfolder:

mkdir -p $GOPATH/src/github.com/sayden/go-design-patterns/creational/singleton

cd $GOPATH/src/github.com/sayden/go-design-
patterns/creational/singleton

Create a new file inside the singleton folder called singleton.go to also reflect the name of the package and write the following package declarations for the singleton type:

package singleton 
 
type Singleton interface { 
    AddOne() int 
} 
 
type singleton struct { 
    count int 
} 
 
var instance *singleton 
 
func GetInstance() Singleton { 
    return nil 
} 
func (s *singleton) AddOne() int { 
    return 0 
} 

As we are following a TDD approach while writing the code, let's code the tests that use the functions we have just declared. The tests are going to be defined by following the acceptance criteria that we have written earlier. By convention in test files, we must create a file with the same name as the file to test, suffixed with _test.go. Both must reside in the same folder:

package singleton 
 
import "testing" 
 
func TestGetInstance(t *testing.T) { 
   counter1 := GetInstance() 
 
   if counter1 == nil { 
         //Test of acceptance criteria 1 failed 
         t.Error("expected pointer to Singleton after calling GetInstance(), not nil") 
   } 
 
   expectedCounter := counter1 
} 

The first test checks something obvious, but no less important, in complex applications. We actually receive something when we ask for an instance of the counter. We have to think of it as a Creational pattern--we delegate the creation of the object to an unknown package that could fail in the creation or retrieval of the object. We also store the current counter in the expectedCounter variable to make a comparison later:

currentCount := counter1.AddOne() 
if currentCount != 1 { 
     t.Errorf("After calling for the first time to count, the count must be 1 but it is %d
", currentCount) 
} 

Now we take advantage of the zero-initialization feature of Go. Remember that integer types in Go cannot be nil and as we know, that this is the first call to the counter, and it is an integer type of variable, and we also know that it is zero-initialized. So after the first call to the AddOne() function, the value of the count must be 1.

The test that checks the second condition proves that the expectedConnection variable is not different to the returned connection that we requested later. If they were different, the message Singleton instances must be different will cause the test to fail:

counter2 := GetInstance() 
if counter2 != expectedCounter { 
    //Test 2 failed 
    t.Error("Expected same instance in counter2 but it got a different instance") 
} 

The last test is simply counting 1 again with the second instance. The previous result was 1, so now it must give us 2:

currentCount = counter2.AddOne() 
if currentCount != 2 { 
    t.Errorf("After calling 'AddOne' using the second counter, the current count must be 2 but was %d
", currentCount) 
} 

The last thing we have to do to finish our test part is to execute the tests to make sure that they are failing before implementation. If one of them doesn't fail, it implies that we have done something wrong, and we have to reconsider that particular test. We must open the terminal and navigate to the path of the singleton package to execute:

$ go test -v .
=== RUN   TestGetInstance
--- FAIL: TestGetInstance (0.00s)
        singleton_test.go:9: expected pointer to Singleton after calling GetInstance(), not nil
        singleton_test.go:15: After calling for the first time to count, the count must be 1 but it is 0
        singleton_test.go:27: After calling 'AddOne' using the second counter, the current count must be 2 but was 0
FAIL
exit status 1
FAIL

Implementation

Finally, we have to implement the Singleton pattern. As we mentioned earlier, we'll usually write a static method and instance to retrieve the Singleton instance in languages such as Java or C++. 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 struct 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 such as 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 struct 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 struct 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 an instance of the type between the parentheses.

The AddOne method will take the count of the variable instance, raise it by 1, and return the current value of the counter.

Let's run now our unit tests again:

$ go test -v -run=GetInstance
=== RUN   TestGetInstance
--- PASS: TestGetInstance (0.00s)
PASS
ok

A few words about the Singleton design pattern

We have seen a very simple example of the Singleton pattern, partially applied to some situation, that is, a simple counter. Just keep in mind that the Singleton pattern will give you the power to have a unique instance of some struct in your application and that no package can create any clone of this struct.

With Singleton, you are also hiding the complexity of creating the object, in case it requires some computation, and the pitfall of creating it every time you need an instance of it if all of them are similar. All this code writing, checking if the variable already exists, and storage, are encapsulated in the singleton and you won't need to repeat it everywhere if you use a global variable.

Here we are learning the classic singleton implementation for single threaded context. We will see a concurrent singleton implementation when we reach the chapters about concurrency because this implementation is not thread safe!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.12.84.150