【デザインパターン - 学習ノート】23のデザインパターン - メメントモード メメント(原理説明+応用シナリオ紹介+事例紹介+Javaコード実装)

事例紹介

ゲームキャラクターには攻撃力と防御力があり、ボスと戦う前に自分のステータス(攻撃力と防御力)を保存し、ボスとの戦闘後に攻撃力と防御力が低下した場合、メモオブジェクトからメモオブジェクトに戻すことができます。戦闘前の状態。

伝統的なデザイン

ロールごとに、ロールのステータスを保存するクラスを設計します。

【分析する】

  • オブジェクトとは、オブジェクトの状態を保存するオブジェクトに相当しますが、このようにゲーム内でオブジェクトが多くなると管理が不便でコストも高くなります。
  • 従来の方法は、単純にバックアップを作成し、別のオブジェクトを作成して、バックアップが必要なデータをこの新しいオブジェクトに入れることですが、これではオブジェクトの内部の詳細が公開されてしまいます。
  • 最適化方法:メモモードを使用

導入

基本的な紹介

  • memento パターンはオブジェクトの内部状態をキャプチャし、カプセル化を破壊することなくこの状態をオブジェクトの外部に保存します。こうすることで、後でオブジェクトを元の保存状態に復元できます。
  • メモのパターンは次のように理解できます。実生活におけるメモは、実行すべき特定のことを記録したり、忘れないようにするために合意された共通の意見を記録したりするために使用されます。ソフトウェア レベルでは、メモ オブジェクトは主にオブジェクトの特定の状態やデータを記録するために使用され、ロールバックする場合は、回復操作のためにメモ オブジェクトから元のデータを取得できます。
  • メモモードは動作モードです

キャラクター

ここに画像の説明を挿入します

  • Originator(生成者): Originator キャラクターは、最新の状態を保存するときに Memento キャラクターを生成します。以前に保存した Memento キャラクターを Originator キャラクターに渡すと、そのキャラクターは、Memento キャラクターが生成されたときの状態に復元されます。
  • Memento(备忘录): Memento ロールは、Originator ロールの内部情報を統合します。Originator ロールの情報は Memento ロールに保存されますが、この情報は外部に公開されません(メソッドの許可文字の設定により)
  • Caretaker(负责人):Caretaker角色現在の状態を保存したい場合にOriginator角色通知されますOriginator角色インスタンスが生成され、Originator角色通知を受信した後に返されます後で元の状態に戻すために使用する、サンプルプログラムには必ず保存されます。2 つのインターフェース (API) のうち、狭いインターフェースのみを使用できるため、すべての内部情報にアクセスすることはできません (たとえば、ケース 3 の場合、金銭情報のみが取得でき、他の操作は実行できません)。生成されたものをブラック ボックスとして保存するだけですと の間には強い相関がありますが、と の間には弱い相関があります自分の内部情報を隠すMemento角色Caretaker角色Memento实例OriginatorCaretaker角色Memento实例Caretaker角色Memento角色Memento角色Caretaker角色Memento角色创建Memento实例Originator角色Memento角色Originator角色Memento角色Caretaker角色Memento角色Memento角色Caretaker角色

Caretaker角色【割り算と和の意味Originator角色

Caretaker ロールの責任は、いつスナップショットを取得するか、いつ Memento ロールを元に戻して保存するかを決定することです。一方、Originator ロールの責任は、Memento ロールを生成し、受け取った Memento ロールを使用して自身の状態を復元することです。この種の責任分担により、次のような需要の変化に対応する必要がある場合に、発信者の役割を変更する必要がありません。

  • 変更は何度でも元に戻すことができます
  • 変更を元に戻すだけでなく、現在の状態をファイルに保存することもできます。

[Caretaker ロールは、狭いインターフェイス (API) を通じてのみ Memento ロールを操作できます。Caretaker ロールが Memento ロールを自由に操作できる場合、何が起こるでしょうか?]

  • オブジェクトのカプセル化の破棄: Memento ロールは、Originator ロールのステータス データを保存する責任があります。Caretaker ロールが Memento ロールを自由に操作できる場合、Memento オブジェクトと Originator オブジェクトのカプセル化は破棄されます。
  • 安全でない状態の回復: Caretaker ロールが Memento ロールを自由に操作できる場合、Memento オブジェクトに保存されている状態データが変更され、状態回復でエラーが発生する可能性があります。
  • データの整合性は保証できません: Caretaker ロールが Memento ロールを自由に操作できる場合、状態データが間違った時点で保存され、データが不完全になる可能性があります。

事例の実装

ケース 1 (基本的なケース)

クラス図

ここに画像の説明を挿入します

  • Originator: 保存する必要があるオブジェクト
  • Memento: メモ オブジェクト。発信者オブジェクトのステータスを記録する責任があります。
  • Caretaker: 複数のメモ オブジェクトを保存する役割を担うガーディアン オブジェクト 一般的に、管理効率を向上させるためにコレクションが管理に使用されます。

成し遂げる

【発信者】

package com.atguigu.memento.theory;

public class Originator {
    
    

    /**
     * 角色的状态信息
     */
    private String state;

    public String getState() {
    
    
        return state;
    }

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

    /**
     * 编写一个方法,可以保存一个状态对象 Memento
     * 因此编写一个方法,返回 Memento
     *
     * @return
     */
    public Memento saveStateMemento() {
    
    
        return new Memento(state);
    }

    /**
     * 通过备忘录对象获取原有状态信息,恢复状态
     *
     * @param memento
     */
    public void getStateFromMemento(Memento memento) {
    
    
        state = memento.getState();
    }
}

【思い出】

package com.atguigu.memento.theory;

public class Memento {
    
    
    /**
     * 用来保存状态信息
     */
    private String state;

    /**
     * 构造器
     *
     * @param state
     */
    public Memento(String state) {
    
    
        super();
        this.state = state;
    }

    /**
     * 获取保存的状态信息
     * @return
     */
    public String getState() {
    
    
        return state;
    }

}

【管理人】

package com.atguigu.memento.theory;

import java.util.ArrayList;
import java.util.List;

/**
 * 统一管理备忘录对象
 */
public class Caretaker {
    
    

    /**
     * 在 List 集合中会有很多的备忘录对象
     * 如果想要保存多个Originator的多个状态,可以使用HashMap<Originator的ID,List<Memento>>
     */
    private List<Memento> mementoList = new ArrayList<Memento>();

    public void add(Memento memento) {
    
    
        mementoList.add(memento);
    }

    /**
     * 获取 Originator 的 第 index 个 备忘录对象(即所保存的状态)
     *
     * @param index
     * @return
     */
    public Memento get(int index) {
    
    
        return mementoList.get(index);
    }
}

【主なカテゴリー】

package com.atguigu.memento.theory;

public class Client {
    
    

   public static void main(String[] args) {
    
    

      Originator originator = new Originator();
      // 备忘录对象管理器
      Caretaker caretaker = new Caretaker();

      originator.setState(" 状态#1 攻击力 100 ");

      //保存了当前的状态
      caretaker.add(originator.saveStateMemento());

      originator.setState(" 状态#2 攻击力 80 ");

      caretaker.add(originator.saveStateMemento());

      originator.setState(" 状态#3 攻击力 50 ");
      caretaker.add(originator.saveStateMemento());

      System.out.println("当前的状态是 =" + originator.getState());

      //希望得到状态 1, 将 originator 恢复到状态1
      originator.getStateFromMemento(caretaker.get(0));
      System.out.println("恢复到状态1 , 当前的状态是 =" + originator.getState());

   }

}

【走る】

当前的状态是 = 状态#3 攻击力 50 
恢复到状态1 , 当前的状态是 = 状态#1 攻击力 100 

Process finished with exit code 0

ケース2

クラス図

ここに画像の説明を挿入します

成し遂げる

【原作者:GameRole】

package com.atguigu.memento.game;

public class GameRole {
    
    

    private int vit;
    private int def;

    /**
     * 创建Memento,即根据当前的状态得到Memento
     *
     * @return
     */
    public Memento createMemento() {
    
    
        return new Memento(vit, def);
    }

    /**
     * 从备忘录对象,恢复GameRole的状态
     *
     * @param memento
     */
    public void recoverGameRoleFromMemento(Memento memento) {
    
    
        this.vit = memento.getVit();
        this.def = memento.getDef();
    }

    /**
     * 显示当前游戏角色的状态
     */
    public void display() {
    
    
        System.out.println("游戏角色当前的攻击力:" + this.vit + " 防御力: " + this.def);
    }

    public int getVit() {
    
    
        return vit;
    }

    public void setVit(int vit) {
    
    
        this.vit = vit;
    }

    public int getDef() {
    
    
        return def;
    }

    public void setDef(int def) {
    
    
        this.def = def;
    }


}

【思い出】

package com.atguigu.memento.game;

public class Memento {
    
    

    /**
     * 攻击力
     */
    private int vit;
    /**
     * 防御力
     */
    private int def;

    public Memento(int vit, int def) {
    
    
        this.vit = vit;
        this.def = def;
    }

    public int getVit() {
    
    
        return vit;
    }

    public void setVit(int vit) {
    
    
        this.vit = vit;
    }

    public int getDef() {
    
    
        return def;
    }

    public void setDef(int def) {
    
    
        this.def = def;
    }

}

【管理人】

package com.atguigu.memento.game;

/**
 * 守护者对象, 保存游戏角色的状态
 */
public class Caretaker {
    
    

    /**
     * 因为大战Boss之前只有一个状态,所以这里保存一次状态,不需要使用集合
     */
    private Memento memento;
    //对GameRole保存多次状态
    //private ArrayList<Memento> mementos;
    //对多个GameRole保存多个状态
    //private HashMap<String, ArrayList<Memento>> rolesMementos;

    public Memento getMemento() {
    
    
        return memento;
    }

    public void setMemento(Memento memento) {
    
    
        this.memento = memento;
    }

}

【主なカテゴリー】

package com.atguigu.memento.game;

public class Client {
    
    

   public static void main(String[] args) {
    
    
      //创建游戏角色
      GameRole gameRole = new GameRole();
      gameRole.setVit(100);
      gameRole.setDef(100);

      System.out.println("和boss大战前的状态");
      gameRole.display();

      //把当前状态保存caretaker
      Caretaker caretaker = new Caretaker();
      caretaker.setMemento(gameRole.createMemento());

      System.out.println("和boss大战~~~");
      gameRole.setDef(30);
      gameRole.setVit(30);

      gameRole.display();

      System.out.println("大战后,使用备忘录对象恢复到站前");

      gameRole.recoverGameRoleFromMemento(caretaker.getMemento());
      System.out.println("恢复后的状态");
      gameRole.display();
   }

}

【走る】

和boss大战前的状态
游戏角色当前的攻击力:100 防御力: 100
和boss大战~~~
游戏角色当前的攻击力:30 防御力: 30
大战后,使用备忘录对象恢复到站前
恢复后的状态
游戏角色当前的攻击力:100 防御力: 100

Process finished with exit code 0

ケース 3

事例の説明

  • ゲームは自動化されている
  • ゲームの主人公はサイコロを振って次の状態を決定します
  • サイコロの目が1の場合、主人公の所持金が増加します
  • サイコロの目が2の場合、主人公の所持金が減ります
  • サイコロの目が6の場合、主人公は果物を獲得します
  • 主人公がお金を持っていないとゲームは終了します

クラス図

ここに画像の説明を挿入します

成し遂げる

【原作者:ゲーマー】

package com.atguigu.memento.Sample.game;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;

public class Gamer {
    
    
    /**
     * 所持金钱
     */
    private int money;
    /**
     * 获得的水果
     */
    private List fruits = new ArrayList();
    /**
     * 随机数生成器
     */
    private Random random = new Random();
    /**
     * 表示水果种类的数组
     */
    private static String[] fruitsName = {
    
    
            "苹果", "葡萄", "香蕉", "橘子",
    };

    /**
     * 构造函数
     *
     * @param money
     */
    public Gamer(int money) {
    
    
        this.money = money;
    }

    /**
     * 获取当前所持金钱
     *
     * @return
     */
    public int getMoney() {
    
    
        return money;
    }

    /**
     * 投掷骰子进行游戏
     */
    public void bet() {
    
    
        // 掷骰子,随机获取一个点数
        int dice = random.nextInt(6) + 1;

        if (dice == 1) {
    
    
            //  骰子结果为1…增加所持金钱
            money += 100;
            System.out.println("所持金钱增加了。");
        } else if (dice == 2) {
    
    
            // 骰子结果为2…所持金钱减半
            money /= 2;
            System.out.println("所持金钱减半了。");
        } else if (dice == 6) {
    
    
            // 骰子结果为6…获得水果
            String f = getFruit();
            System.out.println("获得了水果(" + f + ")。");
            fruits.add(f);
        } else {
    
    
            // 骰子结果为3、4、5则什么都不会发生
            System.out.println("什么都没有发生。");
        }
    }

    /**
     * 拍摄快照
     *
     * @return
     */
    public Memento createMemento() {
    
    
        Memento m = new Memento(money);
        Iterator it = fruits.iterator();
        while (it.hasNext()) {
    
    
            String f = (String) it.next();
            if (f.startsWith("好吃的")) {
    
    
                // 只保存好吃的水果
                m.addFruit(f);
            }
        }
        return m;
    }

    /**
     * 撤销到指定状态
     *
     * @param memento
     */
    public void restoreMemento(Memento memento) {
    
    
        this.money = memento.money;
        this.fruits = memento.getFruits();
    }

    /**
     * 用字符串输出主人公的状态
     *
     * @return
     */
    public String toString() {
    
    
        return "[money = " + money + ", fruits = " + fruits + "]";
    }

    /**
     * 随机获取一个水果
     *
     * @return
     */
    private String getFruit() {
    
    
        String prefix = "";
        if (random.nextBoolean()) {
    
    
            prefix = "好吃的";
        }
        return prefix + fruitsName[random.nextInt(fruitsName.length)];
    }
}

【思い出】

package com.atguigu.memento.Sample.game;

import java.util.ArrayList;
import java.util.List;

public class Memento {
    
    
    /**
     * 游戏主人公所持金钱
     */
    int money;
    /**
     * 游戏主人公当前获得的水果
     */
    ArrayList fruits;
    /**
     * 获取当前所持金钱(narrow interface)
     * narrow interface:Memento角色为外部的 Caretaker 角色提供了“窄接口(API)”。可以通过窄接口(API)获取的Memento角色的内部信息非常有限,因此可以有效地防止信息泄露
     * @return
     */
    public int getMoney() {
    
    
        return money;
    }

    /**
     * 构造函数(wide interface),只有相同包的Gamer类才使用该构造方法,因为方法修饰类型是default
     *
     * @param money
     */
    Memento(int money) {
    
    
        this.money = money;
        this.fruits = new ArrayList();
    }

    /**
     * 添加水果(wide interface)
     *
     * @param fruit
     */
    void addFruit(String fruit) {
    
    
        fruits.add(fruit);
    }

    /**
     * 获取当前所持所有水果(wide interface)
     * wide interface:“宽接口(API)”是指所有用于获取恢复对象状态信息的方法的集合。由于宽接口(API)会暴露所有Memento角色的内部信息,因此能够使用宽接口(API)的只有Originator 角色
     * @return
     */
    List getFruits() {
    
    
        return (List) fruits.clone();
    }
}

【管理人】

package com.atguigu.memento.Sample;


import com.atguigu.memento.Sample.game.Gamer;
import com.atguigu.memento.Sample.game.Memento;

public class Main {
    
    
    public static void main(String[] args) {
    
    
        // 最初的所持金钱数为100
        Gamer gamer = new Gamer(100);
        // 保存最初的状态
        Memento memento = gamer.createMemento();
        for (int i = 0; i < 10; i++) {
    
    
            // 显示掷骰子的次数
            System.out.println("==== " + i);
            // 显示主人公现在的状态
            System.out.println("当前状态:" + gamer);

            // 进行游戏
            gamer.bet();

            System.out.println("所持金钱为" + gamer.getMoney() + "元。");

            // 决定如何处理Memento
            if (gamer.getMoney() > memento.getMoney()) {
    
    
                System.out.println("    (所持金钱增加了许多,因此保存游戏当前的状态)");
                memento = gamer.createMemento();
            } else if (gamer.getMoney() < memento.getMoney() / 2) {
    
    
                System.out.println("    (所持金钱减少了许多,因此将游戏恢复至以前的状态)");
                gamer.restoreMemento(memento);
            }

            // 等待一段时间
            try {
    
    
                Thread.sleep(1000);
            } catch (InterruptedException e) {
    
    
            }
            System.out.println("");
        }
    }
}

【走る】

==== 0
当前状态:[money = 100, fruits = []]
什么都没有发生。
所持金钱为100元。

==== 1
当前状态:[money = 100, fruits = []]
所持金钱增加了。
所持金钱为200元。
    (所持金钱增加了许多,因此保存游戏当前的状态)

==== 2
当前状态:[money = 200, fruits = []]
什么都没有发生。
所持金钱为200元。

==== 3
当前状态:[money = 200, fruits = []]
什么都没有发生。
所持金钱为200元。

==== 4
当前状态:[money = 200, fruits = []]
什么都没有发生。
所持金钱为200元。

==== 5
当前状态:[money = 200, fruits = []]
什么都没有发生。
所持金钱为200元。

==== 6
当前状态:[money = 200, fruits = []]
什么都没有发生。
所持金钱为200元。

==== 7
当前状态:[money = 200, fruits = []]
什么都没有发生。
所持金钱为200元。

==== 8
当前状态:[money = 200, fruits = []]
所持金钱减半了。
所持金钱为100元。

==== 9
当前状态:[money = 100, fruits = []]
所持金钱增加了。
所持金钱为200元。


Process finished with exit code 0

拡大する

Serialization 関数を使用して、Memento クラスのインスタンスをファイルとして保存します。以下の機能を実装するためにサンプルプログラムを修正してください。

  • アプリケーション起動時、game.datファイルが存在しない場合は所持金を100としてゲームが開始され、game.datが既に存在する場合はファイルに保存された状態でゲームが開始されます。ファイル。
  • 保有金額が大幅に増加した場合は、Memento クラスのインスタンスをgame.datファイルに保存します
  1. Serializable インターフェイスから Memento を継承してシリアル化操作を実装します。それ以外に変更は必要ありません。
public class Memento implements Serializable {
    
    }
  1. インスタンスをファイルに保存する機能を拡張したい場合は、Caretakerクラスを変更するだけで済みます。
package com.atguigu.memento.A4;


import com.atguigu.memento.A4.game.Gamer;
import com.atguigu.memento.A4.game.Memento;

import java.io.*;

public class Main {
    
    
    /**
     * 数据保存的文件名
     */
    public static final String SAVE_FILE_NAME = "game.dat";

    public static void main(String[] args) {
    
    
        // 最初的所持金钱数为100
        Gamer gamer = new Gamer(100);
        // 从文件中读取起始状态
        Memento memento = loadMemento();
        if (memento != null) {
    
    
            System.out.println("读取上次保存存档开始游戏。");
            gamer.restoreMemento(memento);
        } else {
    
    
            System.out.println("新游戏。");
            memento = gamer.createMemento();
        }
        for (int i = 0; i < 100; i++) {
    
    
            // 显示次数
            System.out.println("==== " + i);
            // 显示当前主人公的状态
            System.out.println("当前状态:" + gamer);
            // 进行游戏
            gamer.bet();   

            System.out.println("所持金钱为" + gamer.getMoney() + "元。");

            // 决定如何处理Memento
            if (gamer.getMoney() > memento.getMoney()) {
    
    
                System.out.println("    (所持金钱增加了许多,因此保存游戏当前的状态)");
                memento = gamer.createMemento();
                // 将实例保存至文件中
                saveMemento(memento);
            } else if (gamer.getMoney() < memento.getMoney() / 2) {
    
    
                System.out.println("    (所持金钱减少了许多,因此将游戏恢复至以前的状态)");
                gamer.restoreMemento(memento);
            }

            // 等待一段时间
            try {
    
    
                Thread.sleep(1000);
            } catch (InterruptedException e) {
    
    
            }
            System.out.println("");
        }
    }

    /**
     * 保存实例到文件中
     * @param memento
     */
    public static void saveMemento(Memento memento) {
    
       
        try {
    
    
            ObjectOutput out = new ObjectOutputStream(new FileOutputStream(SAVE_FILE_NAME));
            out.writeObject(memento);
            out.close();
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }
    }

    /**
     * 从文件中读取实例
     * @return
     */
    public static Memento loadMemento() {
    
                   
        Memento memento = null;
        try {
    
    
            ObjectInput in = new ObjectInputStream(new FileInputStream(SAVE_FILE_NAME));
            memento = (Memento)in.readObject();
            in.close();
        } catch (FileNotFoundException e) {
    
    
            System.out.println(e.toString());
        } catch (IOException e) {
    
    
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
    
    
            e.printStackTrace();
        }
        return memento;
    }
}

要約する

【アドバンテージ】

  • 状態を復元するメカニズムをユーザーに提供し、ユーザーがより簡単に過去の状態に戻れるようにします。
  • 情報のカプセル化が実装されているため、ユーザーは状態の保存の詳細を気にする必要がありません。

【欠点】

  • クラスのメンバ変数が多すぎると必然的に比較的大きなリソースを占有し、保存するたびに一定量のメモリを消費するため注意が必要です。メモリを節約するために、メモ モードをプロトタイプ モードと組み合わせて使用​​できます。

【適用可能なアプリケーションシナリオ】

  • ゲームプレイ中にファイルを保存する
  • Windowsでctrl+z
  • IEに戻る
  • データベーストランザクション管理

記事の説明

  • この記事は、シャン シリコン バレーを勉強するための私の学習ノートです。記事のほとんどの内容は、シャン シリコン バレーのビデオ (クリックしてシャン シリコン バレー関連コースを学習する) から引用していますが、一部の内容は私自身の考えによるものです。他の人がより便利に勉強できるように記事を作成します。自分のメモを整理したり、記事を通じて関連する知識を直接学習したりできます。侵害がある場合は、削除するためにご連絡ください。最後に、Shang Silicon Valley に感謝の意を表したいと思います。質の高いコース。
  • また、『グラフィックデザインパターン』(グラフィックデザインパターン/結城宏著、楊文宣訳、北京:人民郵政通信社、2017.1)という書籍も同時に読み、両者の内容を統合して知識を作りました。ポイントをより包括的に。

おすすめ

転載: blog.csdn.net/laodanqiu/article/details/132189714