天天5分鐘-行爲型模式(三)

狀態模式

狀態模式的好處是將與特定狀態相關的行爲局部化,而且將不一樣狀態的行爲分割開來java

將特定相關的行爲都放入一個對象中,因爲全部與狀態相關的代碼都存在於某個ConcreteState 中,因此經過定義新的子類能夠很容易地增長新的狀態和轉換。算法

Context: 上下文,定義了客戶程序須要的接口並維護一個狀態類。網絡

State: 狀態類,定義一個接口以封裝上下文環境的一個特定狀態相關的行爲,與狀態相關的操做委託給具體的state對象進行處理。ide

Concrete State: 具體狀態類ui

狀態模式UML

採用的例子是王者裏面的,要麼在打團要麼在打團的路上,這就涉及到了狀態的轉換。this

狀態模式

Statespa

public interface State {
    void handle(Context context);
}

Concrete Staterest

回城狀態code

public class ConcreteStateBack implements State{
    @Override
    public void handle(Context context) {
        System.out.println("沒狀態了,回城補個狀態先");
        context.setState(new ConcreteStateWalk());
    }
}

打團狀態對象

public class ConcreteStateFight implements State{
    @Override
    public void handle(Context context) {
        System.out.println("大招一按,蒼穹一開,雙手一放,要麼黑屏,要麼五殺");
        context.setState(new ConcreteStateBack());
    }
}

打團的路上狀態

public class ConcreteStateWalk implements State{
    @Override
    public void handle(Context context) {
        System.out.println("狀態已滿,等我集合打團");
        context.setState(new ConcreteStateFight());
    }
}

失敗狀態

public class ConcreteStateDefeated implements State{
    @Override
    public void handle(Context context) {
        System.out.println("Defeated!!!");
    }
}

Context

public class Context {
    private State state;

    public State getState() {
        return state;
    }

    public void setState(State state) {
        this.state = state;
    }

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

    public void request() {
        state.handle(this);
    }
}

Client

public class Client {
    public static void main(String[] args) {
        Context context = new Context(new ConcreteStateWalk());
        for (int i = 0; i < 8; i++) {
            context.request();
        }

        context.setState(new ConcreteStateDefeated());
        context.request();

    }
}

策略模式

策略模式是一種定義一系列算法的方法,可是這些方法最終完成的都是相同的工做,只是策略不一樣,也就是實現不一樣。

它能夠以相同的方式來調用全部的算法,減小了各類算法類和使用算法類之間的耦合。

Context: 上下文,內部有個strategy屬性,而且可以經過其調用策略

Strategy: 策略接口或者抽象類,定義了策略的方法

Concrete Strategy: 具體的策略

狀態模式UML

image-20210501160904746

狀態模式

仍是拿遊戲舉例,最終的目的都是爲了贏,可是具體的方式可能要根據對方的陣容作出改變。

Context

public class Context {
    Strategy strategy;

    public Context(Strategy strategy) {
        this.strategy = strategy;
    }

    public void invokeStrategy() {
        strategy.play();
    }
}

Strategy

public interface Strategy {
    void play();
}

Concrete Strategy

public class ConcreteStrategyAttackMid implements Strategy{
    @Override
    public void play() {
        System.out.println("集合進攻中路");
    }
}
public class ConcreteStrategyGank implements Strategy{
    @Override
    public void play() {
        System.out.println("轉線抓人推塔");
    }
}
public class ConcreteStrategyInvade implements Strategy{
    @Override
    public void play() {
        System.out.println("入侵野區");
    }
}

Client

public class Client {
    public static void main(String[] args) {
        Context context = new Context(new ConcreteStrategyAttackMid());
        context.invokeStrategy();

        context = new Context(new ConcreteStrategyGank());
        context.invokeStrategy();

        context = new Context(new ConcreteStrategyInvade());
        context.invokeStrategy();
    }
}

模板方法模式

模板方法就是定義一個操做的算法的骨架,而將一些步驟延遲到子類種。模板方法使得子類能夠不改變一個算法的結構便可重定義改算法的某些特定步驟。

簡而言之就是,我給你一個模板,步驟是哪些,可是具體怎麼實現看我的。

Abstract Class: 實現一個模板方法,定義了算法的估計

Concrete Class: 對模板中各個算法的不一樣實現

模板方法UML

image-20210501165013009

模板方法

都知道電腦的組裝都是有一個模板的,須要哪些零件都是固定的,不一樣的是零件的採用不一樣。這樣咱們就能夠把組裝做爲一個行爲模板給封裝起來。

Abstract Class

public abstract class AbstractClass {
    public void assemble() {
        System.out.println("開始模板組裝電腦");
        cpu();
        radiating();
        screen();
    }

    public abstract void cpu();
    public abstract void radiating();
    public abstract void screen();
}

Concrete Class

public class ConcreteClassMid extends AbstractClass{
    @Override
    public void cpu() {
        System.out.println("Intel 10900K, Intel偶爾的神");
    }

    @Override
    public void radiating() {
        System.out.println("雙銅散熱管");
    }

    @Override
    public void screen() {
        System.out.println("75hz高素質屏幕");
    }
}

圖片來源網絡,侵刪

public class ConcreteClassTop extends AbstractClass{
    @Override
    public void cpu() {
        System.out.println("AMD5950X,AMD永遠的神");
    }

    @Override
    public void radiating() {
        System.out.println("雙銅散熱管加液冷散熱");
    }

    @Override
    public void screen() {
        System.out.println("144hz電競屏");
    }
}

Client

public class Client {
    public static void main(String[] args) {
        AbstractClass templateToAssembleComputer = new ConcreteClassMid();
        templateToAssembleComputer.assemble();

        templateToAssembleComputer = new ConcreteClassTop();
        templateToAssembleComputer.assemble();
    }
}

到這裏有沒有印象以前說過的建造者模式,能夠說很是類似,由於建造者模式就是藉助了模板方法模式來實現的。

驚不驚喜-行爲3

備忘錄模式

在不破壞封裝的前提下,捕獲一個對象的內部狀態,並在該對象以外保存這個狀態,這樣能夠在之後將對象恢復到原先保存的狀態。備忘錄嘛,也是比較形象的,就像咱們解題的時候能夠把過程寫下來看,最後能夠按照步驟檢查,知道哪裏出了問題,從那裏恢復解題的過程,從而正確解題。

Originator: 發起者是咱們須要記住狀態的對象,以便在某個時刻恢復它。

Caretaker: 管理者是負責觸發發起者的變化或者觸發發起者返回先前狀態動做的類。

Memento: 備忘錄是負責存儲發起者內部狀態的類。備忘錄提供了設置狀態和獲取狀態的方法,可是這些方法應該對管理者隱藏。

場景:你們都玩過超級瑪麗,合金彈頭,或者i wanna這類的遊戲叭。有什麼組成呢,一個是玩家(Originator),一個是常常須要存檔的檔案(Memento),還有一個是遊戲後臺管理(Caretaker)。

對於玩家而言,能夠存檔(setMemento和createMemento),也能夠讀檔,恢復到上次存檔的位置(restoreMemento)。

備忘錄UML

image-20210430163508915

普通備忘錄模式:

Originator (玩家)

public class OriginatorPlayer {
    private String name;
    private String status;

    public OriginatorPlayer(String name) {
        this.name = name;
    }

    //交給遊戲後臺處理    1
    public MementoGameState create() {
        return new MementoGameState(status);
    }

    //玩家存檔           2
    public void save(String status) {
        this.status = status;
        System.out.println("存檔:" + status);
    }

    public void read(MementoGameState gameState) {
        this.status = gameState.getGameStatus();
        System.out.println("讀檔:" + status);
    }

}

其實我以爲1,2步是能夠合起來寫成下面這樣子

public MementoGameState save(String status) {
    this.status = status;
    System.out.println("存檔:" + status);
    return new MementoGameState(status);
}

Memento (遊戲狀態)

public class MementoGameState {
    private String gameStatus = "";

    public MementoGameState(String gameStatus) {
        this.gameStatus = gameStatus;
    }

    public String getGameStatus() {
        return gameStatus;
    }

}

Caretaker (後臺管理)

public class CaretakerGameManager {
    MementoGameState gameState;
    
    public MementoGameState getGameState() {
        return gameState;
    }

    //這是後臺真正存檔
    public void setGameState(MementoGameState gameState) {
        System.out.println("系統已經存檔: " + gameState.getGameStatus());
        this.gameState = gameState;
    }
}

Client

public class Client {
    public static void main(String[] args) {
        OriginatorPlayer cutey = new OriginatorPlayer("cutey");
        CaretakerGameManager gameManager = new CaretakerGameManager();

        //玩家本身點了存檔,可是不必定存成功
        cutey.save("第一關");
        //後臺要處理玩家的存檔的請求(imperfect.create())
        gameManager.setGameState(cutey.create());

        cutey.save("第二關");
        gameManager.setGameState(cutey.create());

        //這種狀況就是可能咱們點了存檔,尚未成功就退出了
        cutey.save("第三關");

        //讀取檔案
        cutey.read(gameManager.getGameState());
    }
}

仔細地看代碼會發現,說到底講備忘錄,備忘的是否是就是遊戲角色的狀態,爲此專門有一個類(GameState)來存這個狀態。

在恢復狀態的時候,在讀取備忘錄中的狀態賦給遊戲角色中。因此歸根結底都是如何保存遊戲角色的狀態,而後在須要的時候能夠恢復。

那是否是必定要新建一個類來幫咱們保存呢,若是咱們直接保存的是上個階段的遊戲角色(而不是單純的遊戲狀態),而後讀檔的時候直接讀上個階段的遊戲角色能夠嗎?

也就是發起人(Originator)也充當了備忘錄(Memento)確定是能夠的。

又來想,要存的是本身,要拷貝的是本身來充當備忘錄,爲了節省空間,會用到以後講的原型模式

到此爲止-行爲3

到這裏的話,普通的備忘錄模式就已經講完了,下面要講的都是基於普通上進行的改進,可看可不看。

基於clone的備忘錄模式:

玩家:

public class PlayerC implements Cloneable {
    
    private String name;
    private String state;

    public PlayerC(String name) {
        this.name = name;
    }

    public String getState() {
        return state;
    }

    public void setState(String state) {
        System.out.println("玩家進行到:" + state);
        this.state = state;
    }


    //存檔,存的是本身
    public PlayerC create() {
        System.out.println("玩家存檔:" + this.clone().getState());
        return this.clone();
    }

    
    //讀檔
    public void play(PlayerC playerC) {
        System.out.println("玩家讀檔:" + playerC.getState());
        setState(playerC.getState());
    }
    
    //克隆本身
    @Override
    public PlayerC clone() {
        try {
            return (PlayerC) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return null;
    }

}

後臺管理:

public class GameManagerC {
    PlayerC playerC;

    public PlayerC getPlayerC() {
        return playerC;
    }

    //真正的存檔
    public void setPlayerC(PlayerC playerC) {
        this.playerC = playerC;
    }
}

Client:

public class ClientC {
    public static void main(String[] args) throws CloneNotSupportedException {
        PlayerC playerC = new PlayerC("perfext");
        GameManagerC gameManagerC = new GameManagerC();

        //分析和普通模式同樣,就再也不贅述
        playerC.setState("%10");
        gameManagerC.setPlayerC(playerC.create());

        playerC.setState("%20");
        gameManagerC.setPlayerC(playerC.create());

        playerC.setState("%30");

        playerC.play(gameManagerC.getPlayerC());

    }
}

上面甚至還不是最簡潔的,由於其實咱們存檔仍是要在後臺管理類裏面存,固然這是但願看到的。想一想後臺管理類的做用是幹嗎的,是用來管理備忘錄的,既然備忘錄類均可以省略,後臺管理類天然也能夠精簡掉。

也就是說,玩家的狀態保存在玩家的內部,可是這與定義不符合,在一開始我特地加粗了」在該對象以外保存這個狀態「。因此說本篇博客就再也不講述這種方式的實現,也比較簡單(提示:在玩家類內部聲明一個成員變量做爲恢復的遊戲角色)。

上面講的都是比較簡單的備忘錄模式,還有兩種比較經常使用的,一種是一個角色有多個狀態同時須要備忘,先講這種,另一種賣個關子。

多狀態的備忘錄模式:

場景:一個角色有多個狀態,那仍是拿打遊戲的例子,不過遊戲角色不只僅是第幾關。新的遊戲角色有,打到了哪一個階段,等級是多少以及裝備三個狀態

玩家:

public class PlayerS {
    private String name;
    private String equipment;
    private String schedule;
    private String grade;

    //存檔
    public GameStateS save() {
        System.out.println("玩家存檔:" + toString());
        return new GameStateS(this);
    }

    //讀檔案,從保存的狀態中一個個讀出來
    public void read(GameStateS gameStateS) {
        equipment = (String) gameStateS.getStates().get("equipment");
        schedule = (String) gameStateS.getStates().get("schedule");
        grade = (String) gameStateS.getStates().get("grade");
        System.out.println("玩家讀檔:" + toString());
    }

    public PlayerS(String name) {
        this.name = name;
    }

    /**
     *  省略
     *    1.toString方法,用來方便打印
     *     2.set方法,用來方便玩家存檔
     *    3.get方法,方便存儲玩家的狀態
     */
    
}

遊戲狀態(檔案):

public class GameStateS {
    //多狀態,因此用hashmap來保存
    private HashMap<String, Object> states = new HashMap<>();

    //保存着玩家的狀態
    public GameStateS(PlayerS playerS) {
        states.put("schedule", playerS.getSchedule());
        states.put("grade", playerS.getGrade());
        states.put("equipment", playerS.getEquipment());
    }

    public HashMap<String, Object> getStates() {
        return states;
    }

}

後臺管理:

public class GameManagerS {
    private GameStateS gameStateS;

    public GameStateS getGameStateS() {
        return gameStateS;
    }

    //真正存檔
    public void setGameStateS(GameStateS gameStateS) {
        System.out.println("系統已經存檔!");
        this.gameStateS = gameStateS;
    }
}

Client:

public class ClientS {

    public static void main(String[] args) {
        PlayerS player = new PlayerS("perfext");
        GameManagerS gameManagerS = new GameManagerS();

        player.setSchedule("10%");
        player.setEquipment("2件套");
        player.setGrade("6級");
        gameManagerS.setGameStateS(player.save());

        player.setSchedule("30%");
        player.setEquipment("4件套");
        player.setGrade("10級");
        gameManagerS.setGameStateS(player.save());

        player.setSchedule("80%");
        player.setEquipment("6件套");
        player.setGrade("15級");
        System.out.println("忘記存檔了!已經打到了:");
        System.out.println(player.toString());

        player.read(gameManagerS.getGameStateS());
    }

}

本質仍是那樣,沒有太大變化,就是把狀態用hashmap作了一個封裝。

目前爲止,對於上面所講的全部備忘錄模式,不知道各位小夥伴有沒有發現一個問題,就是在恢復的時候,只能恢復特定的狀態(通常是最後備忘的那個狀態)。

可是在現實社會中,在碼字或者打代碼的時候總你可以ctrl + z(撤銷)好幾回,能夠撤銷回滿意的狀態。下面要講的應該能夠幫助到你。

撤銷屢次的備忘錄模式:

原諒我不知道怎麼高大上專業的表述這種備忘錄模式。

場景:再用遊戲講的話不太清楚,接下來打字員(Originator)打字,內容(Memento)交給電腦(Caretaker)保存來演示。

打字員:

public class Typist {
    private String name;
    private String word;    //最新的狀態
    private List<String> content = new ArrayList<>();        //全部的狀態
    int len = 0;    //狀態的位置,根據這個位置來讀取

    public Typist(String name) {
        this.name = name;
    }

    public void setWord(String word) {
        this.word = word;
    }

    //保存
    public TypeContent save() {
        content.add(word);
        System.out.println("打字員保存:" + word);
        len++;        //長度+1
        return new TypeContent(content);
    }

    //讀取
    public void read(TypeContent typeContent) {
        content = typeContent.getTypeContent();
        System.out.println("目前顯示:" + content.get(--len));    //讀完後長度-1
    }

}

內容:

public class TypeContent {
    private List<String> typeContent = new ArrayList<>();

    //保存用戶寫的字
    public TypeContent(List<String> typeContent) {
        this.typeContent = typeContent;
    }

    public List<String> getTypeContent() {
        return typeContent;
    }

}

電腦:

public class Computer {
    private TypeContent typeContent;

    public TypeContent getTypeContent() {
        return typeContent;
    }

    //真正保存用戶寫的字
    public void setTypeContent(TypeContent typeContent) {
        this.typeContent = typeContent;
    }
}

Client:

public class ClientM {
    public static void main(String[] args) {
        Typist perfext = new Typist("perfext");
        Computer computer = new Computer();

        perfext.setWord("abcd");
        computer.setTypeContent(perfext.save());
        perfext.setWord("efg");
        computer.setTypeContent(perfext.save());
        perfext.setWord("hijkl");
        computer.setTypeContent(perfext.save());
        perfext.setWord("mnopq");
        computer.setTypeContent(perfext.save());

        perfext.read(computer.getTypeContent());
        
        //模擬ctrl+z
        System.out.println("撤銷:");
        perfext.read(computer.getTypeContent());
        
        System.out.println("撤銷:");
        perfext.read(computer.getTypeContent());
        
        System.out.println("撤銷:");
        perfext.read(computer.getTypeContent());

    }
}

結果也和預期同樣,能夠屢次撤銷,至此,全部狀況下的備忘錄模式都講完了。

相關文章
相關標籤/搜索