天天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());

    }
}

结果也和预期同样,能够屡次撤销,至此,全部状况下的备忘录模式都讲完了。

相关文章
相关标签/搜索