Factory Pattern
Creational pattern
The caller does not create objects directly
The factory function or object handles the creation of new objects based on some conditions
- So what is the factory pattern?
- It belongs to a pattern type called creational pattern.
- This is because the factory pattern is mostly targeted towards creating object.
- In the factory pattern, the code that uses the object is not the one that creates it.
- Instead a special function or an object called the factory handles the creation of other object.
- The decision that drives which object types to be created is mostly hidden from the code that ends up using the object.
Why use factory pattern
Makes your code easily expandable
Separation of concerns, only troubleshoot in one place
Efficient team work on large projects
- So why is the factory pattern so useful and popular.
- There are numerous reasons for that.
- However We'll cover only three reasons.
- First your code becomes easliy expandable because whenever you need to add more types behind your factory.
- You only need to add code that the factory function or method or object and nowhere else.
- Second it is up troubleshooting when you know there is an issue with the object creation since your object gets created only in one place.
- Third it may seem collaboration in large projects much easier because each team can only focus on one piece of code without needing to understand how the different objects were created.
How to implement in Go?
Create an interface
Create concrete types
Create factory function or method
- So how to implement the factory pattern in Go.
- Three easy steps.
- We create an interface that will act as a father for all the other types.
- We then created the concrete type such will hide behind the factory.
- Then after that we created the factory
- For our project will make the factory a function as opposed to an object.
Hydra Needs Some Automation
Let's build a system that can control appliances on the Hydra
Start the appliance
Get a description of the appliance
appliance_factorry.go
package appliances
//The main interface used to describe appliances
type Appliance interface {
Start()
GetPurpose() string
}
//Our-appliance types
const (
STOVE = iota
FRIDGE
)
func CreateAppliance(myType int) (Appliance, error) {
switch myType{
case STOVE:
return new(Stove), nil
case FRIDGE:
return new(Fridge), nil
default:
return nil, errors.New("Invalid Appliance Type")
}
}
- Now it's fun to build another piece of the hydra.
- Let's build a system that simulate an appliance automation system in the hydrous spaceship.
- Since the hydrous supposed to travel between galaxies the crew expected to use some regular home appliances like stoves, fridges and microwaves.
- Our code will start the appliance as well as a short description of the appliance that got instantiated.
- Now let's explore some code.
- So we'll create a folder for our project underneath the Go source folder in Go's workspace.
- The folder we'll use will be called class factory tutorial and then in there there will be a class underscore factory underscore tutorial to go which is where our main package will live.
- We'll create appliances folder which will live underneath the class secretarial folder and then in there This is where our factory code will live inside the appliance_factory file
- We will call the package appliances.
- Since It presents the ship appliances, we will import the errors package to use in case errors need to be returned.
- Then we'll create an appliance interface which will contain two methods we expect from an appliance.
- The start method and the GetPurpose method.
- The start method will be expected to start off an appliance.
- The GetPurpose method will return description message of the appliance as a string.
- We'll then use the const keyword in order to create an enum.
- The enum will contain our current appliance types.
- We have two options avaliable. Stoves and Fridges.
- When we use a value iota as shown inside, the cost will increment from zero upwards.
- That one(STOVE) will be zero, This one(FRIDGE) will be one.
- Let's write a function that will act as our factory.
- The function will take the appliance type as an argument.
- So we take it as int, so zero would be a stove, fridge will be one
- and it would return an appliance type or a type that implements appliance interface as well as an error.
- Then based on the type supplied, it will create an object that correspond to that type.
- If it doesn't find the type, it will return nil and Appliance and an error.
- Otherwise if things go well, we return a pointer to the desired object and there will be nil.
Enum
An enumeration is called an enum, and it is a set of related constants.
There is no enum in golang. Define and use constants.
An enum constant can be easily created using an enumerator called iota.
The starting value of iota is 0, and from then on, it is declared as an incremented value by +1.
If you declare only once without using the iota keyword every time, consecutive values are declared for the remaining variables.
refer to https://blog.advenoh.pe.kr/go/Go%EC%97%90%EC%84%9C%EC%9D%98-%EC%97%B4%EA%B1%B0%ED%98%95-%EC%83%81%EC%88%98-Enums-in-Go/
fridge.go
package appliances
//define a fridge struct, the struct contain a string representing the type name
type Fridge struct{
typeName string
}
//The fridge struct implements the start() function
func (fr *Fridge) Start() {
fr.typeName = " Fridge "
}
//The fridge struct implements the GetPurpose() function
func (fr *Fridge) GetPurpose() string {
return "I am a " + fr.typeName + " I cool stuff down!!"
}
- Now let's create the fridge so we'll create a file called fridge.go and it will belong to the appliances package as shown.
- In there, We'll create a type called Fridge which is a struct and it will contain a field called typeName which is a string.
- the first struct will implement the appliance interface.
- So it will implement the start method and the GetPurpose method.
//The main interface used to describe appliances type Appliance interface { Start() GetPurpose() string }
- We can see here that Start() method in Appliance interface and GetPurpose() and the same signature as the GetPurpose method in the appliance interface.
- Now when we called the Start method in Fridge, we'll assign a string " Fridge " to the type name field of the fridge struct.
- Let's just use that as a simulation for the functionality of strating a fridge.
- When we call GetPurpose, we return a message sayinh what the fridge does.
- So this should say "I am a fridge I cool stuff down!!"
- You can get any strings using the plus sign as shown.
stove.go
//define a stove struct, the struct contain a string representing the type name
type Stove struct {
typeName string
}
//The stove struct implements the start() function
func (sv *Stove) Start() {
sv.typeName = " Stove "
}
//The stove struct implements the GetPurpose() function
func (sv *Stove) GetPurpose() string {
return "I am a " + sv.typeName + " I cook food!!"
}
- Now let's create the Stove type the same way.
- So stove.go underneath appliances folder will create the concrete struck type to present or the Stove.
- The functionality here will present the fact that this is the stove it cooks food.
class_factory_tutorial
package main
import (
"ClassFactoryTutorial/appliances"
"fmt"
)
func main() {
//Use the class factory to create an appliance of the requested type
//Request the user to enter the appliance type
fmt.Println("Enter preferred appliance type")
fmt.Println("0: Stove ")
fmt.Println("1: Fridge")
fmt.Println("2: Microwave")
//use fmt.scan to retrive the user's input
var myType int
fmt.Scan(&myType)
myAppliance, err := appliances.CreateAppliance(myType)
//if no errors start the appliance then print it's purpose
if err == nil {
myAppliance.Start()
fmt.Println(myAppliance.GetPurpose())
} else {
//if error encounted, print the error
fmt.Println(err)
}
}
- Now let's go back to our main package.
- It's in class_factory_tutorial underneath a classfactorytutorial folder.
- So we will import the proper packages
- So we're using the appliances packages that is underneath the ClassFactoryTurotial
- And we'll be using fmt.
- So first we will ask the user to enter the appliance type.
- So let's not worrt about the Microwave type.
- For now we'll implement it later.
- That's for now we support stoves and fridges.
- We use fmt.Scan to retrive the type from the standard input
- And then we use that factory function which was create appliance and pass the user input to it.
- If no error occurs, we would get an appliance type.
- And then we'll start the type and we'll print the purpose of that type.
- Not a sound that we don't care what an appliance it is.
- And the code would just work.
- That is the power of interfaces.
- If any errors are encountered we just log them.
- One major advantage of the factory pattern is the fact that we don't have to change this code ever even if we add support for more and more appliances in our code.
- So this code will say simple
- Another advantage of the factory pattern is that it's very expandable.
appliance_factory.go
Before
const (
STOVE = iota
FRIDGE
)
After
const (
STOVE = iota
FRIDGE
MICROWAVE
)
- So let's showcase that but just adding a MICROWAVE type in here.
- So microwave will be three.
- There will be a microwave.go file underneath appliances folder which will implement the appliance interface.
microwave.go
package appliances
type Microwave struct {
typeName string
}
func (mr *Microwave) Start() {
mr.typeName = " Microwave "
}
func (mr *Microwave) GetPurpose() string {
return "I am a " + mr.typeName + " I heat stuff up!!"
}
- So we have Start() And we have GetPurpose()
- And in this case the type will be microwave.
- And the purpose will be heating stuff up.
- So this will be our microwave concrete type.
- Now going back to our factory code.
appliance_factory.go
Before
func CreateAppliance(myType int) (Appliance, error) {
switch myType{
case STOVE:
return new(Stove), nil
case FRIDGE:
return new(Fridge), nil
case MICROWAVE:
return new(Microwave), nil
default:
return nil, errors.New("Invalid Appliance Type")
}
}
- We will add the case of the microwave in our CreateAppliance function.
- So the user passes type Microwave, it will return a new Microwave type with no error.
- This shows us how efficient it is to add new functionality and expand our code within the factory pattern without disturbing outside code.
- So for example, in our example code here the classfactorytutorial made package didn't need to know about the new microwave object.
- The factory abstrated all of that the main package only needed to know about the alliance type.
- And then pass to the factory what the user wanted.
- Now let's try to run code by going into the classfactorytutorial from the terminal and then using that go run command.
C:\Go\Learngo\Udemy\classfactorytutorial> go run class_factory_tutorial.go
- So we'll do go run and then we'll put the file name or the main package and the main function exist.
- So if I enter, tool asks me for my preferred appliance type.
Enter preferred appliance type
0: Stove
1: Fridge
2: Microwave
1
I am a Fridge I cool stuff down!!
- So let's say a fridge
- that says "i'm a fridge I cool stuff down."
Enter preferred appliance type
0: Stove
1: Fridge
2: Microwave
2
I am a Microwave I heat stuff up!!
- Let's try again with a microwave and it works as expected.
0: Stove
1: Fridge
2: Microwave
5
Invalid Appliance Type
- how about an invalid input.
- Printed the error which is the fact that there is no valid appliance type.
Summary
- We explored how to implement the factory pattern in go
- with him follow up with a practical code to solidify out understanding.
Section 4 20. Factory Design Pattern in GO From Mastering Go Programming
댓글 영역