E-Book, Englisch, 402 Seiten
Castro Contreras Go Design Patterns
1. Auflage 2025
ISBN: 978-1-78646-190-2
Verlag: De Gruyter
Format: PDF
Kopierschutz: Adobe DRM (»Systemvoraussetzungen)
Best practices in software development and CSP
E-Book, Englisch, 402 Seiten
ISBN: 978-1-78646-190-2
Verlag: De Gruyter
Format: PDF
Kopierschutz: Adobe DRM (»Systemvoraussetzungen)
Go is a multi-paradigm programming language that has built-in facilities to create concurrent applications. Design patterns allow developers to efficiently address common problems faced during developing applications.
Go Design Patterns will provide readers with a reference point to software design patterns and CSP concurrency design patterns to help them build applications in a more idiomatic, robust, and convenient way in Go.
The book starts with a brief introduction to Go programming essentials and quickly moves on to explain the idea behind the creation of design patterns and how they appeared in the 90's as a common 'language' between developers to solve common tasks in object-oriented programming languages. You will then learn how to apply the 23 Gang of Four (GoF) design patterns in Go and also learn about CSP concurrency patterns, the 'killer feature' in Go that has helped Google develop software to maintain thousands of servers.
With all of this the book will enable you to understand and apply design patterns in an idiomatic way that will produce concise, readable, and maintainable software.
Autoren/Hrsg.
Fachgebiete
Weitere Infos & Material
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 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 , 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 directory. The general rule as we mentioned in the Chapter 1, , 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 so I will create the path . The 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/singletonCreate a new file inside the singleton folder called to also reflect the name of the package and write the following package declarations for the 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 . 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 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\n", 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 function, the value of the count must be 1.
The test that checks the second condition proves that the variable is not different to the returned connection that we requested later. If they were different, the message 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\n", 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 FAILImplementation
Finally, we have to implement the Singleton pattern. As we mentioned earlier, we'll usually write a method and instance to retrieve the Singleton instance in languages such as Java or C++. In Go, we don't have the keyword , but we can achieve the same result by using the scope of the package. First, we create a 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)...



