行为型模式之状态模式

举报
子都爱学习 发表于 2022/02/28 21:24:30 2022/02/28
【摘要】 状态模式当一个对象的内部状态改变时,允许该对象相应地改变其行为。这样该对象看起来好像改变了其所属的类。意图状态模式是将多个行为封装在状态对象中, context 的行为随时可委托到其中一个状态中。当前状态在不同的状态对象中改变,以反映出context 内部的状态,context 的行为也会随之改变。                            优点结构清晰,状态模式将与特定状态相关...

状态模式

当一个对象的内部状态改变时,允许该对象相应地改变其行为。这样该对象看起来好像改变了其所属的类。

意图

  • 状态模式是将多个行为封装在状态对象中, context 的行为随时可委托到其中一个状态中。
  • 当前状态在不同的状态对象中改变,以反映出context 内部的状态,context 的行为也会随之改变。

                           

优点

  • 结构清晰,状态模式将与特定状态相关的行为局部化到一个状态中,并且将不同状态的行为分割开来,满足“单一职责原则”。

  • 将状态转换显示化,减少对象间的相互依赖。将不同的状态引入独立的对象中会使得状态转换变得更加明确,且减少对象间的相互依赖。

  • 状态类职责明确,有利于程序的扩展。通过定义新的子类很容易地增加新的状态和转换。


实现

1.环境类(Context)角色

也称为上下文,它定义了客户端需要的接口,内部维护一个当前状态,并负责具体状态的切换。


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()

    def turn_crank(self):
        # 转动曲柄
        self.state.turn_crank()
        self.state.dispense()

    def release_ball(self):
        # 发放糖果
        print("A gumball comes rolling out the slot...")
        if self.count > 0:
            self.count -= 1


2.抽象状态(State)角色

定义一个接口,用以封装环境对象中的特定状态所对应的行为,可以有一个或多个行为。

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

    def eject_quarter(self):
        pass

    def turn_crank(self):
        pass

    def dispense(self):
        pass

3.具体状态(Concrete State)角色

实现抽象状态所对应的行为,并且在需要的情况下进行状态切换。

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")

3.执行

这是一个状态图,每个圆圈都是一种状态。很明显,有有25分钱、 没有25分钱、 售出糖果、 糖果售罄四个状态,同时也对应四个动作:投入25分钱,退回25分钱,转动曲柄和发放糖果。

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)


缺点

  • 状态模式的使用必然会增加系统的类与对象的个数。
  • 状态模式的结构与实现都较为复杂,如果使用不当会导致程序结构和代码的混乱。
  • 状态模式对开闭原则的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源码,否则无法切换到新增状态,而且修改某个状态类的行为也需要修改对应类的源码。

应用

通常在以下情况下可以考虑使用状态模式。

  • 当一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为时,就可以考虑使用状态模式。
  • 一个操作中含有庞大的分支结构,并且这些分支决定于对象的状态时。

状态模式与责任链模式的区别

状态模式和责任链模式都能消除 if-else 分支过多的问题。但在某些情况下,状态模式中的状态可以理解为责任,那么在这种情况下,两种模式都可以使用。

从定义来看,状态模式强调的是一个对象内在状态的改变,而责任链模式强调的是外部节点对象间的改变。

从代码实现上来看,两者最大的区别就是状态模式的各个状态对象知道自己要进入的下一个状态对象,而责任链模式并不清楚其下一个节点处理对象,因为链式组装由客户端负责。

状态模式与策略模式的区别

状态模式和策略模式的 UML 类图架构几乎完全一样,但两者的应用场景是不一样的。策略模式的多种算法行为择其一都能满足,彼此之间是独立的,用户可自行更换策略算法,而状态模式的各个状态间存在相互关系,彼此之间在一定条件下存在自动切换状态的效果,并且用户无法指定状态,只能设置初始状态。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。