設計模式系列之「狀態模式」

YoungManSter發表於2017-12-02

“歡迎準時準點收看俗到掉渣的《小Y講堂》節目,大家好,我是小Y,一個集性感毛髮與才華於一身的程式猿。很多小夥伴應該都有同感:主人公隻身英勇炸炮樓的情節在抗日題材的影視節目中可謂是習空見慣,情節的魅力點在於主人公扛炸藥包、放置、離開、點火這一過程中的每一個環節都環環相扣,扣人心絃,讓人情緒起伏不定。如果有人此刻問小Y看到這種情節電影心裡會聯想到什麼?那不是廢話嗎?當然是《小Y講堂》這次要講的主題——狀態模式!!”

一、狀態模式的概念

狀態模式定義

當一個物件內在狀態改變時允許其改變行為,這個物件看起來像改變了其類。

使用場景
  • 行為隨狀態的改變而改變。
  • 如果需要使用大量的條件、分支判斷。
角色介紹

狀態模式通用UML

  • State抽象狀態角色

    介面或抽象類,負責物件狀態定義,並且封裝環境角色以實現狀態切換。

  • ConcreteState具體狀態角色

    具體狀態主要有兩個職責:一是處理本狀態下的事情,二是從本狀態如何過渡到其他狀態。

  • Context環境角色

    定義客戶端需要的介面,並且負責具體狀態的切換。

二、小Y炸炮樓

  排長:上面給我們的指示就是不惜一切代價炸掉鬼子的炮樓,大家能不能完成任務?
  眾戰士:誓死完成任務。
  排長:很好,小Y,扛上炸藥包,把炸藥包放到炮樓底下,然後立馬離開回來,然後我們炸它狗日的,不要慫,我們掩護你。
  小Y:。。。。
複製程式碼

1.炸炮樓過程狀態UML圖

炸炮樓過程狀態UML

2.執行任務

①先定義炸炮樓每個階段要做的事情

public abstract class LinkState{
   protected Context context;
   public void setContext(Context  context){
      this.context = context;
   }
   //扛炸藥包
   public abstract void carry();
   //放置炸藥包
   public abstract void put();
   //跑路
   public abstract void goaway();
   //點燃炸藥包
   public abstract void ignite();

}
複製程式碼

②扛上炸藥玩命跑路到炮樓底下

public class CarryingState extends LinkState{
  @Override
  public void carry() {
    System.out.println("小Y:報告排長,我已跑到炮樓底下");
  }

  @Override
  public void put() {
    super.context.setLinkState(Context.puttingState);
    super.context.getLinkState().put();
  }
  @Override
  public void goaway() {
   System.out.println("小Y:還沒放置炸藥成功,撤離失敗");
  }
  @Override
  public void ignite() {
     System.out.println("小Y:還沒放置炸藥成功,引爆失敗");
  }
}
複製程式碼

③把炸藥放置在炮樓底下

public class PuttingState extends LinkState{
   @Override
  public void carry() {
    System.out.println("小Y:還在敵方區,無法再扛炸藥包");
  }

  @Override
  public void put() {
    System.out.println("小Y:好險,成功把放置炸藥包");
  }
  @Override
  public void goaway() {
    super.context.setLinkState(Context.goawayingState);
    super.context.getLinkState().goaway();
  }
  @Override
  public void ignite() {
     System.out.println("小Y:還沒逃離敵方區,無法引爆炸藥");
  }
}
複製程式碼

④放置好炸藥還不趕緊跑路,等被炸藥炸啊?

public class GoAwayingState extends LinkState{
   @Override
  public void carry() {
    System.out.println("小Y:已經逃離,無法再扛炸藥包");
  }

  @Override
  public void put() {
    System.out.println("小Y:已經逃離,無法進行炸藥包放置");
  }
  @Override
  public void goaway() {
     System.out.println("小Y:呼~~,終於撿回條小命");
  }
  @Override
  public void ignite() {
     super.context.setLinkState(Context.ignitingState);
    super.context.getLinkState().ignite();
  }
}
複製程式碼

⑤小Y成功逃離,趕緊引爆啊

public class IgnitingState extends LinkState{
   @Override
  public void carry() {
    System.out.println("炮樓沒徹底倒下,小Y繼續扛炸藥");
    super.context.setLinkState(Context.carryingState);
    super.context.getLinkState().carry();
  }

  @Override
  public void put() {
    System.out.println("小Y:炸藥已引爆,無須進行炸藥包放置");
  }
  @Override
  public void goaway() {
     System.out.println("小Y:炸藥已引爆,早已撤離");
  }
  @Override
  public void ignite() {
      System.out.println("小Y:炸樓完成,準備加薪晉職");
  }
}
複製程式碼

⑥負責切換炸樓每個階段的狀態

public class Context {
  //定義炸樓所有狀態
  public final static CarryingState carryingState = new CarryingState();
  public final static PuttingState puttingState = new PuttingState();
  public final static GoAwayingState goawayingState = new GoAwayingState();
  public final static IgnitingState ignitingState = new IgnitingState();

  //設定當前狀態
  private LinkState linkState;

  public LinkState getLinkState() {
    return linkState;
  }

  public void setLinkState(LinkState linkState) {
     this.linkState= linkState;
     //把當前的所處的狀態通知到各個實現類中
     this.linkState.setContext(this);
  }

  public void carry(){
    this.linkState.carry();
  }

  public void put(){
    this.linkState.put();
  }

 public void goaway(){
    this.linkState.goaway();
  }

 public void ignite(){
    this.linkState.ignite();
  }

 }
複製程式碼

⑦炸樓執行

public class Client {
   public static void main(String[] args) {
       Context context = new Context();
       context.setLinkState(Context.carryingState);
       context.carry();
       context.ignite();
       context.put();
       context.goaway();
       context.ignite();
    }
}  
複製程式碼

輸出的結果為:

①小Y:報告排長,我已跑到炮樓底下
②小Y:還沒放置炸藥成功,引爆失敗
③小Y:好險,成功把放置炸藥包
④小Y:呼~~,終於撿回條小命
⑤小Y:炸樓完成,準備加薪晉職
複製程式碼

三、總結分析

①定義了一個LinkState抽象類,宣告瞭一個Context變數,這個是串聯各個狀態的封裝類。封裝的目的很明顯,就是環節狀態物件內部狀態的變化不被呼叫類知曉,符合迪米特法則。

②Context環境角色具有兩個不成文的約束:一是有幾個狀態物件就宣告幾個靜態常量;二是具有狀態抽象角色定義的所有行為,具體執行使用委託的方式。

③每個具體狀態角色所要做的事情就是處理本狀態下所對應的動作,不屬於本狀態處理的行為,就會切換狀態並把該行為委託給負責該行為的狀態。

④各個狀態可以相互切換。

狀態模式的優點

  • 體現了開閉原則和單一職責原則,每個狀態都是一個子類,你要增加狀態就要增加子類,你要修改狀態,你只修改一個子類就可以了。
  • 符合迪米特法則。
  • 封裝性非常好
  • 結構清晰,避免了過多的switch...case或者if...else語句的使用,避免了程式的複雜性,提高可維護性。

####小Y勇炸敵方炮樓,原想可以安享餘生,結果被上級嘉獎“專職炸樓官”。。。。。。

設計模式系列之「狀態模式」

掘金地址:https://juejin.im/post/59c4c2c6f265da066875f9f8

Android技術交流吧

相關文章