如果物件內的方法,會依據物件內的狀態,使用多個 if - else if - else 或 switch case 來判定不同狀態下該執行的內容時,可以將 switch case 的邏輯抽取出多個獨立的物件,負責狀態的變化以及執行的內容。
通常發生在物件內的方法會依據自身的狀態,產生 if - else if - else 或 switch case 的邏輯判斷,如果判斷的情境數量太多會喪失程式的彈性,導致之後不易於維護以及修改。因此可以將所有的情境獨自轉換成單一物件,每個物件將執行各自的任務,而且還能夠在需要時更改物件的狀態,讓下次物件執行相同方法時,會自動執行新狀態下的相關程式碼。
作法是:
Context)。State 物件),除了將原本邏輯判斷內實作細節的程式碼搬移過來,還可以依據需求,更改 Context 的狀態。Context:
Context 的狀態更改為 State 物件。State 的方法。以下範例以「簡易型電視遊樂器」為核心製作。
製作獨立物件的虛擬層親代:State
public interface State {
    public abstract void tryToShot();
    public abstract void tryToKick();
    public abstract void tryToJump();
    public abstract void tryToMovingToRight();
    public abstract void tryToMovingToLeft();
    public abstract void tryToDoSomething();
    public abstract void tryToBecomeSnipperMode();
    public abstract void tryToBecomeStandingMode();
    public abstract void tryToBecomeBoostMode();
}
製作具有多個邏輯判斷的物件:Hero(Context 物件)
public class Hero {
    private State state;
    public Hero() {
        this.state = new StandingState(this);
    }
    public void changeState(State state) {
        this.state = state;
    }
    public State getState() {
        return state;
    }
    public void tryToShot() {
        state.tryToShot();
    }
    public void shot() {
        System.out.println("英雄射出一發子彈");
    }
    public void tryToKick() {
        state.tryToKick();
    }
    public void kick() {
        System.out.println("英雄向前方踢了一腳");
    }
    public void tryToJump() {
        state.tryToJump();
    }
    public void jump() {
        System.out.println("英雄往上跳");
    }
    public void tryToMovingToRight() {
        state.tryToMovingToRight();
    }
    public void moveToRight() {
        System.out.println("英雄往右走");
    }
    public void tryToMovingToLeft() {
        state.tryToMovingToLeft();
    }
    public void moveToLeft() {
        System.out.println("英雄往左走");
    }
    public void tryToDoSomething() {
        state.tryToDoSomething();
    }
    public void doNothing() {
        System.out.println("什麼事也沒發生");
    }
    public void tryToBecomeSnipperMode() {
        state.tryToBecomeSnipperMode();
    }
    public void tryToBecomeStandingMode() {
        state.tryToBecomeStandingMode();
    }
    public void tryToBecomeBoostMode() {
        state.tryToBecomeBoostMode();
    }
}
製作獨立物件的子代:StandingState、SnipperState、BoostState(State 物件)
public class StandingState implements State {
    private Hero hero;
    public StandingState(Hero hero) {
        this.hero = hero;
    }
    @Override
    public void tryToShot() {
        hero.shot();
    }
    @Override
    public void tryToKick() {
        hero.kick();
    }
    @Override
    public void tryToJump() {
        hero.jump();
    }
    @Override
    public void tryToMovingToRight() {
        hero.moveToRight();
    }
    @Override
    public void tryToMovingToLeft() {
        hero.moveToLeft();
    }
    @Override
    public void tryToDoSomething() {
        hero.doNothing();
    }
    @Override
    public void tryToBecomeSnipperMode() {
        hero.changeState(new SnipperState(hero));
    }
    @Override
    public void tryToBecomeStandingMode() {
        hero.changeState(new StandingState(hero));
    }
    @Override
    public void tryToBecomeBoostMode() {
        hero.changeState(new BoostState(hero));
    }
}
public class SnipperState implements State {
    private Hero hero;
    public SnipperState(Hero hero) {
        this.hero = hero;
    }
    @Override
    public void tryToShot() {
        hero.shot();
    }
    @Override
    public void tryToKick() {
        hero.doNothing();
    }
    @Override
    public void tryToJump() {
        hero.doNothing();
    }
    @Override
    public void tryToMovingToRight() {
        hero.doNothing();
    }
    @Override
    public void tryToMovingToLeft() {
        hero.doNothing();
    }
    @Override
    public void tryToDoSomething() {
        hero.doNothing();
    }
    @Override
    public void tryToBecomeSnipperMode() {
        hero.changeState(new SnipperState(hero));
    }
    @Override
    public void tryToBecomeStandingMode() {
        hero.changeState(new StandingState(hero));
    }
    @Override
    public void tryToBecomeBoostMode() {
        hero.changeState(new BoostState(hero));
    }
}
public class BoostState implements State {
    private Hero hero;
    public BoostState(Hero hero) {
        this.hero = hero;
    }
    @Override
    public void tryToShot() {
        hero.doNothing();
    }
    @Override
    public void tryToKick() {
        hero.doNothing();
    }
    @Override
    public void tryToJump() {
        hero.jump();
    }
    @Override
    public void tryToMovingToRight() {
        hero.moveToRight();
    }
    @Override
    public void tryToMovingToLeft() {
        hero.moveToLeft();
    }
    @Override
    public void tryToDoSomething() {
        hero.doNothing();
    }
    @Override
    public void tryToBecomeSnipperMode() {
        hero.changeState(new BoostState(hero));
    }
    @Override
    public void tryToBecomeStandingMode() {
        hero.changeState(new StandingState(hero));
    }
    @Override
    public void tryToBecomeBoostMode() {
        hero.changeState(new BoostState(hero));
    }
}
製作掌機的搖桿:VideoGameController
public class VideoGameController {
    private Hero hero;
    public VideoGameController(Hero hero) {
        this.hero = hero;
    }
    public void pushButtonY() {
        hero.tryToShot();
    }
    public void pushButtonX() {
        hero.tryToKick();
    }
    public void pushButtonB() {
        hero.tryToJump();
    }
    public void pushButtonA() {
        hero.tryToDoSomething();
    }
    public void pushButtonRightArrow() {
        hero.tryToMovingToRight();
    }
    public void pushButtonLeftArrow() {
        hero.tryToMovingToLeft();
    }
    public void holdButtonR() {
        hero.tryToBecomeSnipperMode();
    }
    public void releaseButtonR() {
        hero.tryToBecomeStandingMode();
    }
    public void holdButtonL() {
        hero.tryToBecomeBoostMode();
    }
    public void releaseButtonL() {
        hero.tryToBecomeStandingMode();
    }
}
測試,模擬玩家不斷在不同模式間嘗試按鈕:VideoGameStateSample
public class VideoGameStateSample {
    public static void main(String[] args) {
        Hero hero = new Hero();
        VideoGameController controller = new VideoGameController(hero);
        System.out.println("---嘗試基本按鈕---");
        standardOperation(controller);
        System.out.println("\n---按住按鈕 R---");
        controller.holdButtonR();
        standardOperation(controller);
        System.out.println("\n---放開按鈕 R---");
        controller.releaseButtonR();
        standardOperation(controller);
        System.out.println("\n---按住按鈕 L---");
        controller.holdButtonL();
        standardOperation(controller);
        System.out.println("\n---放開按鈕 L---");
        controller.releaseButtonL();
        standardOperation(controller);
    }
    private static void standardOperation(VideoGameController controller) {
        controller.pushButtonY();
        controller.pushButtonX();
        controller.pushButtonB();
        controller.pushButtonA();
        controller.pushButtonRightArrow();
        controller.pushButtonLeftArrow();
    }
}
製作獨立物件的虛擬層親代:State
/** @abstract */
class State {
  /** @param {Hero} hero */
  constructor(hero) {
    this.hero = hero;
  }
  tryToShot() { return; }
  tryToKick() { return; }
  tryToJump() { return; }
  tryToMovingToRight() { return; }
  tryToMovingToLeft() { return; }
  tryToDoSomething() { return; }
  tryToBecomeSnipperMode() { return; }
  tryToBecomeStandingMode() { return; }
  tryToBecomeBoostMode() { return; }
}
製作具有多個邏輯判斷的物件:Hero(Context 物件)
class Hero {
  constructor() {
    /** @type {State} */
    this.state = new StandingState(this);
  }
  /** @param {State} state */
  changeState(state) {
    this.state = state;
  }
  getState() {
    return this.state;
  }
  tryToShot() {
    this.state.tryToShot();
  }
  shot() {
    console.log("英雄射出一發子彈");
  }
  tryToKick() {
    this.state.tryToKick();
  }
  kick() {
    console.log("英雄向前方踢了一腳");
  }
  tryToJump() {
    this.state.tryToJump();
  }
  jump() {
    console.log("英雄往上跳");
  }
  tryToMovingToRight() {
    this.state.tryToMovingToRight();
  }
  moveToRight() {
    console.log("英雄往右走");
  }
  tryToMovingToLeft() {
    this.state.tryToMovingToLeft();
  }
  moveToLeft() {
    console.log("英雄往左走");
  }
  tryToDoSomething() {
    this.state.tryToDoSomething();
  }
  doNothing() {
    console.log("什麼事也沒發生");
  }
  tryToBecomeSnipperMode() {
    this.state.tryToBecomeSnipperMode();
  }
  tryToBecomeStandingMode() {
    this.state.tryToBecomeStandingMode();
  }
  tryToBecomeBoostMode() {
    this.state.tryToBecomeBoostMode();
  }
}
製作獨立物件的子代:StandingState、SnipperState、BoostState(State 物件)
class StandingState extends State {
  /** @param {Hero} hero */
  constructor(hero) {
    super(hero);
  }
  /** @override */
  tryToShot() {
    this.hero.shot();
  }
  /** @override */
  tryToKick() {
    this.hero.kick();
  }
  /** @override */
  tryToJump() {
    this.hero.jump();
  }
  /** @override */
  tryToMovingToRight() {
    this.hero.moveToRight();
  }
  /** @override */
  tryToMovingToLeft() {
    this.hero.moveToLeft();
  }
  /** @override */
  tryToDoSomething() {
    this.hero.doNothing();
  }
  /** @override */
  tryToBecomeSnipperMode() {
    this.hero.changeState(new SnipperState(this.hero));
  }
  /** @override */
  tryToBecomeStandingMode() {
    this.hero.changeState(new StandingState(this.hero));
  }
  /** @override */
  tryToBecomeBoostMode() {
    this.hero.changeState(new BoostState(this.hero));
  }
}
class SnipperState extends State {
  /** @param {Hero} hero */
  constructor(hero) {
    super(hero);
  }
  /** @override */
  tryToShot() {
    this.hero.shot();
  }
  /** @override */
  tryToKick() {
    this.hero.doNothing();
  }
  /** @override */
  tryToJump() {
    this.hero.doNothing();
  }
  /** @override */
  tryToMovingToRight() {
    this.hero.doNothing();
  }
  /** @override */
  tryToMovingToLeft() {
    this.hero.doNothing();
  }
  /** @override */
  tryToDoSomething() {
    this.hero.doNothing();
  }
  /** @override */
  tryToBecomeSnipperMode() {
    this.hero.changeState(new SnipperState(this.hero));
  }
  /** @override */
  tryToBecomeStandingMode() {
    this.hero.changeState(new StandingState(this.hero));
  }
  /** @override */
  tryToBecomeBoostMode() {
    this.hero.changeState(new BoostState(this.hero));
  }
}
class BoostState extends State {
  /** @param {Hero} hero */
  constructor(hero) {
    super(hero);
  }
  /** @override */
  tryToShot() {
    this.hero.doNothing();
  }
  /** @override */
  tryToKick() {
    this.hero.doNothing();
  }
  /** @override */
  tryToJump() {
    this.hero.jump();
  }
  /** @override */
  tryToMovingToRight() {
    this.hero.moveToRight();
  }
  /** @override */
  tryToMovingToLeft() {
    this.hero.moveToLeft();
  }
  /** @override */
  tryToDoSomething() {
    this.hero.doNothing();
  }
  /** @override */
  tryToBecomeSnipperMode() {
    this.hero.changeState(new BoostState(this.hero));
  }
  /** @override */
  tryToBecomeStandingMode() {
    this.hero.changeState(new StandingState(this.hero));
  }
  /** @override */
  tryToBecomeBoostMode() {
    this.hero.changeState(new BoostState(this.hero));
  }
}
製作掌機的搖桿:VideoGameController
class VideoGameController {
  /** @param {Hero} hero */
  constructor(hero) {
    this.hero = hero;
  }
  pushButtonY() {
    this.hero.tryToShot();
  }
  pushButtonX() {
    this.hero.tryToKick();
  }
  pushButtonB() {
    this.hero.tryToJump();
  }
  pushButtonA() {
    this.hero.tryToDoSomething();
  }
  pushButtonRightArrow() {
    this.hero.tryToMovingToRight();
  }
  pushButtonLeftArrow() {
    this.hero.tryToMovingToLeft();
  }
  holdButtonR() {
    this.hero.tryToBecomeSnipperMode();
  }
  releaseButtonR() {
    this.hero.tryToBecomeStandingMode();
  }
  holdButtonL() {
    this.hero.tryToBecomeBoostMode();
  }
  releaseButtonL() {
    this.hero.tryToBecomeStandingMode();
  }
}
測試,模擬玩家不斷在不同模式間嘗試按鈕:VideoGameStateSample
/** @param {VideoGameController} controller */
const standardOperation = (controller) => {
  controller.pushButtonY();
  controller.pushButtonX();
  controller.pushButtonB();
  controller.pushButtonA();
  controller.pushButtonRightArrow();
  controller.pushButtonLeftArrow();
};
const videoGameStateSample = () => {
  const hero = new Hero();
  const controller = new VideoGameController(hero);
  console.log("---嘗試基本按鈕---");
  standardOperation(controller);
  console.log("\n---按住按鈕 R---");
  controller.holdButtonR();
  standardOperation(controller);
  console.log("\n---放開按鈕 R---");
  controller.releaseButtonR();
  standardOperation(controller);
  console.log("\n---按住按鈕 L---");
  controller.holdButtonL();
  standardOperation(controller);
  console.log("\n---放開按鈕 L---");
  controller.releaseButtonL();
  standardOperation(controller);
};
videoGameStateSample();
State 模式有趣的地方,在於要改善方法內因為過多的 if - else if - else 或 switch case 導致程式碼過多的情況,因為過多的程式碼通常暗示著「難以改動」的可能,違反追求高彈性、易於修改的理念。
實作時觀察到有三點要多加注意:
State 的虛擬層規範的方法足以用於所有的 State?Context 在呼叫方法時,可以連帶更改自己的狀態?然而,就我自己的開發經驗,使用的 if - else if - else 或 switch case 不一定是糟糕的做法,假如邏輯判斷的數量只有幾個的話,反而不用特地抽離、製作成 State 物件,避免會增加開發上的麻煩。
講來講去可以發現,什麼情況下需要轉換,會是更重要的問題。
我想一個好的開發者如果能了解現在以及往後的可能需求,將更有把握判斷要不要轉換。
最後,嘗試使用後明顯感受到,那些依賴狀態的變換而有不同程式碼要執行的方法們,不用再寫一大堆判斷了,單單一、兩行就做到相同的事情,這感覺真棒!
明天將介紹 Behavioural patterns 的第九個模式:Strategy 模式。