шаблон проектирования python — шаблон состояния

Python Шаблоны проектирования

问题:Есть кондитерская компания, которой необходимо спроектировать автомат по продаже конфет.Процесс управления выглядит следующим образом.Как его реализовать?

1b0d0134acf9ab9b2240066f847412f1.png

Это диаграмма состояний, где каждый кружок представляет собой состояние. Очевидно, есть有25分钱,没有25分钱,售出糖果,糖果售罄четыре штата, что также соответствуетчетыре действия:投入25分钱,退回25分钱,转动曲柄а также发放糖果.

Так как же получить настоящий код из диаграммы состояний?

Простая реализация кода выглядит следующим образом:

#! -*- coding: utf-8 -*-

class GumballMachine:

    # 找出所有状态,并创建实例变量来持有当前状态,然后定义状态的值
    STATE_SOLD_OUT = 0
    STATE_NO_QUARTER = 1
    STATE_HAS_QUARTER = 2
    STATE_SOLD = 3

    state = STATE_SOLD_OUT

    def __init__(self, count=0):
        self.count = count
        if count > 0:
            self.state = self.STATE_NO_QUARTER

    def __str__(self):
        return "Gumball machine current state: %s" % self.state

    def insert_quarter(self):
        # 投入25分钱
        if self.state == self.STATE_HAS_QUARTER: # 如果已经投过
            print("You can't insert another quarter")
        elif self.state == self.STATE_NO_QUARTER: # 如果没有投过
            self.state = self.STATE_HAS_QUARTER
            print("You inserted a quarter")
        elif self.state == self.STATE_SOLD_OUT: # 如果已经售罄
            print("You can't insert a quarter, the machine is sold out")
        elif self.state == self.STATE_SOLD: # 如果刚刚买了糖果
            print("Please wait, we're already giving you a gumball")

    def eject_quarter(self):
        # 退回25分
        if self.state == self.STATE_HAS_QUARTER:
            print("Quarter returned")
            self.state = self.STATE_NO_QUARTER
        elif self.state == self.STATE_NO_QUARTER:
            print("You haven't inserted a quarter")
        elif self.state == self.STATE_SOLD:
            print("Sorry, you alread turned the crank")
        elif self.state == self.SOLD_OUT:
            print("You can't eject, you haven't inserted")

    def turn_crank(self):
        # 转动曲柄
        if self.state == self.STATE_SOLD:
            print("Turning twice doesn't get you another gumball")
        elif self.state == self.STATE_NO_QUARTER:
            print("You turned but there's no quarter")
        elif self.state == self.STATE_SOLD_OUT:
            print("You turned, but there are no gumballs")
        elif self.state == self.STATE_HAS_QUARTER:
            print("You turned...")
            self.state = self.STATE_SOLD
            self.dispense()
    
    def dispense(self):
        # 发放糖果
        if self.state == self.STATE_SOLD:
            print("A gumball comes rolling out the slot")
            self.count -= 1
            if self.count == 0:
                self.state = self.STATE_SOLD_OUT
            else:
                self.state = self.STATE_NO_QUARTER
        elif self.state == self.STATE_NO_QUARTER:
            print("You need to pay first")
        elif self.state == self.STATE_SOLD_OUT:
            print("No gumball dispensed")
        elif self.state == self.STATE_HAS_QUARTER:
            print("No gumball dispensed")


if __name__ == "__main__":
    # 以下是代码测试
    gumball_machine = GumballMachine(5) # 装入5 个糖果
    print(gumball_machine)

    gumball_machine.insert_quarter() # 投入25分钱
    gumball_machine.turn_crank() # 转动曲柄
    print(gumball_machine)

    gumball_machine.insert_quarter() #投入25分钱
    gumball_machine.eject_quarter()  # 退钱
    gumball_machine.turn_crank()     # 转动曲柄

    print(gumball_machine)
    
    gumball_machine.insert_quarter() # 投入25分钱
    gumball_machine.turn_crank() # 转动曲柄 
    gumball_machine.insert_quarter() # 投入25分钱 
    gumball_machine.turn_crank()  # 转动曲柄
    gumball_machine.eject_quarter() # 退钱

    print(gumball_machine)

Есть несколько проблем с этим кодом:

  1. Не следуя принципу открыто-закрыто
  2. больше похоже на процессно-ориентированный дизайн
  3. Переходы между состояниями скрыты в условных операторах
  4. Добавление новых требований в будущем требует дополнительных изменений, их нелегко поддерживать, и это может привести к ошибкам.

Как это улучшить?

рассмотреть возможностьпакетные изменения, поместите поведение каждого состояния в свой собственный класс, и каждому состоянию нужно только реализовать свое собственное действие, и добавить новое состояние, добавив новый класс.

  1. Определите родительский класс State, в этом классе каждое действие конфетной машины имеет соответствующий метод
  2. Реализовать классы состояния для каждого состояния в машине, эти классы будут отвечать за поведение машины в соответствующем состоянии
  3. Избавьтесь от старого условного кода, делегируйте действия классам состояний

Новый код реализации выглядит следующим образом:

#! -*- coding: utf-8 -*-

class State:
    # 定义state基类
    def insert_quarter(self):
        pass

    def eject_quarter(self):
        pass

    def turn_crank(self):
        pass

    def dispense(self):
        pass


class SoldOutState(State):
    # 继承State 类
    def __init__(self, gumball_machine):
        self.gumball_machine = gumball_machine

    def __str__(self):
        return "sold_out"

    def insert_quarter(self):
        print("You can't insert a quarter, the machine is sold out")

    def eject_quarter(self):
        print("You can't eject, you haven't inserted a quarter yet")

    def turn_crank(self):
        print("You turned, but ther are no gumballs")

    def dispense(self):
        print("No gumball dispensed")


class SoldState(State):
    # 继承State 类
    def __init__(self, gumball_machine):
        self.gumball_machine = gumball_machine

    def __str__(self):
        return "sold"

    def insert_quarter(self):
        print("Please wait, we're already giving you a gumball")

    def eject_quarter(self):
        print("Sorry, you already turned the crank")

    def turn_crank(self):
        print("Turning twice doesn't get you another gumball")

    def dispense(self):
        self.gumball_machine.release_ball()
        if gumball_machine.count > 0:
            self.gumball_machine.state = self.gumball_machine.no_quarter_state
        else:
            print("Oops, out of gumballs!")
            self.gumball_machine.state = self.gumball_machine.soldout_state


class NoQuarterState(State):
    # 继承State 类
    def __init__(self, gumball_machine):
        self.gumball_machine = gumball_machine

    def __str__(self):
        return "no_quarter"

    def insert_quarter(self):
        # 投币 并且改变状态
        print("You inserted a quarter")
        self.gumball_machine.state = self.gumball_machine.has_quarter_state

    def eject_quarter(self):
        print("You haven't insert a quarter")

    def turn_crank(self):
        print("You turned, but there's no quarter")

    def dispense(self):
        print("You need to pay first")


class HasQuarterState(State):
    # 继承State 类
    def __init__(self, gumball_machine):
        self.gumball_machine = gumball_machine

    def __str__(self):
        return "has_quarter"

    def insert_quarter(self):
        print("You can't insert another quarter")

    def eject_quarter(self):
        print("Quarter returned")
        self.gumball_machine.state = self.gumball_machine.no_quarter_state

    def turn_crank(self):
        print("You turned...")
        self.gumball_machine.state = self.gumball_machine.sold_state

    def dispense(self):
        print("No gumball dispensed")


class GumballMachine:

    def __init__(self, count=0):
        self.count = count
        # 找出所有状态,并创建实例变量来持有当前状态,然后定义状态的值
        self.soldout_state = SoldOutState(self)
        self.no_quarter_state = NoQuarterState(self)
        self.has_quarter_state = HasQuarterState(self)
        self.sold_state = SoldState(self)
        if count > 0:
            self.state = self.no_quarter_state
        else:
            self.state = self.soldout_state

    def __str__(self):
        return ">>> Gumball machine current state: %s" % self.state

    def insert_quarter(self):
        # 投入25分钱
        self.state.insert_quarter()

    def eject_quarter(self):
        # 退回25分
        self.state.eject_quarter()
        # print("state", self.state, type(self.state))

    def turn_crank(self):
        # 转动曲柄
        # print("state", self.state, type(self.state))
        self.state.turn_crank() # 修改状态
        self.state.dispense()  # 发糖 感谢 @dyq666 指出错误
    
    def release_ball(self):
        # 发放糖果
        print("A gumball comes rolling out the slot...")
        if self.count > 0:
            self.count -= 1
        
        
if __name__ == "__main__":
    # 以下是代码测试
    gumball_machine = GumballMachine(5) # 装入5 个糖果
    print(gumball_machine)

    gumball_machine.insert_quarter() # 投入25分钱
    gumball_machine.turn_crank() # 转动曲柄
    print(gumball_machine)

    gumball_machine.insert_quarter() #投入25分钱
    gumball_machine.eject_quarter()  # 退钱
    gumball_machine.turn_crank()     # 转动曲柄

    print(gumball_machine)
    
    gumball_machine.insert_quarter() # 投入25分钱
    gumball_machine.turn_crank() # 转动曲柄 
    gumball_machine.insert_quarter() # 投入25分钱 
    gumball_machine.turn_crank()  # 转动曲柄
    gumball_machine.eject_quarter() # 退钱

    print(gumball_machine)

Что рефакторинговый код делает по сравнению с предыдущим кодом?

  1. Поведение каждого состояния на местное тогда ваш собственный класс
  2. Удалить оператор if
  3. Буду状态类Ближе к модификации, к Candy Season Class к扩展开放

Следующий рисунок иллюстрирует начальное состояние:

В приведенной выше рефакторинговой части кода используется шаблон состояния:

определение

状态模式: Шаблон состояния позволяет объекту изменять свое поведение при изменении его внутреннего состояния, и объект, кажется, изменяет свой класс.

Диаграмма классов шаблона состояния выглядит следующим образом:

Шаблон состояния предназначен для инкапсуляции нескольких вариантов поведения в объекте состояния, а поведение контекста может быть делегировано одному из состояний в любое время. Текущее состояние изменяется в различных объектах состояния, чтобы отразить состояние внутри контекста, и соответственно изменяется поведение контекста.

расширять

Если теперь мы хотим добавить еще одно состояние к четырем состояниям (после покупки конфет есть 10% вероятность получить еще одно), как этого добиться?

# 添加WinnerState 类,只有dispense 方法不同,可以从SoldState 类继承
class WinnerState(SoldState):
    
    def __str__(self):
        return "winner"

    def dispense(self):
        print("You're a WINNER! You get two gumballs for your quarter")
        self.gumball_machine.release_ball()
        if gumball_machine.count == 0:
            self.gumball_machine.state = self.gumball_machine.soldout_state
        else:
            self.gumball_machine.release_ball()
            if gumball_machine.count > 0:
                self.gumball_machine.state = self.gumball_machine.no_quarter_state
            else:
                print("Oops, out of gumballs!")
                self.gumball_machine.state = self.gumball_machine.soldout_state

# 修改turn_crank 方法
class HasQuarterState(State):
    ...
    def turn_crank(self):
        print("You turned...")
        winner = random.randint(0, 9)
        if winner == 4 and self.gumball_machine.count > 1: # 如果库存大于 1 并且随机数等于4(可以是0到9任意值)
            self.gumball_machine.state = self.gumball_machine.winner_state
        else:
            self.gumball_machine.state = self.gumball_machine.sold_state


# 在 GumballMachine 中初始化
class GumballMachine:

    def __init__(self, count=0):
        self.count = count
        # 找出所有状态,并创建实例变量来持有当前状态,然后定义状态的值
        ...
        self.winner_state = WinnerState(self)
        ...

Суммировать

  1. Шаблон состояния позволяет объекту иметь различное поведение в зависимости от его внутреннего состояния.
  2. Шаблон состояния использует классы для представления состояний.
  3. Контекст делегирует поведение объекту текущего состояния.
  4. Локализация изменений путем инкапсуляции каждого состояния в класс
  5. Загрузка состояния может контролироваться классом State или классом Context.
  6. Использование шаблона состояния увеличивает количество классов
  7. Классы состояния могут совместно использоваться несколькими экземплярами контекста.

Примеры в этой статье взяты из "Шаблоны проектирования Head First".

С новым годом.

Наконец, поблагодарите мою девушку за ее поддержку и терпимость, чем ❤️

Вы также можете ввести следующие ключевые слова в официальном аккаунте, чтобы получить исторические статьи:公号&小程序 | 设计模式 | 并发&协程