State Pattern in Python

这一生的挚爱 提交于 2020-05-15 11:01:08

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!