In our last blog, we went through design patterns and their benefits, including one of the types; structural design patterns.
In this blog, we will look into Creational design patterns and It’s Usage
Creational Design Patterns
The creational design patterns deal with the creation and initialization of objects. These patterns provide methods for creating objects in a situation-appropriate manner without specifying the exact class of object that will be created.
These are some types of creational design patterns.
- Factory Method
- Abstract Factory
- Builder
- Prototype
Let us discuss the Factory and Singletone pattern.
Factory Pattern
The Factory Design Pattern involves creating various objects without specifying the exact class of the object being created. In some cases, creating an object may involve intricate processes that are not suitable to be included in the client object. The factory class acts as an abstraction, hiding the complex creation logic from the client and offering a standard interface for the client to access newly created objects. The Factory Method is implemented in subclasses, which are responsible for creating the objects of the class.
Consider the following example. Suppose you have a vehicle manufacturing company, and you’re currently manufacturing Sedans and SUVs. After some time, you wanted to start manufacturing trucks, so three classes could be initialized with the common method. `CreateVehicle’ according to the parameter passed to this function will return the respective object.
- Let us create a struct with some required data and an interface that has the CreateVechicle method
//Vehicle struct with required data
type Vehicle struct {
Name string
Type string
MaxSpeed int32
Engine string
}
//Interface for factory with CreateVehicle method
type IVehicle interface {
CreateVehicle() Vehicle
}
- Now as per the pattern let’s implement all of these types individually with the CreateVehicle method
func (s SUV) CreateVehicle() Vehicle {
return Vehicle{
Name: "SUV Model 1",
Type: "SUV",
MaxSpeed: 150,
Engine: "2600cc",
}
}
//Truck Implementation
type Truck struct {
Vehicle
}
func (t Truck) CreateVehicle() Vehicle {
return Vehicle{
Name: "Truck Model 1",
Type: "Truck",
MaxSpeed: 100,
Engine: "5000cc",
}
}
//Sedan Implementation
type Sedan struct {
Vehicle
}
func (s Sedan) CreateVehicle() Vehicle {
return Vehicle{
Name: "Sedan Model 1",
Type: "Sedan",
MaxSpeed: 150,
Engine: "2000cc",
}
}
- And finally, let us implement the factory function which will give the object according to the vehicle type provide and the main method to test out implementation.
// fn to get a object for specific type of vehicle
func getVehicle(vehicleType string) (IVehicle, error) {
if vehicleType == "Sedan" {
return Sedan{}, nil
}
if vehicleType == "SUV" {
return SUV{}, nil
}
if vehicleType == "Truck" {
return Truck{}, nil
}
return nil, fmt.Errorf("Wrong vehicle type passed")
}
func main() {
vehicle, _ := getVehicle("Sedan")
fmt.Printf("%+v\n", vehicle.CreateVehicle())
vehicle, _ = getVehicle("SUV")
fmt.Printf("%+v\n", vehicle.CreateVehicle())
vehicle, _ = getVehicle("Truck")
fmt.Printf("%+v\n", vehicle.CreateVehicle())
}
Singleton Pattern
The singleton pattern ensures you have one instance of an object. This is useful when only one object is required to control the action during execution. Since only one instance of the class is created, any instance fields of a Singleton are guaranteed to be unique across all instances in the system.
To implement this we will require to use the sync package provided by Go, which will ensure that the object instantiates only once.
type Object struct{}
// Initializing global object instance and sync.Once which helps us to execute the function only once
var (
object *Object
once sync.Once
)
And now we will write a function that will instantiate the object.
// CreateNewObject will be called many times but will give the only object which instantiated first.
func CreateNewObject() *Object {
if object == nil {
once.Do(func() {
object = &Object{}
})
}
return object
}
// Calling CreateNewObject five times to ensure the same memory address is printing on each call
func main() {
for i := 0; i < 5; i++ {
fmt.Printf("%p\n", CreateNewObject())
}
}
The output will be the address of the object with the same memory address.