狀態模式主要解決當控制一個對象狀態的轉換的條件表達過於復雜的情況,使得狀態的轉換不依賴於整體的操作。本文將通過一個具體的例子說明狀態模式的應用。假設下面一個場景:
一個新任務提交後,先是收集數據,數據收集完成後等等分配一台機器,分配到機器後就可以將此任務部署至此機器後就可以通知相關模塊開始工作。
先對此場景做分析如下:
一個任務共分四個步驟:一收集數據,二分配機器,三部署到測試機,四通知相關對象。
任務的狀態可以分為:new, waiting, Ready, Running分別對應新任務提交,等待分配機器,准備部署,和部署完成於上面的步驟一一對應。
定義一個任務類:
/*abstract*/ class Task : IGatherData, IAllotMachine, IDeployTestManchine, INotifyUser // 這裡是分別為以上四個步驟定義的接口
{
// 任務在數據庫裡對應的ID
private string taskIdInDataTable;
public string TaskIdInDataTable
{
get
{
return taskIdInDataTable;
}
set
{
taskIdInDataTable = value;
}
}
// 任務的狀態
private TaskState _TaskState;
public TaskState TaskState
{
get
{
return _TaskState;
}
set
{
_TaskState = value;
Log.Info("當前狀態:" + _TaskState.GetType().Name);
}
}
public Task()
{
this._TaskState = new NewTaskState();
}
// 可以指定一個任務的當前狀態
public UpDateTask(TaskState taskTask)
{
this._TaskState = taskTask;
}
/// <summary> 改變一個任務的狀態至當前任務的下一個狀態
/// </summary>
public void ChangeState()
{
System.Threading.Thread.Sleep(500);
this._TaskState.ChangeState(this);
}
/// 以下是實現接口中的方法
/// 合並數據
public virtual bool gatherData()
{
return true;
}
/// 分配機器
/// 分配機器放在基類裡面做,因為這個動作對每個任務來說基本都是一樣的
public virtual bool allotMachine()
{
DoWork work = new DoWork();
return work.allotMachine(this.taskIdInDataTable);
}
/// 部署至測試機
/// 部署至測試機放在基類裡面做,因為這個動作對每個任務來說基本都是一樣的
public virtual bool deployTestManchine()
{
DoWork work = new DoWork();
return work.deployTestManchine(this.taskIdInDataTable);
}
/// 通知相關人員
/// 通知相關人員放在基類裡面做,因為這個動作對每個任務來說基本都是一樣的
public virtual bool deployTestManchine()
{
DoWork work = new DoWork();
return work.deployTestManchine(this.taskIdInDataTable);
}
}
到此我們的任務類已經定義完成,其中有一個ChangeState的方法,此方法負責調用狀態類的改變狀態的方法。
接下面就寫Status類:
先是定義一個超類,此類只有一個方法就是ChangeState, 此方法負責控制任務狀態的轉換。
abstract class TaskState
{
/// <summary>狀態改變過程
/// </summary>狀態的改變由統一的接口進行從一個狀態到下一下狀態的改變,
/// 使用時不用關心內部的狀態是怎麼改變的,只需統一調用此方法即可。
/// <param name="task"></param>
public abstract void ChangeState(UpDateTask task);
}
下面定義具體的狀態類:
// 新任務狀態
class NewTaskState: TaskState
{
public override void ChangeState(UpDateTask task)
{
// 檢查當前狀態
if (this == task.TaskState)
{
if (task.gatherData())
{
task.TaskState = new WaitingTaskState(task);
}
else // 如果合並數據任務失敗,將任務打到已清除狀態
{
task.TaskState = new DeletedState(task);
}
}
}
public NewTaskState()
{
}
public NewTaskState(UpDateTask task)
{
DoWork.setTaskState(task.TaskIdInDataTable, enumTaskState.NewTask);
}
}
// 等待任務狀態
class WaitingTaskState : TaskState
{
public override void ChangeState(UpDateTask task)
{
if (this == task.TaskState)
{
if (task.allotMachine())
{
task.TaskState = new ReadyTaskState(task);
}
else
{
task.TaskState = new WaitingTaskState(task);
}
}
}
public WaitingTaskState()
{
}
public WaitingTaskState(UpDateTask task)
{
DoWork.setTaskState(task.TaskIdInDataTable, enumTaskState.Waiting);
}
}
// 准備任務狀態
class ReadyTaskState : TaskState
{
public override void ChangeState(UpDateTask task)
{
if (this == task.TaskState)
{
if (task.deployTestManchine())
{
task.TaskState = new RunningState(task);
}
else
{
// 部署失敗則重新分配新的測試機器
task.TaskState = new WaitingTaskState(task);
}
}
}
public ReadyTaskState()
{
}
public ReadyTaskState(UpDateTask task)
{
DoWork.setTaskState(task.TaskIdInDataTable, enumTaskState.Ready);
}
}
// 運行任務狀態
class RunningState : TaskState
{
public override void ChangeState(UpDateTask task)
{
// send e-mail
}
public RunningState()
{
}
public RunningState(UpDateTask task)
{
DoWork.setTaskState(task.TaskIdInDataTable, enumTaskState.Running);
}
}
// 下來看客戶端調用
Task task = new Task ();
task.TaskIdInDataTable = 1111; // 為此任務指定一個ID
下來就最重要的,做了這麼多事就是為了下面的調用:
task.ChangeState();
task.ChangeState();
task.ChangeState();
task.ChangeState();
這樣此任務就完成了,客戶端根本不用關心任務的狀態是怎麼轉換的,因為每一個狀態的轉換都是由當前的狀態決定自己的下一個狀態是什麼,即使要修改狀態的流程也只需要修改狀態類就可以,也不用關心具體的操作。這樣做很方便的將狀態轉換、任務的具體步驟、與客戶端的調用分開,之間不受影響。
也可以指定一個任務的當前狀態,從此狀態開始,比如可以指定此任務從Wating開始,此任務就會從Waiting對應的allotMachine開始運行:
Task task = new Task (new WaitingTaskState);
到此狀態模式的應該已全部寫完。