乐趣区

关于后端开发:每天5分钟行为型模式三

状态模式

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

将特定相干的行为都放入一个对象中,因为所有与状态相干的代码都存在于某个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("%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());

    }
}

后果也和预期一样,能够屡次撤销,至此,所有状况下的备忘录模式都讲完了。

退出移动版