千家信息网

如何使用行为型模式

发表于:2025-01-21 作者:千家信息网编辑
千家信息网最后更新 2025年01月21日,本篇内容介绍了"如何使用行为型模式"的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!状态模式状态模式的好
千家信息网最后更新 2025年01月21日如何使用行为型模式

本篇内容介绍了"如何使用行为型模式"的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

状态模式

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

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

Context: 上下文,定义了客户程序需要的接口并维护一个状态类。

State: 状态类,定义一个接口以封装上下文环境的一个特定状态相关的行为,与状态相关的操作委托给具体的state对象进行处理。

Concrete State: 具体状态类

状态模式UML

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

状态模式

State

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

Concrete State

回城状态

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

状态模式

还是拿游戏举例,最终的目的都是为了赢,但是具体的方式可能要根据对方的阵容做出改变。

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

模板方法

都知道电脑的组装都是有一个模板的,需要哪些零件都是固定的,不同的是零件的采用不同。这样我们就可以把组装作为一个行为模板给封装起来。

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

到这里有没有印象之前说过的建造者模式,可以说非常相似,因为建造者模式就是借助了模板方法模式来实现的。

备忘录模式

在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。备忘录嘛,也是比较形象的,就像我们解题的时候可以把过程写下来看,最后可以按照步骤检查,知道哪里出了问题,从那里恢复解题的过程,从而正确解题。

Originator: 发起者是我们需要记住状态的对象,以便在某个时刻恢复它。

Caretaker: 管理者是负责触发发起者的变化或者触发发起者返回先前状态动作的类。

Memento: 备忘录是负责存储发起者内部状态的类。备忘录提供了设置状态和获取状态的方法,但是这些方法应该对管理者隐藏。

**场景:**大家都玩过超级玛丽,合金弹头,或者i wanna这类的游戏叭。有什么组成呢,一个是玩家(Originator),一个是经常需要存档的档案(Memento),还有一个是游戏后台管理(Caretaker)。

对于玩家而言,可以存档(setMemento和createMemento),也可以读档,恢复到上次存档的位置(restoreMemento)。

备忘录UML

普通备忘录模式:

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)肯定是可以的。

又来想,要存的是自己,要拷贝的是自己来充当备忘录,为了节省空间,会用到之后讲的原型模式

到这里的话,普通的备忘录模式就已经讲完了,下面要讲的都是基于普通上进行的改进,可看可不看。

基于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("");        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 states = new HashMap<>();    //保存着玩家的状态    public GameStateS(PlayerS playerS) {        states.put("schedule", playerS.getSchedule());        states.put("grade", playerS.getGrade());        states.put("equipment", playerS.getEquipment());    }    public HashMap 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 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 typeContent = new ArrayList<>();    //保存用户写的字    public TypeContent(List typeContent) {        this.typeContent = typeContent;    }    public List 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());    }}

"如何使用行为型模式"的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注网站,小编将为大家输出更多高质量的实用文章!

0