前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >每天5分钟-行为型模式(三)

每天5分钟-行为型模式(三)

作者头像
用户8902830
发布2021-08-12 11:01:36
3680
发布2021-08-12 11:01:36
举报
文章被收录于专栏:CodeNoneCodeNone

状态模式

状态模式的好处是将与特定状态相关的行为局部化,并且将不同状态的行为分割开来

将特定相关的行为都放入一个对象中,由于所有与状态相关的代码都存在于某个ConcreteState 中,所以通过定义新的子类可以很容易地增加新的状态和转换。

Context: 上下文,定义了客户程序需要的接口并维护一个状态类。 State: 状态类,定义一个接口以封装上下文环境的一个特定状态相关的行为,与状态相关的操作委托给具体的state对象进行处理。 Concrete State: 具体状态类

状态模式UML

采用的例子是王者里面的,要么在打团要么在打团的路上,这就涉及到了状态的转换。

状态模式

State

代码语言:javascript
复制
public interface State {
    void handle(Context context);
}

Concrete State

回城状态

代码语言:javascript
复制
public class ConcreteStateBack implements State{
    @Override
    public void handle(Context context) {
        System.out.println("没状态了,回城补个状态先");
        context.setState(new ConcreteStateWalk());
    }
}

打团状态

代码语言:javascript
复制
public class ConcreteStateFight implements State{
    @Override
    public void handle(Context context) {
        System.out.println("大招一按,苍穹一开,双手一放,要么黑屏,要么五杀");
        context.setState(new ConcreteStateBack());
    }
}

打团的路上状态

代码语言:javascript
复制
public class ConcreteStateWalk implements State{
    @Override
    public void handle(Context context) {
        System.out.println("状态已满,等我集合打团");
        context.setState(new ConcreteStateFight());
    }
}

失败状态

代码语言:javascript
复制
public class ConcreteStateDefeated implements State{
    @Override
    public void handle(Context context) {
        System.out.println("Defeated!!!");
    }
}

Context

代码语言:javascript
复制
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

代码语言:javascript
复制
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

代码语言:javascript
复制
public class Context {
    Strategy strategy;

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

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

Strategy

代码语言:javascript
复制
public interface Strategy {
    void play();
}

Concrete Strategy

代码语言:javascript
复制
public class ConcreteStrategyAttackMid implements Strategy{
    @Override
    public void play() {
        System.out.println("集合进攻中路");
    }
}
代码语言:javascript
复制
public class ConcreteStrategyGank implements Strategy{
    @Override
    public void play() {
        System.out.println("转线抓人推塔");
    }
}
代码语言:javascript
复制
public class ConcreteStrategyInvade implements Strategy{
    @Override
    public void play() {
        System.out.println("入侵野区");
    }
}

Client

代码语言:javascript
复制
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

代码语言:javascript
复制
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

代码语言:javascript
复制
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高素质屏幕");
    }
}

图片来源网络,侵删

代码语言:javascript
复制
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

代码语言:javascript
复制
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 (玩家)

代码语言:javascript
复制
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步是可以合起来写成下面这样子

代码语言:javascript
复制
public MementoGameState save(String status) {
    this.status = status;
    System.out.println("存档:" + status);
    return new MementoGameState(status);
}

Memento (游戏状态)

代码语言:javascript
复制
public class MementoGameState {
    private String gameStatus = "";

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

    public String getGameStatus() {
        return gameStatus;
    }

}

Caretaker (后台管理)

代码语言:javascript
复制
public class CaretakerGameManager {
    MementoGameState gameState;
    
    public MementoGameState getGameState() {
        return gameState;
    }

    //这是后台真正存档
    public void setGameState(MementoGameState gameState) {
        System.out.println("系统已经存档: " + gameState.getGameStatus());
        this.gameState = gameState;
    }
}

Client

代码语言:javascript
复制
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的备忘录模式:

玩家:

代码语言:javascript
复制
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;
    }

}

后台管理:

代码语言:javascript
复制
public class GameManagerC {
    PlayerC playerC;

    public PlayerC getPlayerC() {
        return playerC;
    }

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

Client:

代码语言:javascript
复制
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());

    }
}

上面甚至还不是最简洁的,因为其实我们存档还是要在后台管理类里面存,当然这是希望看到的。想想后台管理类的作用是干嘛的,是用来管理备忘录的,既然备忘录类都可以省略,后台管理类自然也可以精简掉。

也就是说,玩家的状态保存在玩家的内部,但是这与定义不符合,在一开始我特意加粗了”在该对象之外保存这个状态“。所以说本篇博客就不再讲述这种方式的实现,也比较简单(提示:在玩家类内部声明一个成员变量作为恢复的游戏角色)。

上面讲的都是比较简单的备忘录模式,还有两种比较常用的,一种是一个角色有多个状态同时需要备忘,先讲这种,另外一种卖个关子。

多状态的备忘录模式:

场景:一个角色有多个状态,那还是拿打游戏的例子,不过游戏角色不仅仅是第几关。新的游戏角色有,打到了哪个阶段,等级是多少以及装备三个状态

玩家:

代码语言:javascript
复制
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方法,方便存储玩家的状态
     */
    
}

游戏状态(档案):

代码语言:javascript
复制
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;
    }

}

后台管理:

代码语言:javascript
复制
public class GameManagerS {
    private GameStateS gameStateS;

    public GameStateS getGameStateS() {
        return gameStateS;
    }

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

Client:

代码语言:javascript
复制
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)保存来演示。

打字员:

代码语言:javascript
复制
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
    }

}

内容:

代码语言:javascript
复制
public class TypeContent {
    private List<String> typeContent = new ArrayList<>();

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

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

}

电脑:

代码语言:javascript
复制
public class Computer {
    private TypeContent typeContent;

    public TypeContent getTypeContent() {
        return typeContent;
    }

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

Client:

代码语言:javascript
复制
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());

    }
}

结果也和预期一样,可以多次撤销,至此,所有情况下的备忘录模式都讲完了。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-05-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 CodeNone 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 状态模式
    • 状态模式UML
      • 状态模式
      • 策略模式
        • 状态模式UML
          • 状态模式
          • 模板方法模式
            • 模板方法UML
              • 模板方法
              • 备忘录模式
                • 备忘录UML
                  • 普通备忘录模式:
                    • 基于clone的备忘录模式:
                      • 多状态的备忘录模式:
                        • 撤销多次的备忘录模式:
                        领券
                        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档