Factory Design Patterns and SOLID Principles in Golang
Design patterns and SOLID principles are fundamental concepts in software engineering that can greatly enhance the quality, maintainability, and scalability of code. This essay explores their application in Golang, focusing on the benefits of design patterns, the key SOLID principles, and practical implementations of the Factory and Abstract Factory patterns.
Design patterns in Golang offer numerous advantages. They promote code reusability by providing proven solutions to common problems, enhance scalability through loose coupling and high cohesion, improve readability by using recognizable structures, and offer flexibility for future modifications. Additionally, they encourage best practices, potentially improve performance through optimized solutions, and foster consistency across projects and teams.
The SOLID principles, a cornerstone of object-oriented design, are equally applicable in Golang:
1. Single Responsibility Principle (SRP): Each module should have only one reason to change, focusing on a single actor or stakeholder.
2. Open-Closed Principle (OCP): Software entities should be open for extension but closed for modification.
3. Liskov Substitution Principle (LSP): Objects of a superclass should be replaceable with objects of its subclasses without affecting program correctness.
4. Interface Segregation Principle (ISP): Clients should not be forced to depend on interfaces they don’t use.
5. Dependency Inversion Principle (DIP): High-level modules should not depend on low-level modules; both should depend on abstractions.
The Factory Method pattern, a creational design pattern, provides an interface for creating objects in a superclass while allowing subclasses to alter the type of objects created. This pattern is particularly useful when dealing with families of related objects. The provided example demonstrates a “Mark Factory” that randomly creates different types of guns, showcasing how the Factory Method can be implemented in Golang.
The Abstract Factory pattern takes this concept further by allowing the production of families of related objects without specifying their concrete classes. This pattern is illustrated through a sports equipment example, where different brands (Nike and Adidas) produce their own lines of shoes and shirts.
Both patterns demonstrate the power of abstraction and polymorphism in Golang, allowing for flexible and extensible code structures. They encapsulate object creation logic, promoting loose coupling between client code and concrete implementations.
Why use design patterns in Golang?
1. Code reusability: Design patterns provide proven solutions to common problems, allowing developers to reuse efficient code structures.
2. Scalability: They help create more scalable and maintainable code by promoting loose coupling and high cohesion.
3. Readability: Using well-known patterns makes code more recognizable and easier to understand for other developers.
4. Flexibility: Design patterns often simplify modifying or extending code without major refactoring.
5. Best practices: They encourage following established best practices in software design.
6. Efficiency: Many patterns are optimized solutions, potentially improving performance.
7. Consistency: Using patterns promotes a consistent approach across a project or team.
SOLID Principles
Single Responsibility
The single-responsibility principle (SRP) is a computer programming principle that states that “A module should be responsible to one, and only one, actor.”[1] The term actor refers to a group (consisting of one or more stakeholders or users) requiring a module change.
Open Closed Principle
In object-oriented programming, the open-closed principle (OCP) states, “software entities (classes, modules, functions, etc.) should be open for extension but closed for modification”;[1] that is, such an entity can allow its behavior to be extended without modifying its source code.

Liskov substitution principle
The Liskov Substitution Principle (LSP) is one of the five SOLID principles of object-oriented programming. It states that objects of a superclass should be replaceable with objects of a subclass without affecting the program's correctness. In other words, a subclass should be able to be used interchangeably with its parent class without causing unexpected behavior.
Interface segregation principle
A client should never be forced to implement an interface it doesn’t use or depend on methods it doesn't use.

Dependency Inversion
The Dependency Inversion Principle (DIP) emphasizes decoupling and abstraction. The principle consists of two core concepts: high-level modules should not depend on low-level modules, and both should depend on abstractions. This inverted dependency relationship promotes flexibility, testability, and maintainability.
What is the difference between pointer receivers and receivers?
Receivers allow you to associate a method with a type, and they come in two flavors: value receivers and pointer receivers. Understanding the differences between these two types of receivers is crucial for designing efficient and correct Go programs.
What are Receivers?
In Go, a receiver is a parameter of a method that binds the method to a specific type. This is similar to what is commonly known as “this” or “self” in other programming languages. By using a receiver, you can define methods that operate on instances of the associated type. Receivers can be applied to both value and pointer types.
Choosing Between Value and Pointer Receivers
The choice between value and pointer receivers depends on the use case and the behavior you want to achieve. Here are some considerations:
Value Receivers:
- Use value receivers when the method doesn’t need to modify the instance’s state and operates purely on a copy of the instance.
- Value receivers are ideal for methods that are read-only and don’t mutate the internal state of the type.
Pointer Receivers:
- Use pointer receivers when the method needs to modify the instance’s state directly.
- Pointer receivers are essential when you want to modify the underlying state of a struct or any other type.
Initial Project
go mod initSimple Impeliment of Factory design Pattern
Dependency Injection

In dependency Injection, we inject the repository into the service layer instead of directly using the database in the service layer.
main.go
package main
import (
"go-example/repository"
"go-example/service"
)
func main() {
repository := repository.New("mysql") service := service.New("user", repository) service.Run()
}service.go
package serviceimport "go-example/repository"type Service struct {
ServiceName string
Model *repository.DB
}
func New(serviceName string, model *repository.DB) *Service {
return &Service{
ServiceName: serviceName, Model: model,
}
}
func (s *Service) Run() {
println("Service is running")
}repository.go
package repositorytype DB struct {
RepositoryName string
}
func New(repositoryName string) *DB {
return &DB{
RepositoryName: repositoryName,
}
}Factory Pattern
Factory Method
Factory Method is a creational design pattern that provides an interface for creating objects in a superclass but allows subclasses to alter the type of objects that will be created.
The Factory Method pattern suggests replacing direct object construction calls (using the new operator) with calls to a special factory method. Don’t worry: the objects are still created via the new operator, but it’s being called from within the factory method. Objects returned by a factory method are often referred to as products.


Suppose that we are going to design a game. In this game, we have a factory named Mark Factory. In this factory, two different types of guns are randomly created. The factory makes them with special distribution. We can only order guns. The factory will give us a list of guns.
main.go
package main
import (
"go-example/manufacture"
)
func main() {
markCompany := manufacture.NewMarkCompany() for i := 0;
i <= 10;
i++ {
markCompany.AddGun()
}
markCompany.PrintGunList()
}manufacture.go
package manufacture
import (
"fmt"
"go-example/gun"
"math/rand"
)
type MarkCompany struct {
gunList []gun.IGun
}
func NewMarkCompany() *MarkCompany {
mk := MarkCompany{
}
mk.gunList = make([]gun.IGun, 0) return &mk
}
func (m *MarkCompany) AddGun() {
myRand := rand.Intn(2) //Random distribution if myRand == 0 {
g := gun.NewAK47() m.gunList = append(m.gunList, g)
}
else {
g := gun.NewShotgun() m.gunList = append(m.gunList, g)
}
}
func (m *MarkCompany) PrintGunList() {
for _, g := range m.gunList {
fmt.Println(g.GetName(), g.GetPower())
}
}shotgun.go
package guntype Shotgun struct {
Gun
}
func NewShotgun() IGun {
return &Shotgun{
Gun: Gun{
name: "Shotgun", power: 10,
},
}
}AK47.go
package guntype AK47 struct {
Gun
}
func NewAK47() IGun {
return &AK47{
Gun: Gun{
name: "AK47", power: 5,
},
}
}gun.go
package guntype IGun interface {
setName(name string) GetName() string setPower(power int) GetPower() int
}
type Gun struct {
name string
power int
}
func (g *Gun) setName(name string) {
g.name = name
}
func (g *Gun) GetName() string {
return g.name
}
func (g *Gun) setPower(power int) {
g.power = power
}
func (g *Gun) GetPower() int {
return g.power
}
Abstract Factory
Abstract Factory is a creational design pattern that lets you produce families of related objects without specifying their concrete classes.
The abstract factory pattern in software engineering is a design pattern that provides a way to create families of related objects without imposing their concrete classes by encapsulating a group of individual factories that have a common theme without specifying their concrete classes.


Suppose that we have 2 different products. product Sauce and product Chips. product Sauce has been made in 3 different ways and products Chips has been made in 4 differents way. On the other hand, we have Chips Factories look like pringles, they only make a chips with special combinations. Not all combination has accepted in Chips factory.

factory.go
package factory
import (
"go-example/abstract/shirt"
"go-example/abstract/shoe"
)
type ISportFactory interface {
MakeShoe() shoe.IShoe MakeShirt() shirt.IShirt GetShoe() shoe.IShoe GetShirt() shirt.IShirt
}shirt.go
package shirttype IShirt interface {
SetLogo(logo string) GetLogo() string
}
type Shirt struct {
logo string
}
func NewShirt(logo string) Shirt {
return Shirt{
logo: logo,
}
}
func (s *Shirt) SetLogo(logo string) {
s.logo = logo
}
func (s *Shirt) GetLogo() string {
return s.logo
}shoe.go
package shoetype IShoe interface {
SetLogo(logo string) SetSize(size int) GetLogo() string GetSize() int
}
type Shoe struct {
logo string
size int
}
func NewShoe(logo string, size int) Shoe {
return Shoe{
logo: logo, size: size,
}
}
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
}nike.go
package factory
import (
"go-example/abstract/shirt"
"go-example/abstract/shoe"
)
type Nike struct {
shoe shoe.IShoe
shirt shirt.IShirt
}
type NikeShoe struct {
shoe.Shoe
}
type NikeShirt struct {
shirt.Shirt
}
func NewNike() *Nike {
nike := Nike{
}
nike.shirt = nike.MakeShirt() nike.shoe = nike.MakeShoe() return &nike
}
func (n *Nike) MakeShoe() shoe.IShoe {
return &NikeShoe{
shoe.NewShoe("nike", 20),
}
}
func (n *Nike) MakeShirt() shirt.IShirt {
return &NikeShirt{
shirt.NewShirt("nike"),
}
}
func (n *Nike) GetShoe() shoe.IShoe {
return n.shoe
}
func (n *Nike) GetShirt() shirt.IShirt {
return n.shirt
}adidas.go
package factory
import (
"go-example/abstract/shirt"
"go-example/abstract/shoe"
)
type Adidas struct {
shoe shoe.IShoe
shirt shirt.IShirt
}
type AdidasShoe struct {
shoe.Shoe
}
type AdidasShirt struct {
shirt.Shirt
}
func NewAdidas() *Adidas {
adidas := Adidas{
}
adidas.shoe = adidas.MakeShoe() adidas.shirt = adidas.MakeShirt() return &adidas
}
func (a *Adidas) MakeShoe() shoe.IShoe {
return &AdidasShoe{
shoe.NewShoe("adidas", 20),
}
}
func (a *Adidas) MakeShirt() shirt.IShirt {
return &AdidasShirt{
shirt.NewShirt("adidas"),
}
}
func (a *Adidas) GetShoe() shoe.IShoe {
return a.shoe
}
func (a *Adidas) GetShirt() shirt.IShirt {
return a.shirt
}