状态模式(State)及代码实现

模式定义:

把复杂的 逻辑判断 提取到不同的状态对象中,在对象的内部状态发生变化时,其对应行为发生变化;

生活中的例子

  • 射击游戏中,当 角色 受到攻击时,其身体状态 会变为 受伤状态,对应行为是 动作变得迟缓,呼吸加重; 当打上药剂后,其 身体状态 变为 健康状态, 对应行为 是 动作恢复如初;
  • 有人打你右脸时,你 变为 愤怒状态,对应动作是 把你的左脸侧过去让别人打...

该模式关键的角色:

  • 上下文角色:  此角色应该是 一个具体的,代表某一个对象,这个对象有状态概念,其中一个属性是 状态,只是状态属性值是一个具体的状态对象;此角色 必须要有 改变状态的方法,以及 触发状态改变的方法; 根据需求添加 其他辅助状态判断的属性字段;如 学生考试成绩 划分4种不同等级(优,良,及格,不及格)的状态; 这时 考试成绩就是一个 上下文角色的 对象,其 状态属性 是具体的等级; 从100分开始扣分,便是一个 触发状态改变的方法, 等级由优变为良 便是一个 改变状态的方法; 分数为 辅助状态判断的字段;
  • 抽象状态角色: 此角色 定义子类需要实现公共方法(状态检查方法,状态对应行为 方法) 和 一个 上下文对象 属性;
  • 具体状态角色:  此角色 实现状态对应行为方法; 并 根据业务逻辑 在 检查状态方法中 实现校验状态变化的代码,并在满足条件时,调用上下文对象 改变状态

 

该模式的主要优缺点如下:

优点:

  • 符合单一职责原则: 每个状态负责自己的状态行为和 状态检查逻辑
  • 简化上下文角色的代码; 把复杂难以维护的条件判断语句 分散在了每个状态类中,从而更易维护;
  • 状态间的切换 实际由上下文角色实现,减少了 状态 间的依赖

缺点:

  • 部分符合开闭原则,需要修改现有类的 状态检查逻辑;但是可以做到不用修改上下文类

 

何时使用此模式:

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

 

和 其他模式 的 比较:

责任链模式比较:  状态模式每个 状态类 知道自己的可能的下个状态是什么; 但是 责任链模式 中的每个责任者无法知道下个责任者是谁,因为 责任链顺序的定义由客户端完成;

与策略模式的比较:  策略模式的多个算法,用户自由选择任何一个均能满足需求,算法间完全独立;  而状态模式中 状态间存在相互转换关系,且用户只能设置初始状态,后续状态变化 由每个状态类的 状态检查逻辑 决定;

 

示例代码部分:

# -*- coding: utf-8 -*-
"""
(C) rgc
All rights reserved
create time '2020/12/21 20:36'

Usage:
状态模式使用示例
实现目标:
状态转换:具体状态类的 上下文对象 变量 调用对应方法改变状态,状态是否转换由具体状态类决定;

示例业务:
在买入FOF类型基金时,份额在途为 t+3,交易成功为t+4;
0.在t日用户买入,初始订单状态为 份额待确认,对应行为是 通知用户请耐心等待份额确认
1.在日期符合t+3时,订单状态改为 份额在途, 对应行为是 通过 买入金额/净值=份额 公式 计算份额
2.在日期符合t+4时,订单状态改为 交易成功, 对应行为是 通知用户买入成功
"""


class OrderContext:
    """
    订单类
    上下文角色
    此角色应该是 一个具体的,代表某一个对象,这个对象有状态概念,其中一个属性是 状态,只是状态属性值是一个具体的状态对象;
    此角色 必须要有 改变状态的方法,以及 触发状态改变的方法;
    此处 订单便是一个上下文角色,其 状态 属性值 有3中,每种状态用一个对象表示;
    天数的变化(mock_date_lapse) 便是一个 触发状态改变的方法;
    改变状态 是 change_state方法
    """
    state = None

    def __init__(self, state, buy_amount):
        """
        初始化订单状态
        :param state:
        """
        self.buy_amount = buy_amount
        self.change_state(state)
        self.buy_ashare = None
        # 天数,状态改变的依据
        self.days = 0

    def change_state(self, state):
        """
        改变状态
        :param state:
        :return:
        """
        self.state = state
        self.state.context_obj = self

    def get_state(self):
        """
        获取上下文当前状态
        :return:
        """
        return self.state

    def mock_date_lapse(self):
        """
        调用状态基类的 模拟天数流逝 功能
        作用:触发状态改变的方法
        :return:
        """
        self.days += 1
        print(f'当前天数为:{self.days}')
        past_state = self.state
        self.state.check_state()
        print(f'当前状态为:{self.state}')
        # 对应状态动作的行为,如果状态相同则不重复执行动作
        if past_state != self.state:
            self.state.action()


class BaseState:
    """
    状态基类
    抽象状态角色
    1.定义标准化的 状态对应的行为方法,为了让所有子类均使用此行为方法
    2.定义标准化的 检查状态的 抽象方法,所有子类各自实现 根据上下文对象业务字段 添加判断逻辑 检查状态是否需要改变;
    """

    # 上下文对象
    context_obj = None

    def check_state(self):
        """
        根据 天数 检查状态,满足条件则转换状态
        :param days:
        :return:
        """
        pass

    def action(self):
        """
        对应状态应该做的事情
        :return:
        """
        pass


class WaitConfirmState(BaseState):
    """
    份额待确认状态
    具体状态角色
    1.实现 状态对应行为方法
    2.根据业务逻辑 在 检查状态方法中 实现校验状态变化的代码,并在满足条件时,调用上下文对象 改变状态
    """

    def __str__(self):
        """

        :return:
        """
        return '份额待确认'

    def action(self):
        """

        :return:
        """
        print('执行动作:尊敬的用户,您好,你的基金份额待确认中,请耐心等待')

    def check_state(self):
        """
        根据 当前所在天数 检查订单状态,如果状态可变,则通过 上下文对象变更状态
        :return:
        """
        if self.context_obj.days >= 3:
            self.context_obj.change_state(OnTheWayState())


class OnTheWayState(BaseState):
    """
    份额在途状态
    """

    def __str__(self):
        """

        :return:
        """
        return '份额在途'

    def action(self):
        """

        :return:
        """
        buy_ashare = self.context_obj.buy_amount / 1.1
        self.context_obj.buy_ashare = buy_ashare
        print(f'执行动作:尊敬的用户,您的基金份额为:{buy_ashare}')

    def check_state(self):
        """
        根据 当前所在天数 检查订单状态,如果状态可变,则通过 上下文对象变更状态
        :return:
        """
        if self.context_obj.days >= 4:
            self.context_obj.change_state(SuccessState())


class SuccessState(BaseState):
    """
    交易成功
    """

    def __str__(self):
        """

        :return:
        """
        return '交易成功'

    def action(self):
        """

        :return:
        """
        print(f'执行动作:尊敬的用户,您的基金买入成功,买入金额为:{self.context_obj.buy_amount},份额为:{self.context_obj.buy_ashare}')

    def check_state(self):
        """
        根据 当前所在天数 检查订单状态,如果状态可变,则通过 上下文对象变更状态
        :return:
        """
        if self.context_obj.days >= 5:
            print('基金交易成功,此订单结束!')


if __name__ == '__main__':
    context_obj = OrderContext(WaitConfirmState(), 1000)
    context_obj.mock_date_lapse()
    context_obj.mock_date_lapse()
    context_obj.mock_date_lapse()
    context_obj.mock_date_lapse()
    context_obj.mock_date_lapse()

运行结果:

总结: 

此模式 能够缩减大量的 条件判断,通过将状态判断逻辑分散到每个具体状态类中,从而方便维护,易于拓展;

业务状态间的管理 通过此模式变得非常方便;

python中有 扩展的第三方 包专门用来实现状态模式的 状态机; 并且此包被<<精通Python设计模式>> 书籍所引用; 链接如下: https://github.com/jtushman/state_machine

 

相关链接:

https://refactoringguru.cn/design-patterns/state

http://c.biancheng.net/view/1388.html

https://my.oschina.net/u/3995125/blog/3054183

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值