• 8.19 实现状态对象或者状态机
    • 问题
    • 解决方案
    • 讨论

    8.19 实现状态对象或者状态机

    问题

    你想实现一个状态机或者是在不同状态下执行操作的对象,但是又不想在代码中出现太多的条件判断语句。

    解决方案

    在很多程序中,有些对象会根据状态的不同来执行不同的操作。比如考虑如下的一个连接对象:

    1. class Connection:
    2. """普通方案,好多个判断语句,效率低下~~"""
    3.  
    4. def __init__(self):
    5. self.state = 'CLOSED'
    6.  
    7. def read(self):
    8. if self.state != 'OPEN':
    9. raise RuntimeError('Not open')
    10. print('reading')
    11.  
    12. def write(self, data):
    13. if self.state != 'OPEN':
    14. raise RuntimeError('Not open')
    15. print('writing')
    16.  
    17. def open(self):
    18. if self.state == 'OPEN':
    19. raise RuntimeError('Already open')
    20. self.state = 'OPEN'
    21.  
    22. def close(self):
    23. if self.state == 'CLOSED':
    24. raise RuntimeError('Already closed')
    25. self.state = 'CLOSED'

    这样写有很多缺点,首先是代码太复杂了,好多的条件判断。其次是执行效率变低,因为一些常见的操作比如read()、write()每次执行前都需要执行检查。

    一个更好的办法是为每个状态定义一个对象:

    1. class Connection1:
    2. """新方案——对每个状态定义一个类"""
    3.  
    4. def __init__(self):
    5. self.new_state(ClosedConnectionState)
    6.  
    7. def new_state(self, newstate):
    8. self._state = newstate
    9. # Delegate to the state class
    10.  
    11. def read(self):
    12. return self._state.read(self)
    13.  
    14. def write(self, data):
    15. return self._state.write(self, data)
    16.  
    17. def open(self):
    18. return self._state.open(self)
    19.  
    20. def close(self):
    21. return self._state.close(self)
    22.  
    23.  
    24. # Connection state base class
    25. class ConnectionState:
    26. @staticmethod
    27. def read(conn):
    28. raise NotImplementedError()
    29.  
    30. @staticmethod
    31. def write(conn, data):
    32. raise NotImplementedError()
    33.  
    34. @staticmethod
    35. def open(conn):
    36. raise NotImplementedError()
    37.  
    38. @staticmethod
    39. def close(conn):
    40. raise NotImplementedError()
    41.  
    42.  
    43. # Implementation of different states
    44. class ClosedConnectionState(ConnectionState):
    45. @staticmethod
    46. def read(conn):
    47. raise RuntimeError('Not open')
    48.  
    49. @staticmethod
    50. def write(conn, data):
    51. raise RuntimeError('Not open')
    52.  
    53. @staticmethod
    54. def open(conn):
    55. conn.new_state(OpenConnectionState)
    56.  
    57. @staticmethod
    58. def close(conn):
    59. raise RuntimeError('Already closed')
    60.  
    61.  
    62. class OpenConnectionState(ConnectionState):
    63. @staticmethod
    64. def read(conn):
    65. print('reading')
    66.  
    67. @staticmethod
    68. def write(conn, data):
    69. print('writing')
    70.  
    71. @staticmethod
    72. def open(conn):
    73. raise RuntimeError('Already open')
    74.  
    75. @staticmethod
    76. def close(conn):
    77. conn.new_state(ClosedConnectionState)

    下面是使用演示:

    1. >>> c = Connection()
    2. >>> c._state
    3. <class '__main__.ClosedConnectionState'>
    4. >>> c.read()
    5. Traceback (most recent call last):
    6. File "<stdin>", line 1, in <module>
    7. File "example.py", line 10, in read
    8. return self._state.read(self)
    9. File "example.py", line 43, in read
    10. raise RuntimeError('Not open')
    11. RuntimeError: Not open
    12. >>> c.open()
    13. >>> c._state
    14. <class '__main__.OpenConnectionState'>
    15. >>> c.read()
    16. reading
    17. >>> c.write('hello')
    18. writing
    19. >>> c.close()
    20. >>> c._state
    21. <class '__main__.ClosedConnectionState'>
    22. >>>

    讨论

    如果代码中出现太多的条件判断语句的话,代码就会变得难以维护和阅读。这里的解决方案是将每个状态抽取出来定义成一个类。

    这里看上去有点奇怪,每个状态对象都只有静态方法,并没有存储任何的实例属性数据。实际上,所有状态信息都只存储在 Connection 实例中。在基类中定义的 NotImplementedError 是为了确保子类实现了相应的方法。这里你或许还想使用8.12小节讲解的抽象基类方式。

    设计模式中有一种模式叫状态模式,这一小节算是一个初步入门!

    原文:

    http://python3-cookbook.readthedocs.io/zh_CN/latest/c08/p19_implements_stateful_objects_or_state_machines.html