16.4方法过长是坏味道

MartinFowler曾在《重构》中写过一个很重要的代码坏味道,叫做‘LongMethod’,方法如果过长其实极有可能是有坏味道了。
如果一个方法很长,而且有很多的判断分支,这也就意味着它的责任过大了。无论是任何状态,都需要通过它来改变,这实际上是很糟糕的。
面向对象设计其实就是希望做到代码的责任分解。

16.5状态模式

状态模式(State),当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类。
状态模式主要解决的是当控制一个对象状态转换的条件表达式过于复杂时的情况。把状态的判断逻辑转移到表示不同状态的一系列类当中,可以把复杂的判断逻辑简化。
状态模式(State)结构图 第十六章 无尽加班何时休——状态模式 - 图1代码示例
State类,抽象状态类,定义一个接口以封装与Context的一个特定状态相关的行为。

  1. abstract class State
  2. {
  3. abstract function Handle($context);
  4. }

ConcreteState类,具体状态,每一个子类实现一个与Context的一个状态相关的行为。

  1. class ConcreteStateA extends State
  2. {
  3. public function Handle($context)
  4. {
  5. $context->getState();
  6. //设置ConcreteStateA的下一状态是ConcreteStateB
  7. $context->setState(new ConcreteStateB());
  8. }
  9. }
  10. class ConcreteStateB extends State
  11. {
  12. public function Handle($context)
  13. {
  14. $context->getState();
  15. //设置ConcreteStateB的下一状态是ConcreteStateA
  16. $context->setState(new ConcreteStateA());
  17. }
  18. }

Context类,维护一个ConcreteState子类的实例,这个实例定义当前的状态。

  1. class Context
  2. {
  3. private $state = null;
  4. //定义Context的初始状态
  5. public function __construct($state)
  6. {
  7. $this->state = $state;
  8. }
  9. //用于设置新状态
  10. public function setState($state)
  11. {
  12. $this->state = $state;
  13. }
  14. //用于读取当前状态
  15. public function getState()
  16. {
  17. echo '当前状态:' . get_class($this->state) . PHP_EOL;
  18. }
  19. //对请求做处理,同时更改状态
  20. public function Request()
  21. {
  22. $this->state->Handle($this);
  23. }
  24. }

客户端代码

  1. public function stateDemo()
  2. {
  3. //设置Context的初始状态ContextStateA
  4. $c = new Context(new ConcreteStateA());
  5. //不断的请求,同时更改状态
  6. $c->Request();
  7. $c->Request();
  8. $c->Request();
  9. $c->Request();
  10. }

16.6状态模式好处与用处

状态模式的好处:

  • 将与特定状态相关的行为局部化,并且将不同状态的行为分割开来。
  • 将特定的状态相关的行为都放入一个对象中,由于所有与状态相关的代码都存在于某个ConcreteState中,所以通过定义新的子类可以很容易地增加新的状态和转换。

说白了,这样做的目的就是为了消除庞大的条件分支语句,大的分支判断会使得它们难以修改和扩展,就像我们最早说的刻板印刷一样,任何改动和变化都是致命的。状态模式通过把各种状态转移逻辑分布到 State 的子类之间,来减少相互间的依赖,好比把整个版面改成了一个又一个的活字,此时就容易维护和扩展了。
状态模式的使用场景:

  • 当一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为时,就可以考虑使用状态模式了。
  • 另外如果业务需求某项业务有多个状态,通常都是一些枚举常量,状态的变化都是依靠大量的多分支判断语句来实现,此时应该考虑将每一种业务状态定义为一个State的子类。

    16.7工作状态-状态模式版

    代码结构图 第十六章 无尽加班何时休——状态模式 - 图2代码实现
    抽象状态类,定义一个抽象方法“写程序”

    1. //抽象状态
    2. abstract class State
    3. {
    4. abstract function WriteProgram($w);
    5. }

    上午和中午工作状态类

    1. //上午工作状态
    2. class ForenoonState extends State
    3. {
    4. public function WriteProgram($w)
    5. {
    6. if ($w->getHour() < 12) {
    7. echo '当前时间:' . $w->getHour() . '点 上午工作,精神百倍' . PHP_EOL;
    8. } else {
    9. //超过12点,则转入中午工作状态
    10. $w->SetState(new NoonState());
    11. $w->WriteProgram();
    12. }
    13. }
    14. }
    15. //中午工作状态
    16. class NoonState extends State
    17. {
    18. public function WriteProgram($w)
    19. {
    20. if ($w->getHour() < 13) {
    21. echo '当前时间:' . $w->getHour() . '点 饿了,午饭;犯困,午休。' . PHP_EOL;
    22. } else {
    23. //超过13点,则转入下午工作状态
    24. $w->SetState(new AfternoonState());
    25. $w->WriteProgram();
    26. }
    27. }
    28. }

    下午和傍晚工作状态类

    1. //下午工作状态
    2. class AfternoonState extends State
    3. {
    4. public function WriteProgram($w)
    5. {
    6. if ($w->getHour() < 17) {
    7. echo '当前时间:' . $w->getHour() . '点 下午状态不错,继续努力' . PHP_EOL;
    8. } else {
    9. //超过17点,则转入傍晚工作状态
    10. $w->SetState(new EveningState());
    11. $w->WriteProgram();
    12. }
    13. }
    14. }
    15. //晚间工作状态
    16. class EveningState extends State
    17. {
    18. public function WriteProgram($w)
    19. {
    20. if ($w->getTaskFinished()) {
    21. //如果完成任务,则转入下班状态
    22. $w->SetState(new RestState());
    23. $w->WriteProgram();
    24. } else {
    25. if ($w->getHour() < 21) {
    26. echo '当前时间:' . $w->getHour() . '点 加班哦,疲累之极' . PHP_EOL;
    27. } else {
    28. //超过21点,则转入睡眠工作状态
    29. $w->SetState(new SleepingState());
    30. $w->WriteProgram();
    31. }
    32. }
    33. }
    34. }

    睡眠状态和下班休息状态类

    1. //睡眠状态
    2. class SleepingState extends State
    3. {
    4. public function WriteProgram($w)
    5. {
    6. echo '当前时间:' . $w->getHour() . '点 不行了,睡着了' . PHP_EOL;
    7. }
    8. }
    9. //下班休息状态
    10. class RestState extends State
    11. {
    12. public function WriteProgram($w)
    13. {
    14. echo '当前时间:' . $w->getHour() . '点 下班回家了' . PHP_EOL;
    15. }
    16. }

    工作类,此时没有了过长的分支判断语句。

    1. class Work
    2. {
    3. private $current = null;
    4. //工作状态初始化为上午工作状态,即上午9点开始上班
    5. public function __construct()
    6. {
    7. $this->current = new ForenoonState();
    8. }
    9. //“钟点”属性,状态转换的依据
    10. private $hour;
    11. public function setHour($hour)
    12. {
    13. $this->hour = $hour;
    14. }
    15. public function getHour()
    16. {
    17. return $this->hour;
    18. }
    19. //“任务完成”属性,是否能下班的依据
    20. private $finish = false;
    21. public function setTaskFinished($finish)
    22. {
    23. $this->finish = $finish;
    24. }
    25. public function getTaskFinished()
    26. {
    27. return $this->finish;
    28. }
    29. public function SetState($s)
    30. {
    31. $this->current = $s;
    32. }
    33. public function WriteProgram()
    34. {
    35. $this->current->WriteProgram($this);
    36. }
    37. }

    客户端代码

    1. public function stateImp()
    2. {
    3. $emergencyProjects = new Work();
    4. $emergencyProjects->setHour(9);
    5. $emergencyProjects->WriteProgram();
    6. $emergencyProjects->setHour(10);
    7. $emergencyProjects->WriteProgram();
    8. $emergencyProjects->setHour(12);
    9. $emergencyProjects->WriteProgram();
    10. $emergencyProjects->setHour(13);
    11. $emergencyProjects->WriteProgram();
    12. $emergencyProjects->setHour(14);
    13. $emergencyProjects->WriteProgram();
    14. $emergencyProjects->setHour(17);
    15. //$emergencyProjects->setTaskFinished(true);
    16. $emergencyProjects->setTaskFinished(false);
    17. $emergencyProjects->WriteProgram();
    18. $emergencyProjects->setHour(19);
    19. $emergencyProjects->WriteProgram();
    20. $emergencyProjects->setHour(22);
    21. $emergencyProjects->WriteProgram();
    22. }

    输出结果

    1. 当前时间:9 上午工作,精神百倍
    2. 当前时间:10 上午工作,精神百倍
    3. 当前时间:12 饿了,午饭;犯困,午休。
    4. 当前时间:13 下午状态不错,继续努力
    5. 当前时间:14 下午状态不错,继续努力
    6. 当前时间:17 加班哦,疲累之极
    7. 当前时间:19 加班哦,疲累之极
    8. 当前时间:22 不行了,睡着了