状态模式

思考并回答以下问题:

工作状态-函数版

主程序如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
class Program
{
static int Hour = 0;
static bool WorkFinished = false;

static void Main(string[] args)
{

Hour = 9;
WriteProgram();
Hour = 10;
WriteProgram();
Hour = 12;
WriteProgram();
Hour = 13;
WriteProgram();
Hour = 14;
WriteProgram();
Hour = 17;

WorkFinished = true;
//WorkFinished = false;

WriteProgram();
Hour = 19;
WriteProgram();
Hour = 22;
WriteProgram();

Console.Read();
}

public static void WriteProgram()
{
if (Hour < 12)
{
Console.WriteLine("当前时间:{0}点 上午工作,精神百倍", Hour);
}
else if (Hour < 13)
{
Console.WriteLine("当前时间:{0}点 饿了,午饭;犯困,午休。", Hour);
}
else if (Hour < 17)
{
Console.WriteLine("当前时间:{0}点 下午状态还不错,继续努力", Hour);
}
else
{
if (WorkFinished)
{
Console.WriteLine("当前时间:{0}点 下班回家了", Hour);
}
else
{
if (Hour < 21)
{
Console.WriteLine("当前时间:{0}点 加班哦,疲累之极", Hour);
}
else
{
Console.WriteLine("当前时间:{0}点 不行了,睡着了。", Hour);
}
}
}
}
}

应该有个“工作”类,‘写程序’方法是类方法,而‘钟点’、‘任务完成’其实就是类的对外属性

工作状态-分类版

工作类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// 工作
public class Work
{
//钟点
private int hour;
public int Hour
{
get { return hour; }
set { hour = value; }
}

//任务完成
private bool finish = false;
public bool TaskFinished
{
get { return finish; }
set { finish = value; }
}


public void WriteProgram()
{
if (hour < 12)
{
Console.WriteLine("当前时间:{0}点 上午工作,精神百倍", hour);
}
else if (hour < 13)
{
Console.WriteLine("当前时间:{0}点 饿了,午饭;犯困,午休。", hour);
}
else if (hour < 17)
{
Console.WriteLine("当前时间:{0}点 下午状态还不错,继续努力", hour);
}
else
{
if (finish)
{
Console.WriteLine("当前时间:{0}点 下班回家了", hour);
}
else
{
if (hour < 21)
{
Console.WriteLine("当前时间:{0}点 加班哦,疲累之极", hour);
}
else
{
Console.WriteLine("当前时间:{0}点 不行了,睡着了。", hour);
}
}
}
}
}

客户端程序如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Program
{
static void Main(string[] args)
{
//紧急项目
Work emergencyProjects = new Work();
emergencyProjects.Hour = 9;
emergencyProjects.WriteProgram();
emergencyProjects.Hour = 10;
emergencyProjects.WriteProgram();
emergencyProjects.Hour = 12;
emergencyProjects.WriteProgram();
emergencyProjects.Hour = 13;
emergencyProjects.WriteProgram();
emergencyProjects.Hour = 14;
emergencyProjects.WriteProgram();
emergencyProjects.Hour = 17;

//emergencyProjects.WorkFinished = true;
emergencyProjects.TaskFinished = false;

emergencyProjects.WriteProgram();
emergencyProjects.Hour = 19;
emergencyProjects.WriteProgram();
emergencyProjects.Hour = 22;
emergencyProjects.WriteProgram();

Console.Read();
}
}

结果表现如下

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

方法过长是坏味道

MartinFowler曾在《重构》中写过一个很重要的代码坏味道,叫做‘Long Method’,方法如果过长其实极有可能是有坏味道了。

‘Work(工作)’类的‘WriteProgram(写程序)’方法过长。这个方法很长,而且有很多的判断分支,这也就意味着它的责任过大了。无论是任何状态,都需要通过它来改变,这实际上是很糟糕的。

面向对象设计其实就是希望做到代码的责任分解。这个类违背了‘单一职责原则’。

由于‘WriteProgram(写程序)’的方法里有这么多判断,使得任何需求的改动或增加,都需要去更改这个方法了,比如,你们老板也感觉加班有些过分,对于公司的办公室管理以及员工的安全都不利,于是发了一通知,不管任务再多,员工必须在20点之前离开公司。这样的需求很合常理,所以要满足需求你就得更改这个方法,但真正要更改的地方只涉及到17点到22点之间的状态,但目前的代码却是对整个方法做改动的,维护出错的风险很大。

这样写方法违背了‘开放-封闭原则’。

把这些分支想办法变成一个又一个的类,增加时不会影响其他类。然后状态的变化在各自的类中完成。

GoF已经为这类问题提供了解决方案,那就是‘状态模式’。

状态模式

状态模式主要解决的是当控制一个对象状态转换的条件表达式过于复杂时的情况。把状态的判断逻辑转移到表示不同状态的一系列类当中,可以把复杂的判断逻辑简化。当然,如果这个状态判断很简单,那就没必要用‘状态模式’了。”

状态模式(State)结构图

State类,抽象状态类,定义一个接口以封装与Context的一个特定状态相关的行为。

1
2
3
4
abstract class State
{
public abstract void Handle(Context context);
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class ConcreteStateA : State
{
public override void Handle(Context context)
{
context.State = new ConcreteStateB();
}
}

class ConcreteStateB : State
{
public override void Handle(Context context)
{
context.State = new ConcreteStateA();
}
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Context
{
private State state;

public Context(State state)
{
this.state = state;
}

public State State
{
get
{
return state;
}
set
{
state = value;
Console.WriteLine("当前状态:" + state.GetType().Name);
}
}

public void Request()
{
state.Handle(this);
}
}

客户端代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Program
{
static void Main(string[] args)
{
Context c = new Context(new ConcreteStateA());

c.Request();
c.Request();
c.Request();
c.Request();

Console.Read();
}
}

状态模式好处与用处

状态模式的好处是将与特定状态相关的行为局部化,并且将不同状态的行为分割开来。

是不是就是将特定的状态相关的行为都放入一个对象中,由于所有与状态相关的代码都存在于某个ConcreteState中,所以通过定义新的子类可以很容易地增加新的状态和转换。

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

什么时候应该考虑使用状态模式呢?

当一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为时,就可以考虑使用状态模式了。另外如果业务需求某项业务有多个状态,通常都是一些枚举常量,状态的变化都是依靠大量的多分支判断语句来实现,此时应该考虑将每一种业务状态定义为一个State的子类。这样这些对象就可以不依赖于其他对象而独立变化了,某一天客户需要更改需求,增加或减少业务状态或改变状态流程,对你来说都是不困难的事。

工作状态-状态模式版

代码结构图

抽象状态类,定义一个抽象方法“写程序”

1
2
3
4
5
// 抽象状态
public abstract class State
{
public abstract void WriteProgram(Work w);
}

上午和中午工作状态类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 上午工作状态
public class ForenoonState : State
{
public override void WriteProgram(Work w)
{
if (w.Hour < 12)
{
Console.WriteLine("当前时间:{0}点 上午工作,精神百倍", w.Hour);
}
else
{
w.SetState(new NoonState());
w.WriteProgram();
}
}
}

//中午工作状态
public class NoonState : State
{
public override void WriteProgram(Work w)
{
if (w.Hour < 13)
{
Console.WriteLine("当前时间:{0}点 饿了,午饭;犯困,午休。", w.Hour);
}
else
{
w.SetState(new AfternoonState());
w.WriteProgram();
}
}
}

下午和傍晚工作状态类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
//下午工作状态
public class AfternoonState : State
{
public override void WriteProgram(Work w)
{
if (w.Hour < 17)
{
Console.WriteLine("当前时间:{0}点 下午状态还不错,继续努力", w.Hour);
}
else
{
w.SetState(new EveningState());
w.WriteProgram();
}
}
}

//晚间工作状态
public class EveningState : State
{
public override void WriteProgram(Work w)
{
if (w.TaskFinished)
{
w.SetState(new RestState());
w.WriteProgram();
}
else
{
if (w.Hour < 21)
{
Console.WriteLine("当前时间:{0}点 加班哦,疲累之极", w.Hour);
}
else
{
w.SetState(new SleepingState());
w.WriteProgram();
}
}
}
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 睡眠状态
public class SleepingState : State
{
public override void WriteProgram(Work w)
{
Console.WriteLine("当前时间:{0}点 不行了,睡着了。", w.Hour);
}
}

//下班休息状态
public class RestState : State
{
public override void WriteProgram(Work w)
{
Console.WriteLine("当前时间:{0}点 下班回家了", w.Hour);
}
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 工作
public class Work
{
private State current;
public Work()
{
current = new ForenoonState();
}

private double hour;
public double Hour
{
get { return hour; }
set { hour = value; }
}

private bool finish = false;
public bool TaskFinished
{
get { return finish; }
set { finish = value; }
}


public void SetState(State s)
{
current = s;
}

public void WriteProgram()
{
current.WriteProgram(this);
}
}

客户端代码,没有任何改动。但我们的程序却更加灵活易变了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Program
{
static void Main(string[] args)
{
//紧急项目
Work emergencyProjects = new Work();
emergencyProjects.Hour = 9;
emergencyProjects.WriteProgram();
emergencyProjects.Hour = 10;
emergencyProjects.WriteProgram();
emergencyProjects.Hour = 12;
emergencyProjects.WriteProgram();
emergencyProjects.Hour = 13;
emergencyProjects.WriteProgram();
emergencyProjects.Hour = 14;
emergencyProjects.WriteProgram();
emergencyProjects.Hour = 17;

//emergencyProjects.WorkFinished = true;
emergencyProjects.TaskFinished = false;

emergencyProjects.WriteProgram();
emergencyProjects.Hour = 19;
emergencyProjects.WriteProgram();
emergencyProjects.Hour = 22;
emergencyProjects.WriteProgram();


Console.Read();
}
}

结果表现如下

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

此时的代码,如果要完成‘员工必须在20点之前离开公司’,只需要增加一个‘强制下班状态’,并改动一下‘傍晚工作状态’类的判断就可以了。而这是不影响其他状态的代码的。

0%