淺談Memento備忘錄模式

1、前言java

  備忘錄模式用於保存和恢復對象的狀態,相信你們看過我前面的拙做就會想到原型模式也能保存一個對象在某一個時刻的狀態,那麼二者有何不一樣的呢?原型模式保存的是當前對象的全部狀態信息,恢復的時候會生成與保存的對象徹底相同的另一個實例;而備忘錄模式保存的是咱們關心的在恢復時須要的對象的部分狀態信息,至關於快照。備忘錄模式你們確定都見過,好比在玩遊戲的時候有一個保存當前闖關的狀態的功能,會對當前用戶所處的狀態進行保存,當用戶闖關失敗或者須要從快照的地方開始的時候,就能讀取當時保存的狀態完整地恢復到當時的環境,這一點和VMware上面的快照功能很相似。編程

2、代碼設計模式

Memento類:dom

package zyr.dp.memento;

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

public class Memento {
    
    private int menoy;
    private ArrayList fruits;

    //窄接口,訪問部分信息
    public int getMenoy(){
        return menoy;
    }
    
    //寬接口,本包以內皆可訪問
    Memento(int menoy){
        this.menoy=menoy;
        fruits=new ArrayList();//每次調用的時候從新生成,很重要
    }
    //寬接口,本包以內皆可訪問
    List getFruits(){
        return (List) fruits.clone();
    }
    //寬接口,本包以內皆可訪問
    void  addFruits(String fruit){
        fruits.add(fruit);
    }

}

Gamer類:組件化

package zyr.dp.memento;

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

public class Gamer {

    private static  String[] FruitsSame={"香蕉","蘋果","橘子","柚子"};
    
    private int menoy;
    private List fruits=new ArrayList();
    private  Random random=new Random();
    
    public int getMenoy(){
        return menoy;
    }
    
    public Gamer(int menoy){
        this.menoy=menoy;
    }
    
    public void bet(){
        int next=random.nextInt(6)+1;
        if(next==1){
            menoy+=100;
            System.out.println("金錢增長了100,當前金錢爲:"+menoy);
        }else if(next==2){
            menoy/=2;
            System.out.println("金錢減小了一半,當前金錢爲:"+menoy);
        }else if(next==6){
            String f=getFruit();
            fruits.add(f);
            System.out.println("得到了水果:"+f+",當前金錢爲:"+menoy);
        }else {
            System.out.println("金錢沒有發生改變,當前金錢爲:"+menoy);
        }
    }
    
    private String getFruit() {

        String prefix="";
        if(random.nextBoolean()){
            prefix="好吃的";
        }
        return prefix+FruitsSame[random.nextInt(FruitsSame.length)];
        
    }
    
    public Memento createMemento(){
        Memento m=new Memento(menoy);
        Iterator it=fruits.iterator();
        while(it.hasNext()){
            String fruit=(String)it.next();
            if(fruit.startsWith("好吃的")){
                m.addFruits(fruit);
            }
        }
        return m;
    }
    
    public  void restoreMemento(Memento memento){
        this.menoy=memento.getMenoy();
        this.fruits=memento.getFruits();
    }
    
    public String toString(){
        return "Menoy:"+menoy+" ,Fruits:"+fruits;
    }
    
}

Main類:ui

package zyr.dp.test;

import zyr.dp.memento.Gamer;
import zyr.dp.memento.Memento;

public class Main {

    public static void main(String[] args) {
        Gamer gamer=new Gamer(100);
        Memento memento=gamer.createMemento();
        for(int i=0;i<100;i++){
            System.out.println("當前狀態:"+i);
            System.out.println("當前金額:"+gamer.getMenoy());
            gamer.bet();
            if(gamer.getMenoy()<memento.getMenoy()/2){
                System.out.println("金錢過少,恢復到之前的狀態:");
                gamer.restoreMemento(memento);
                System.out.println("此時狀態爲:"+gamer);
            }else if(gamer.getMenoy()>memento.getMenoy()){
                System.out.println("金錢增多,保存當前狀態:");
                memento=gamer.createMemento();
                System.out.println("此時狀態爲:"+gamer);
            }
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }

}

運行結果:this

本程序的功能是根據循環次數隨機的生成1~6這6個數字,若是數字是1,則金錢加一百,若是是二,則金錢減半,若是是6,則隨機生成水果,水果分爲好吃的和很差吃的,在保存的時候只保存好吃的水果,恢復的時候就只有好吃的水果了。當金錢少於當前備忘錄中金錢的一半的時候就要恢復到備忘錄的狀態;當金錢大於備忘錄的狀態的時候就要備份當前的狀態,備份的時候只備份好的水果以及當前金額,這就是遊戲的功能,能夠看到運行的結果的正確性。spa

  這裏有幾點要注意:、.net

一、窄接口和寬接口設計

   在代碼中我已經標註出了窄接口和寬接口,如何定義這兩種接口還要看這兩種接口前面的修飾符,若是是默認的(只有本包的類可使用),而且這些接口結合到一塊兒能夠徹底的將本類的信息顯示出來,那麼就是寬接口;只能在本包之中使用,若是修飾符是public的接口,而且只能表示本類一部分信息,由於是public能夠在其餘包中使用的,就是窄接口,只能查看部分信息,所以是窄的。以下圖所示,對於Main類所在的包,只能使用其餘兩個類中聲明爲public的字段和方法,所以在Main中只能使用窄接口來完成必定信息的讀取getMenoy()。這只是一個概念,強調的是類、字段、方法的可見性。

二、可見性

   同時咱們也知道,public修飾的字段和方法在任何包中均可以使用,private修飾的字段和方法只能在本類之中使用,protected修飾的方法能夠在本包之中以及該類的子類(能夠在其餘包)中使用,默認的沒有任何修飾的能夠在本包之中使用。這就是四種修飾關鍵字的可見性。在編程的時候咱們必定要考慮這些問題,否則就會致使咱們不想看到的字段、方法、類被誤用的結果。

三、將備分內容存盤而且讀取

Memento類:

package zyr.dp.serializable;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

public class Memento implements Serializable {
    
    private static final long serialVersionUID = 8497203738547925495L;
    
    private int menoy;
    private ArrayList fruits;

    //窄接口,訪問部分信息
    public int getMenoy(){
        return menoy;
    }
    
    //寬接口,本包以內皆可訪問
    Memento(int menoy){
        this.menoy=menoy;
        fruits=new ArrayList();//每次調用的時候從新生成,很重要
    }
    //寬接口,本包以內皆可訪問
    List getFruits(){
        return (List) fruits.clone();
    }
    //寬接口,本包以內皆可訪問
    void  addFruits(String fruit){
        fruits.add(fruit);
    }

}

Gamer類:

package zyr.dp.serializable;

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

public class Gamer {

    private static  String[] FruitsSame={"香蕉","蘋果","橘子","柚子"};
    
    private int menoy;
    private List fruits=new ArrayList();
    private  Random random=new Random();
    
    public int getMenoy(){
        return menoy;
    }
    
    public Gamer(int menoy){
        this.menoy=menoy;
    }
    
    public void bet(){
        int next=random.nextInt(6)+1;
        if(next==1){
            menoy+=100;
            System.out.println("金錢增長了100,當前金錢爲:"+menoy);
        }else if(next==2){
            menoy/=2;
            System.out.println("金錢減小了一半,當前金錢爲:"+menoy);
        }else if(next==6){
            String f=getFruit();
            fruits.add(f);
            System.out.println("得到了水果:"+f+",當前金錢爲:"+menoy);
        }else {
            System.out.println("金錢沒有發生改變,當前金錢爲:"+menoy);
        }
    }
    
    private String getFruit() {

        String prefix="";
        if(random.nextBoolean()){
            prefix="好吃的";
        }
        return prefix+FruitsSame[random.nextInt(FruitsSame.length)];
        
    }
    
    public Memento createMemento(){
        Memento m=new Memento(menoy);
        Iterator it=fruits.iterator();
        while(it.hasNext()){
            String fruit=(String)it.next();
            if(fruit.startsWith("好吃的")){
                m.addFruits(fruit);
            }
        }
        return m;
    }
    
    public  void restoreMemento(Memento memento){
        this.menoy=memento.getMenoy();
        this.fruits=memento.getFruits();
    }
    
    public String toString(){
        return "Menoy:"+menoy+" ,Fruits:"+fruits;
    }
    
}

SerializableMain類:

package zyr.dp.test;

import java.io.*;

import zyr.dp.serializable.Gamer;
import zyr.dp.serializable.Memento;


public class SerializableMain {

    private static String filename="game.dat";
    public static void main(String[] args) {
        Gamer gamer=new Gamer(100);
        Memento memento=loadMemento();
        
        if(memento==null){
             memento=gamer.createMemento();
        }else{
            System.out.println("從上次保存處開始...");
            gamer.restoreMemento(memento);
        }
        
        for(int i=0;i<100;i++){
            System.out.println("當前狀態:"+i);
            System.out.println("當前金額:"+gamer.getMenoy());
            gamer.bet();
            if(gamer.getMenoy()<memento.getMenoy()/2){
                System.out.println("金錢過少,恢復到之前的狀態:");
                gamer.restoreMemento(memento);
                System.out.println("此時狀態爲:"+gamer);
            }else if(gamer.getMenoy()>memento.getMenoy()){
                System.out.println("金錢增多,保存當前狀態:");
                memento=gamer.createMemento();
                saveMemento(memento);
                System.out.println("此時狀態爲:"+gamer);
            }
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
    private static void saveMemento(Memento memento) {
        try {
            ObjectOutput o=new ObjectOutputStream(new FileOutputStream(filename));
            o.writeObject(memento);
            o.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    private static Memento loadMemento() {
        Memento memento=null;
        ObjectInput in;
        try {
            in = new ObjectInputStream(new FileInputStream(filename));
            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;
    }

}

運行結果:

初次運行:

第二次運行:

能夠看到保存和讀取成功。

3、總結

備忘錄模式也是一種很是常見的模式,用來保存對象的部分用於恢復的信息,和原型模式有着本質的區別,普遍運用在快照功能中,另外咱們知道了寬接口和窄接口,這裏的接口指的就是方法,沒其餘意思,以及類的可見性。一樣的使用備忘錄模式可使得程序能夠組件化,好比打算屢次撤銷當前的狀態以及不只能夠撤銷並且能夠將當前的狀態保存到文件中,咱們不須要修改Gamer的代碼就能作到,職責明確是一種很是重要的軟件工程思想。

 

淺談設計模式<最通俗易懂的講解>

相關文章
相關標籤/搜索