Шаблон состояния в Python

У меня возникли некоторые проблемы с реализацией шаблона проектирования состояния в Python.

Я новичок в Python и написал некоторый код, чтобы попытаться ответить на этот вопрос, который был задан мне:

Напишите код для простого банкомата, который позволяет пользователю вставить свою карту, ввести свой PIN-код, запросить наличные и извлечь карту. Используйте следующую объектную модель для системы, которая показывает использование шаблона состояния. Вам нужно будет решить, в какое состояние перейти для каждого действия.

Для получения дополнительной информации см. диаграмму UML ниже:

Схема банкомата

Вот моя попытка ниже...

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)

Самая большая путаница для меня заключается в том, как мне реализовать это с помощью классов? Мне удалось установить правильную логику, но я борюсь с реализацией, используя шаблон проектирования состояния.

Мы будем очень признательны за любые рекомендации.


person Paresa    schedule 09.12.2018    source источник


Ответы (2)


Atm должен наследовать не от AtmState, а ни от чего (или от object, не имеет значения). Он должен содержать только: state переменную, change метод для изменения состояния и для каждого метода в AtmState метод, который вызывает метод с тем же именем в текущем state с дополнительным параметром с именем, например. atm, содержащий вызывающий Atm объект (контекст).

AtmState должен содержать только методы без реализации (и без переменных), поскольку это интерфейс в исходном шаблоне. Для Python вы должны сделать его абстрактным классом с абстрактными методами, см. модуль abc как это сделать.

Конкретные классы, производные от AtmState, теперь должны реализовывать методы. Обычно для каждого класса действительно нужен только один или два метода, остальные должны просто выводить ошибку. Например. метод NoCard.ejectCard() просто показывает ошибку, что несуществующая карта не может быть извлечена.

Переключение между состояниями происходит вызовом из одного из методов метода atm.change() (atm был дополнительным параметром, добавленным классом Atm).

person Michael Butscher    schedule 09.12.2018

Вот быстрая и грязная реализация в python3 упрощенной версии вашей проблемы.

Состояние — это абстрактный класс, использующий (пакет abc, описанный Микилом). Он действует как интерфейс (производные классы должны реализовывать абстрактные методы). Фактическое состояние получает запрос, реализует функцию и выполняет переход состояний, поэтому я передаю объект банкомата в качестве методов аргумента.

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
person jeanmi    schedule 10.02.2019