備忘錄模式


1.模式簡介

      備忘錄模式能夠在不破壞封裝性的前提下,實現對象狀態的保存和恢復工作,又叫快照模式或Token模式。保存對象的狀態是為了以后在需要的時候快速恢復到保存時的狀態,因此常用在備份、撤銷操作上,例如編輯器里的撤銷、游戲里的存檔和悔棋等功能。

      備忘錄模式有三個組成部分:

      Originagor(發起人):即需要備份的對象,可以創建備忘錄,以及根據備忘錄來恢復狀態,可以看到備忘錄提供的寬接口。

      Memento(備忘錄):存儲Originator的部分或所有狀態,對外提供寬窄接口。

      CareTaker(管理人):負責保存Memento對象,只能看到備忘錄提供的窄接口。

      上面提到了寬接口和窄接口,有必要先解釋一下,寬窄接口實際上代表了外界對備忘錄的訪問權限問題:

      寬接口:能夠看到備忘錄保存的所有數據,一般只對發起人可見,對其他角色不可見。

      窄接口:只能看到備忘錄保存的部分數據(甚至可以實現不對外暴露任何數據),通常出於封裝和安全性考慮,對發起人之外的其他角色只提供窄接口。

2. 示例

      下面以一個簡單的例子演示備忘錄模式的用法,示例模仿棋類游戲中的悔棋,為簡單起見,只記錄棋子的坐標。

       先定義棋子類Chessman,包含棋子的x坐標和y坐標:

public class Chessman {
  private int positionx;
  private int positiony;

  public void setPosition(int positionx, int positiony) {
    this.positionx = positionx;
    this.positiony = positiony;
  }

  @Override
  public String toString() {
    return "當前位置{" +
      "positionx=" + positionx +
      ", positiony=" + positiony +
      '}';
  }

  public Chessman(int x, int y){
    this.positionx = x;
    this.positiony = y;
  }

  public Memento createMemento(){
    return new Memento(positionx, positiony);
  }

  public void restore(Memento memento){
    this.positionx = memento.getPositionx();
    this.positiony = memento.getPositiony();
  }
}

      接着定義備忘錄類Memento,用來存儲棋子的坐標信息:

public class Memento {
  private int positionx;
  private int positiony;

  public int getPositionx() {
    return positionx;
  }

  public int getPositiony() {
    return positiony;
  }

  public Memento(int x, int y){
    this.positionx = x;
    this.positiony = y;
  }
}

      定義管理者類CareTaker,外界通過該類獲取備份信息:

public class CareTaker {
  private Memento memento;

  public Memento getMemento() {
    return memento;
  }

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

      接下來用客戶端來測試這個簡單的備忘錄:

public class MementoTest {
  public static void main(String[] args) {
    Chessman chessman = new Chessman(0,0);
    chessman.setPosition(3,4);
    System.out.println(chessman);
    CareTaker careTaker = new CareTaker();
    System.out.println("備份棋子位置。。。");
    careTaker.setMemento(chessman.createMemento());
    chessman.setPosition(7,5);
    System.out.println(chessman);
    System.out.println("悔棋。。。");
    chessman.restore(careTaker.getMemento());
    System.out.println(chessman);
  }
}

      輸出為:

當前位置{positionx=3, positiony=4}
備份棋子位置。。。
當前位置{positionx=7, positiony=5}
悔棋。。。
當前位置{positionx=3, positiony=4}

      該示例所對應的類圖結構如下:

      上面這個示例只是單備份,也就是說只能備份一個狀態,將CareTaker中的Memento修改成集合的形式可以實現多備份。

3. “黑箱”備忘錄模式

      其實上面的實現方式有一個很大的問題,就是Memento對所有的外界對象都是公開的,任何對象都可以訪問和修改Memento的字段,這種模式稱為“白箱”模式。由於沒有相應的權限控制,這種方式無法保證備忘錄的安全性,不具備太大的實用價值。一種解決方案是將Memento設置為Originator的內部類,並通過權限控制符來限制外界對他的訪問。

      修改后的Chessman類,擁有私有的Memebto類:

public class ChessmanNew {
  private int positionx;
  private int positiony;

  public void setPosition(int positionx, int positiony) {
    this.positionx = positionx;
    this.positiony = positiony;
  }

  @Override
  public String toString() {
    return "當前位置{" +
      "positionx=" + positionx +
      ", positiony=" + positiony +
      '}';
  }

  public ChessmanNew(int x, int y){
    this.positionx = x;
    this.positiony = y;
  }

  public MementoFace createMemento(){
    return new Memento(positionx, positiony);
  }

  public void restore(MementoFace memento){
    this.positionx = memento.getx();
    this.positiony = ((Memento)memento).getPositiony();
  }

  private class Memento implements MementoFace{
    private int positionx;
    private int positiony;

    private Memento(int x, int y){
      this.positionx = x;
      this.positiony = y;
    }

    private int getPositiony(){
      return this.positiony;
    }

    @Override
    public int getx() {
      return this.positionx;
    }
  }
}

      Memento對外以接口MementoFace的形式提供有限的服務(即只允許外界訪問x坐標,而對外隱藏y坐標),MementoFace的定義如下:

public interface MementoFace {
  //窄接口
  int getx();
}

      CareTaker類也需要做對應的修改:

public class CareTakerNew {
  private MementoFace mementoFace;

  public MementoFace getMementoFace() {
    return mementoFace;
  }

  public void setMementoFace(MementoFace mementoFace) {
    this.mementoFace = mementoFace;
  }
}

      客戶端測試如下:

public class MementoTest {
  public static void main(String[] args) {
    newtest();
  }

  public static void newtest(){
    ChessmanNew chessman = new ChessmanNew(0,0);
    chessman.setPosition(3,4);
    System.out.println(chessman);
    System.out.println("備份棋子位置。。。");
    CareTakerNew careTaker = new CareTakerNew();
    careTaker.setMementoFace(chessman.createMemento());
    System.out.println(chessman);
    System.out.println("悔棋。。。");
    chessman.restore(careTaker.getMementoFace());
    System.out.println(chessman);
  }
}

      輸出與修改前的代碼一致,這里略去。這種方式修改后,外界能夠接觸到的只有MementoFace接口,只能訪問x坐標,其他什么也做不了,從而保證了封裝性。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM