问题
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:
ATM Diagram
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.
回答1:
Atm
shouldn't inherit from AtmState
but from nothing (or from object
, doesn't matter). It should only contain: state
variable, change
method to change state and for each method in AtmState
a method which calls the same named method in the current state
with an additional parameter named e.g. atm
containing the calling Atm
object (the context).
AtmState
should only contain the methods without implementation (and no variables) as it is an interface in the original pattern. For Python you should make it an abstract class with abstract methods, see module abc how to do that.
The concrete classes derived from AtmState
should now implement the methods. Usually only one or two methods are really needed per class, the rest should just print an error. E.g. the NoCard.ejectCard()
method just shows an error that a non-existing card can't be ejected.
Switching between states happens by calling from one of the methods the atm.change()
method (atm
was the additional parameter added by Atm
class).
回答2:
Here is a quick and dirty implementation in python3 of a simplyfied version of your problem.
State is an abstract class using (abc package described by Mickeal) It acts as an interface (derived classes must implements abstract methods). Actual state receives the request and implements the function and performs the states transition this is why i pass the atm object as argument methods
import abc
class State(object,metaclass = abc.ABCMeta):
@abc.abstractmethod
def eject(self, atm):
raise NotImplementedError('')
@abc.abstractmethod
def insert(self, atm):
raise NotImplementedError('')
class NoCard(State):
def eject(self, atm):
print('Error : no card')
def insert(self, atm):
print('ok')
atm.state = HasCard()
class HasCard(State):
def eject(self, atm):
print('ok')
atm.state = NoCard()
def insert(self, atm):
print('Error : card already present')
class ATM:
def __init__(self):
self.state = NoCard()
def insert(self):
self.state.insert(self)
def eject(self):
self.state.eject(self)
if __name__ == "__main__":
atm = ATM()
atm.eject() # default state is no card error no card
atm.insert() # ok state is has card
atm.insert() # error card already in
atm.eject() # ok state become no card
atm.eject() # error no card
来源:https://stackoverflow.com/questions/53689312/state-pattern-in-python