Go Design Pattern — State Design Pattern — Low-Level Design of Vending Machine
In this article, we will discuss State Design Pattern — a type of behavioral design pattern. Also, we will discuss one example of this pattern by discussing the Low-Level Design of a Vending Machine.
State is a behavioral design pattern that allows an object to change its behavior when its internal state changes.
The pattern extracts state-related behaviors into separate state classes and forces the original object to delegate the work to an instance of these classes, instead of acting on its own. In Simple words, if the functionality of the system depends on various states, a state design pattern is best to implement.
Applicability:
- Use the State pattern when you have an object that behaves differently depending on its current state, the number of states is enormous, and the state-specific code changes frequently.
- Use State when you have a lot of duplicate code across similar states and transitions of a condition-based state machine.
How to Implement:
We will discuss how to implement a state design pattern by considering the LLD of a Vending Machine. I will attach the links in the reference to know about requirements details. Here we will focus only on the design/ code part.
Let’s take a look at the state diagram of a vending machine.
- Declare a State Interface which will all methods defined which will perform some actions for a particular state.
type IState interface {
InsertCoin(vendingMachine IVendingMachine, coin constants.Coins) error
ChooseProduct(vendingMachine IVendingMachine, productCode int) error
ReturnChange(IVendingMachine, int) error
DispenseProduct(IVendingMachine, int) error
RefundFullMoney(vendingMachine IVendingMachine, amount int) error
ClickOnInsertCoinButton(IVendingMachine) error
ClickOnInsertProdctCodeButton(IVendingMachine) error
}
This state interface declares all the methods that will perform some action in a particular state. The names of the functions are self-explanatory 💁 . Here you can see, that all the methods have an instance of a Vending Machine.
2. Now, for every actual state create a class that will implement the State Interface.
// idle state or initial state of the machine
type idleState struct {
}
// insert coin state - when user will insert the coin/cash in the machine
type hasCoinState struct {
}
// product selection state - when user will enter whcih product he wants
type hasProductState struct {
}
// dispense product state
type dispenseState struct {
}
Each of the state classes implements IState interface methods. Each method has some significance for a particular state. E.g In Dispense State, you cannot insert a coin. In the product selection state, we cannot insert coins.
While moving the code to the state class, you might discover that it depends on private members of the context. There are several workarounds:
- Make these fields or methods public. Turn the behavior you’re extracting into a public method in the context and call it from the state class. This way is ugly but quick, and you can always fix it later.
- Nest the state classes into the context class, but only if your programming language supports nesting classes.
- In the context class, add a reference field of the state interface type and a public setter that allows overriding the value of that field.
3. Go over the method of the context again and replace empty state conditionals with calls to corresponding methods of the state object.
4. To switch the state of the context, create an instance of one of the state classes and pass it to the context. You can do this within the context itself, in various states, or in the client. Wherever this is done, the class becomes dependent on the concrete state class that it instantiates.
func (h *hasCoinState) ClickOnInsertProdctCodeButton(vendingMachine interfaces.IVendingMachine) error {
vendingMachine.SetMachineState(NewHasProductState())
return nil
}
func (h *hasProductState) RefundFullMoney(vendingMachine interfaces.IVendingMachine, refundAmount int) error {
fmt.Println(" your complate money is refunded Rs ", refundAmount)
vendingMachine.SetMachineState(NewIdleState())
return nil
}
See, In the above code
- From HasCoinState we are switching to HasProductState (product selection state), as the User presses the button.
- From HasProductState, we are switching to an idle state after refunding the full amount of money
We also created a class for a vending machine, items, and inventory which you can refer to the GitHub link mentioned below.