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