I am having some issues wrapping my head around on implementing the state design pattern in Python.
I am new to Python and wrote some code to try and answer this question that was presented to me:
Write the code for a simple ATM that allows a user to insert their card, enter their PIN, request cash and eject card. Use the following object model for the system that shows the use of the State Pattern. You will need to figure what state to change to for each action.
Please see the below UML diagram for more info:
Here is my attempt below...
import re
class AtmState(object):
name = "ready"
allowed = []
def switch(self, state):
""" Switch to new state """
if state.name in self.allowed:
# print("Current {} => switched to new state {}.".format(self, state.name))
self.__class__=state
# These print statements show how you switch between states.
# else:
# print("Current {} => switched to {} not possible.".format(self, state.name))
def getState(self):
print("The current state is {}".format(self.state))
def __str__(self):
return self.name
def __repr__(self):
return r"The ATM is in a {} state.".format(self.state)
def insertCard(self, card):
# Set messages for card format and inserted
wrong_format = "Please insert your card in the following format: XXXX-XXXX-XXXX-XXXX."
card_inserted = "Card Inserted: {}"
card_pattern='^([0-9]{4})(-?|\s)([0-9]{4})(-?|\s)([0-9]{4})(-?|\s)([0-9]{4})$'
pattern = re.compile(card_pattern)
if pattern.match(card) and str(self.state) in ["insert", "ready", "no card"]:
self.state.switch(HasCard)
print(card_inserted.format(card))
self.state.switch(HasPin)
elif pattern.match(card)==False and str(self.state) ["insert", "ready", "no card"]:
print(wrong_format)
elif str(self.state) in ["enter_pin", "withdraw"]:
print("Card already inserted")
elif str(self.state) in ["no card"]:
print("Error: No Card Inserted. Please insert card.")
def ejectCard(self):
if str(self.state) in ["ready", "insert", "enter_pin", "withdraw"]:
print("Card Ejected")
self.state.switch(NoCard)
else:
print("Error: Card can't be Ejected - No Card Inserted")
def requestCash(self, withdrawl):
if str(self.state)=="withdraw":
if self.balance >= withdrawl:
self.balance-= withdrawl
print("Withdrawing ${}.".format(withdrawl))
if self.balance == 0:
print("Error: Out of Cash")
else:
print("${} remaining in ATM.".format(self.balance))
else:
print("Error: Out of Cash")
elif str(self.state)=="no card":
print("Error: No Card inserted. Please insert your ATM card.")
else:
print("Error: Please enter pin.")
def insertPin(self, pin):
if str(self.state) == "enter_pin" and pin.isdigit() and len(pin)>=4:
print("Pin Entered: {}".format(pin))
self.state.switch(HasCash)
elif str(self.state)== "no card":
print("Error: No Card inserted. Please insert your ATM card.")
else:
print("Pin must be numeric and at least 4 digits.")
class HasCard(AtmState):
name="insert"
allowed=["no card", "enter_pin", "ready"]
class NoCard(AtmState):
name="no card"
allowed=["insert", "ready"]
class HasPin(AtmState):
name="enter_pin"
allowed=["no card", "withdraw", "insert"]
class HasCash(AtmState):
name="withdraw"
allowed=["no card"]
# This is known as the contect class. It does two main things:
# Defines the base state of the ATM...
# Defines a method to change the state of the ATM.
class Atm(AtmState):
"""A class representing an ATM"""
def __init__(self, balance=2000):
self.balance = balance
# State of ATM - default is ready.
self.state = NoCard()
print("ATM has a balance of ${}".format(balance))
def change(self, state):
self.state.switch(state)
The biggest point of confusion for me is how do I implement it using classes? I was able to get the correct logic in place but I am struggling with the implementation using the state design pattern.
Any guidance would be greatly appreciated.