Abstract Design Pattern in Golang Two Examples with Unit Tests
In this article, I will explain the Abstract design pattern’s concept, objectives, pros and cons, scenarios and how to implement, and provide two instances and unit tests.

Concept
Abstract pattern is also called abstract factory pattern. The abstract design pattern is a type of creational design pattern that involves defining an abstract base interface that declares common methods for concrete structs. Concrete structs implement this interface, providing their own specific implementations of these methods. The client code works with instances of the interface, allowing for flexibility and extensibility.
Abstract pattern is a new layer of grouping to achieve a bigger and more complex composite object, which is used through its interfaces.
Objectives
The main objectives of the abstract design pattern are to:
- Define an interface for creating related objects without specifying their concrete structs.
- Encapsulate object creation logic into separate factory structs.
- Enable the easy replacement of one set of related objects with another, without modifying client code.
Pros and Cons
Advantages of using the abstract design pattern:
- Decouples object creation from the client code.
- Improves code reusability and maintainability.
- Enables adding new products without changing the client code.
- Easier to test, as dependencies can be easily mocked.
Disadvantages of using the abstract design pattern:
- Increased complexity: Introducing interfaces and additional layers of abstraction may make the code harder to understand for developers unfamiliar with the pattern.
- Potential performance overhead: In some cases, the extra level of abstraction might introduce a performance penalty, although this is usually negligible.
Scenarios
The Abstract Pattern is often used in real-world scenarios where flexibility, extensibility, and maintainability are crucial. Here are a few examples of how the Abstract Pattern can be applied in practice:
1. Payment processing systems
In an e-commerce application, you might need to support various payment gateways (e.g., PayPal, Stripe, Braintree). The Abstract Pattern can be used to define a common interface for processing payments, with each concrete implementation handling a specific payment gateway. This approach makes it easy to add or modify payment gateways without affecting the client code.
2. External APIs
When integrating with multiple external APIs, such as social media platforms (e.g., Facebook, Twitter, LinkedIn), you can use the Abstract Pattern to define a common interface for all API interactions. Each concrete implementation corresponds to a specific platform, allowing the application to easily switch between platforms or support new ones without changing the client code.
3. Communication protocols
An application that supports multiple communication protocols (e.g., HTTP, WebSocket, gRPC) could use the abstract factory pattern to create an interface for sending and receiving messages. Each concrete factory would implement the methods specific to a particular protocol.
4. Cloud service providers
An application that needs to work with multiple cloud service providers (e.g., AWS, Google Cloud, Azure) can use the abstract factory pattern to provide a consistent interface for creating and managing resources, such as virtual machines, storage, or databases. Each concrete factory would correspond to a specific cloud provider, implementing the methods specific to that provider’s APIs.

How to implement
The following are steps on how to implement the abstract pattern in Go.
- Define the abstract product interfaces.
- Create concrete product implementations.
- Define the abstract factory interface.
- Create concrete factory implementations.
- Use the factory to create related objects without specifying their concrete structs
The following image is how to implement the abstract pattern diagram. Perhaps you think about Golang does not have a class. Correct, Golang does not have classes in the traditional sense like some other object-oriented programming languages such as Java or C++. Instead, Go has types with methods, and it uses interfaces to achieve polymorphism. I use the term “class” as a general term to represent a type that has methods.

First instance
In the following instance, I will implement the clothes instance. The abstract pattern needs a clear code structure, it will be helpful to understand it. In the following instance, there are 6 files in total.
The below code is the sports.go file content. The SportsFactory is an Abstract product interface. The makeShoe() and the makeShirt() are methods that concrete products must implement.
package clothes
import "fmt"
const (
adidas = "adidas"
nike = "nike"
)
type SportsFactory interface {
makeShoe() IShoe
makeShirt() IShirt
}
func GetSportsFactory(brand string) (SportsFactory, error) {
if brand == adidas {
return &Adidas{}, nil
}
if brand == nike {
return &Nike{}, nil
}
return nil, fmt.Errorf("wrong brand type passed")
}The below code is the adidas.go file content. Adidas is a Concrete product.
package clothes
type Adidas struct {
}
func (a *Adidas) makeShoe() IShoe {
return &AdidasShoe{
Shoe: Shoe{
logo: adidas,
size: 14,
},
}
}
func (a *Adidas) makeShirt() IShirt {
return &AdidasShirt{
Shirt: Shirt{
logo: adidas,
size: 14,
},
}
}The following code is the nike.go file content. Nike is a Concrete product.
package clothes
type Nike struct {
}
func (n *Nike) makeShoe() IShoe {
return &NikeShoe{
Shoe: Shoe{
logo: nike,
size: 14,
},
}
}
func (n *Nike) makeShirt() IShirt {
return &NikeShirt{
Shirt: Shirt{
logo: nike,
size: 14,
},
}
}Both Adidas and Nike implement the makeShoe() and the makeShirt() methods.
The following code is the shirt.go file content. IShirt is an abstract factory. Shirt implements all 4 methods in the IShirt. These 4 methods can be used in concrete factories. AdidasShirt and NikeShirt are concrete factories that can use them directly.
package clothes
type IShirt interface {
setLogo(logo string)
setSize(size int)
getLogo() string
getSize() int
}
type Shirt struct {
logo string
size int
}
func (s *Shirt) setLogo(logo string) {
s.logo = logo
}
func (s *Shirt) getLogo() string {
return s.logo
}
func (s *Shirt) setSize(size int) {
s.size = size
}
func (s *Shirt) getSize() int {
return s.size
}
type AdidasShirt struct {
Shirt
}
type NikeShirt struct {
Shirt
}The below code is the shoe.go file content. It is the same code structure as shirt.go. IShoe is an abstract factory. Shoe implements all 4 methods in the IShoe. These 4 methods can be used in concrete factories. AdidasShoe and NikeShoe are concrete factories that can use them directly.
package clothes
type IShoe interface {
setLogo(logo string)
setSize(size int)
getLogo() string
getSize() int
}
type Shoe struct {
logo string
size int
}
func (s *Shoe) setLogo(logo string) {
s.logo = logo
}
func (s *Shoe) getLogo() string {
return s.logo
}
func (s *Shoe) setSize(size int) {
s.size = size
}
func (s *Shoe) getSize() int {
return s.size
}
type AdidasShoe struct {
Shoe
}
type NikeShoe struct {
Shoe
}The last following code is the sports_test.go file content.
package clothes
import (
"testing"
"github.com/go-playground/assert/v2"
)
func TestAdidasShoe(t *testing.T) {
adidasFactory, _ := GetSportsFactory(adidas)
adidasShoe := adidasFactory.makeShoe()
assert.Equal(t, adidas, adidasShoe.getLogo())
assert.Equal(t, 14, adidasShoe.getSize())
}
func TestAdidasShirt(t *testing.T) {
adidasFactory, _ := GetSportsFactory(adidas)
adidasShirt := adidasFactory.makeShirt()
assert.Equal(t, adidas, adidasShirt.getLogo())
assert.Equal(t, 14, adidasShirt.getSize())
}
func TestNikeShoe(t *testing.T) {
nikeFactory, _ := GetSportsFactory(nike)
nikeShoe := nikeFactory.makeShoe()
assert.Equal(t, nike, nikeShoe.getLogo())
assert.Equal(t, 14, nikeShoe.getSize())
}
func TestNikeShirt(t *testing.T) {
nikeFactory, _ := GetSportsFactory(nike)
nikeShirt := nikeFactory.makeShirt()
assert.Equal(t, nike, nikeShirt.getLogo())
assert.Equal(t, 14, nikeShirt.getSize())
}After these codes are finished, let’s run the unit tests. The tests result from the screenshot is below.

Second Instance
In the below instance, I will implement the GUI example.
The following code is the content of the file named gui.go.
package gui
// Step 1: Define the abstract product interfaces
type Button interface {
Click() string
}
type Checkbox interface {
Check() string
}
// Step 2: Create concrete product implementations
type WindowsButton struct{}
func (w *WindowsButton) Click() string {
return "Windows button clicked"
}
type MacOSButton struct{}
func (m *MacOSButton) Click() string {
return "MacOS button clicked"
}
type WindowsCheckbox struct{}
func (w *WindowsCheckbox) Check() string {
return "Windows checkbox checked"
}
type MacOSCheckbox struct{}
func (m *MacOSCheckbox) Check() string {
return "MacOS checkbox checked"
}
// Step 3: Define the abstract factory interface
type FactoryGUI interface {
CreateButton() Button
CreateCheckbox() Checkbox
}
// Step 4: Create concrete factory implementations
type WindowsFactory struct{}
func (w *WindowsFactory) CreateButton() Button {
return &WindowsButton{}
}
func (w *WindowsFactory) CreateCheckbox() Checkbox {
return &WindowsCheckbox{}
}
type MacOSFactory struct{}
func (m *MacOSFactory) CreateButton() Button {
return &MacOSButton{}
}
func (m *MacOSFactory) CreateCheckbox() Checkbox {
return &MacOSCheckbox{}
}The below code is the content of the file named gui_test.go.
package gui
import (
"runtime"
"testing"
"github.com/go-playground/assert/v2"
)
func TestMacGUI(t *testing.T) {
var factory FactoryGUI
// Choose the concrete factory based on the platform or configuration.
if runtime.GOOS == "darwin" {
factory = &MacOSFactory{}
} else {
factory = &WindowsFactory{}
}
button := factory.CreateButton()
checkbox := factory.CreateCheckbox()
assert.Equal(t, button.Click(), "MacOS button clicked")
assert.Equal(t, checkbox.Check(), "MacOS checkbox checked")
}The test result is the below screenshot. As I am using the Mac Pro, I didn’t test the Windows GUI.

Conclusion
In conclusion, the abstract design pattern is a powerful tool for simplifying the design of complex systems and provides a flexible and modular architecture that makes it easier to add new features or functionality to a system. However, it can be more complex than other design patterns and may introduce some performance overhead. As with any design pattern, it is important to carefully evaluate the pros and cons of using the abstract design pattern and choose the pattern that best fits your system's needs.
Bonus
When you use abstract pattern, you should remember two essential things:
1. The most essential thing is an abstract product interface matches at least one concrete product. 2. The last thing is an abstract factory matches at least one concrete factory.
Go back to Creational Design Patterns click here.
To View Structural Design Patterns in Golang, please click here.
To View Behavioural Design Patterns in Golang, please click here.
To View Concurrency Design Patterns in Golang, please click here.





