[Design Pattern] 用 State Pattern 來製作 Android Application 的 教學精靈 (Wizard)

最近想在發佈的 Android 應用程式內加上教學精靈 (Wizard),經過簡單的思考,整理大致的目標如下,

  • Step by step 變換頁面
  • 頁面間的切換要有淡入淡出動畫
  • 要能夠搭配 Android UI 操作
  • 能夠在前個頁面或後個頁面之間切換

經過思考之後,知道這是 State 變換的運作模式,但因為初步規劃只有 3 個頁面,而且我希望先試試看操作的效果如何,為了避免 Over-Design,因此先試著使用 switch…case… 來完成實作。

透過 switch…case… 完成這種實作,最麻煩的問題就是 Code 看起來很 dirty,但也只能先耐住性子了,畢竟只是想要個簡單的 prototype 看看樣子。首先,因為我們有 Back Button 和 Foward Button,所以我們需要在這兩個 Button 的 OnClick Listener 內分別實作 State Transition 的運作規則,

寫出來的樣子大概會像下面的 Code,

 
mFowardButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
switch (mState) {
case STATE_SCREEN_01: {
///
/// Do the operations about SCREEN 1
/// 1. Set the UI component
/// 2. Initialize/Set the Animation
/// 3. Start the Animation
///

mState = STATE_SCREEN_02;
} break;

case STATE_SCREEN_02: {
///
/// Do the operations about SCREEN 2
/// 1. Set the UI component
/// 2. Initialize/Set the Animation
/// 3. Start the Animation
///

mState = STATE_SCREEN_03;
} break;

case STATE_SCREEN_03: {
///
/// Do the operations about SCREEN 3
/// 1. Set the UI component
/// 2. Initialize/Set the Animation
/// 3. Start the Animation
///

mState = STATE_SCREEN_03; /// Final State
} break;
}
}
});

mBackButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
switch (mState) {
case STATE_SCREEN_01: {
///
/// Do the operations about SCREEN 1
/// 1. Set the UI component
/// 2. Initialize/Set the Animation
/// 3. Start the Animation
///

mState = STATE_SCREEN_01; /// Final State
} break;

case STATE_SCREEN_02: {
///
/// Do the operations about SCREEN 2
/// 1. Set the UI component
/// 2. Initialize/Set the Animation
/// 3. Start the Animation
///

mState = STATE_SCREEN_01;
} break;

case STATE_SCREEN_03: {
///
/// Do the operations about SCREEN 3
/// 1. Set the UI component
/// 2. Initialize/Set the Animation
/// 3. Start the Animation
///

mState = STATE_SCREEN_02;
} break;
}
}
});

雖然兩個 Button 的 OnClick Listener 就涵蓋了整個 State Transition,但是已經看起來有複雜感、重複的 Code 也不少了。

值得高興的是,這樣的 Code 足以讓我快速知道 Wizard 的實際運作狀況會是如何。

觀察這個 prototype 後,確定這樣的操作觀感很不錯,而且有更多想法出現了,我發現需要多增加幾個頁面,才能完善表達我要的 Wizard 操作感受。顯然現有的設計會讓 State 的操作不好維護,也缺乏延展彈性。這樣的理由就足以進行 Refactor 了,把不好的 Design 翻修吧。

這邊要採用的是 State Pattern,一般化的 State Pattern,可以參考 GoF 的解釋,或者 Wiki 的 State Pattern 介紹

為了能夠表現出我的作法,分享我繪製的 Class Diagram,

下面是 State Diagram,不是很熟悉 State Diagram 的表達方式,先簡略畫出來,期待各位先進提供改進建議。

經過這樣設計的程式碼,會相對地簡潔有力,下面是 AppWizardStateMachine 的實作,看起來很簡單!
AppWizardStateMachine.java

public class AppWizardStateMachine {
private IWizardState mState;

public AppWizardStateMachine(Context context) {
mState = new AppWizardPage01(context);
}

public void setState(IWizardState state) {
mState = state;
}

public void back() {
mState.back(this);
}

public void forward() {
mState.forward(this);
}
}

AppWizardPage01.java

public class AppWizardPage01 extends AppWizardPage {
private static final String TAG = "AppWizardPage01";
private static final boolean LOCAL_LOG = FingerTranslateApp.LOCAL_LOG;

public AppWizardPage01(Context context) {
super(context, inflateView);

///
/// Do Action
///
}

@Override
public void back(AppWizardStateMachine page) {
return;
}

@Override
public void forward(AppWizardStateMachine page) {
page.setState(new AppWizardPage02(mContext));
}
}

在上面的實作中,只要操作 AppWizardStateMachine 的 back 或 forward method,即可產生 State Transition,切換到另外一個 State,而在這個例子中,每個 State 就是對應到一個頁面狀態。因此,我們希望有幾個畫面狀態時,就是產生幾個 State,並且翱遊在這些 State 之中。

比較前後的例子後,使用 switch…case… 來做 State 切換是很麻煩的事情,當然,如果只是簡單的 State(也許只有兩個 State,或者 State Transition 的規則很單純),或許採用 switch…case… 還是可以達成目的,但遇到複雜的目標時,可以拿 State Pattern 來改善 Code Quality。

發佈留言