30 Dec 2014, 07:23

Java で Memento パターンを実装をしてみた

はじめに

Java で Memento パターンを実装をしてみました.

Memento パターンとは

オブジェクトの状態を保存しておき, 元に戻せるようにしておく. オブジェクトを以前の状態に (ロールバックにより) 戻す能力を提供する.

パターン適用前

以前, State Pattern を実装したコードを改造. ステートを保存できるようにします.

public class StatePatternMemento {

    public static void main (String[] args) {
        Context context = new Context (new Morning ());

        while (!context.isDone ()) {
            context.greeting ();
        }
    }
}

class Context {
    private State state;

    public Context (State state) {
        this.state = state;
    }

    public void greeting () {
        state = state.greeting ();
    }

    public boolean isDone () {
        return state == null;
    }
}

interface State {
    public State greeting ();
}

class Morning implements State {

    public Morning () {
        System.out.println (" === It's 09:00 ===");
    }

    public State greeting () {
        System.out.println ("Good morning!! ('▽`)");
        return new Afternoon ();
    }
}

class Afternoon implements State {

    public Afternoon () {
        System.out.println (" === It's 15:00 ===");
    }

    public State greeting () {
        System.out.println ("Good afternoon!! ('▽`)");
        return new Evening ();
    }
}

class Evening implements State {

    public Evening () {
        System.out.println (" === It's 20:00 ===");
    }

    public State greeting () {
        System.out.println ("Good evening!! ('▽`)");
        return null;
    }
}

パターン適用後

状態保存する機能を順番に追加していく.

メメントパターンは, 保存/ 復元される側とする側の役割を独立させるのが普通.

  • originator (代表者) 内部属性を保存されるもの.
  • caretaker (世話役) originator 内部属性 (状態) を保存するコンテナ.
  • Memento (記念品) originator 内部属性 (状態) を記憶するデータ構造.

その 1 初期状態を復元

まずは, 初期状態を追加. originator である Context を拡張する.

class Context {
    private State initialState; 

    public Context (State state) {
        this.state = state;
        this.initialState = state;
    }

    public void restoreState () {
        System.out.println ();  
        System.out.println (" === Restore State ===");
        System.out.println ();      
        state = initialState;
    }
}

初期状態を保存する属性と 外部から restore するためのインタフェースを用意.

public class StatePatternMemento1 {

    public static void main (String[] args) {
        Context context = new Context (new Morning ());

        while (!context.isDone ()) {
            context.greeting ();
        }

        context.restoreState ();

        while (!context.isDone ()) {
            context.greeting ();
        }
}
}

class Context {
    private State state;
    private State initialState; 

    public Context (State state) {
        this.state = state;
        this.initialState = state;
    }

    public void greeting () {
        state = state.greeting ();
    }

    public boolean isDone () {
        return state == null;
    }

    public void restoreState () {
        System.out.println ();  
        System.out.println (" === Restore State ===");
        System.out.println ();      
        state = initialState;
    }
}

interface State {
    public State greeting ();
}

class Morning implements State {

    public Morning () {
        System.out.println (" === It's 09:00 ===");
    }

    public State greeting () {
        System.out.println ("Good morning!! ('▽`)");
        return new Afternoon ();
    }
}

class Afternoon implements State {

    public Afternoon () {
        System.out.println (" === It's 15:00 ===");
    }

    public State greeting () {
        System.out.println ("Good afternoon!! ('▽`)");
        return new Evening ();
    }
}

class Evening implements State {

    public Evening () {
        System.out.println (" === It's 20:00 ===");
    }

    public State greeting () {
        System.out.println ("Good evening!! ('▽`)");
        return null;
    }
}

実行結果

 === It's 09:00 ===
Good morning!! ('▽`)
 === It's 15:00 ===
Good afternoon!! ('▽`)
 === It's 20:00 ===
Good evening!! ('▽`)

 === Restore State ===

Good morning!! ('▽`)
 === It's 15:00 ===
Good afternoon!! ('▽`)
 === It's 20:00 ===
Good evening!! ('▽`)

その 2 状態 + アルファを復元

状態 + アルファを保存するためのデータ構造を用意する. これこそが memento!!

ここでは, アルファは count とする.

private int count;

count と state を保持するデータ構造 memento クラスを作成. memento のコンストラクタで状態を保存する. memento の getter で属性を取得してリストアする.

private static class Memento {
    private State state;
    private int count;

    public Memento (State stateToSave, int countToSave) {
        state = stateToSave;
        count = countToSave;            
    }
    public State getSavedState () { return state; }
    public int getSavedCount () { return count; }       
}

全コード

public class StatePatternMemento2 {

    public static void main (String[] args) {
        Context context = new Context (new Morning ());

        while (!context.isDone ()) {
            context.greeting ();
        }

        context.restoreState ();

        while (!context.isDone ()) {
            context.greeting ();
        }
    }
}

class Context {
    private State state;
    private int count;
    private Memento m;

    public Context (State state) {
        this.state = state;
    }

    public void greeting () {
        state = state.greeting ();
        count++;

        if (count == 1) {
            m = new Memento (state, count);
        }
    }

    public boolean isDone () {
        return state == null;
    }

    public void restoreState () {
        System.out.println ();  
        System.out.println (" === Restore State ===");
        System.out.println ();

        state = m.getSavedState ();
        count = m.getSavedCount ();     
    }

    private static class Memento {
        private State state;
        private int count;

        public Memento (State stateToSave, int countToSave) {
            state = stateToSave;
            count = countToSave;            
        }
        public State getSavedState () { return state; }
        public int getSavedCount () { return count; }       
    }
}

interface State {
    public State greeting ();
}

class Morning implements State {

    public Morning () {
        System.out.println (" === It's 09:00 ===");
    }

    public State greeting () {
        System.out.println ("Good morning!! ('▽`)");
        return new Afternoon ();
    }
}

class Afternoon implements State {

    public Afternoon () {
        System.out.println (" === It's 15:00 ===");
    }

    public State greeting () {
        System.out.println ("Good afternoon!! ('▽`)");
        return new Evening ();
    }
}

class Evening implements State {

    public Evening () {
        System.out.println (" === It's 20:00 ===");
    }

    public State greeting () {
        System.out.println ("Good evening!! ('▽`)");
        return null;
    }
}

実行結果

 === It's 09:00 ===
Good morning!! ('▽`)
 === It's 15:00 ===
Good afternoon!! ('▽`)
 === It's 20:00 ===
Good evening!! ('▽`)

 === Restore State ===

Good afternoon!! ('▽`)
 === It's 20:00 ===
Good evening!! ('▽`)

その 3 スナップショットを復元

最後にスナップショット機能を追加. 保存するためには, 第三者を登場させて外部に状態を保持する.

class SnapShot {
    private List<Object> states = new ArrayList<Object>();

    public void addMemento (Object m) { states.add (m); }
    public Object getMemento (int index) { return states.get (index); }
}

ここでのポイントは, Context の情報のみをもつ Memento データを 保存しておくこと. 保存する情報はメモリ上で小さくすることが目的.

全コード

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

public class StatePatternMemento3 {

    public static void main (String[] args) {
        Context context = new Context (new Morning ());
        SnapShot snaps = new SnapShot ();
        snaps.addMemento (context.saveToMemento ());

        while (!context.isDone ()) {
            context.greeting ();
        }

        context.restoreFromMemento (snaps.getMemento (0));

        while (!context.isDone ()) {
            context.greeting ();
        }
    }
}

class Context {
    private State state;
    private int count;

    public Context (State state) {
        this.state = state;
    }

    public void greeting () {
        state = state.greeting ();
        count++;
    }

    public boolean isDone () {
        return state == null;
    }

    public Object saveToMemento () { 
        System.out.println (" == Save to Memento == ");
        return new Memento (state, count); 
    }

    public void restoreFromMemento (Object m) {
        if (m instanceof Memento) {
            Memento memento = (Memento) m;
            state = memento.getSavedState ();
            count = memento.getSavedCount ();

            System.out.println ();  
            System.out.println (" === Restore State ===");
            System.out.println ();
        }
    }   

    private static class Memento {
        private State state;
        private int count;

        public Memento (State stateToSave, int countToSave) {
            state = stateToSave;
            count = countToSave;            
        }
        public State getSavedState () { return state; }
        public int getSavedCount () { return count; }       
    }
}

class SnapShot {
    private List<Object> states = new ArrayList<Object>();

    public void addMemento (Object m) { states.add (m); }
    public Object getMemento (int index) { return states.get (index); }
}

interface State {
    public State greeting ();
}

class Morning implements State {

    public Morning () {
        System.out.println (" === It's 09:00 ===");
    }

    public State greeting () {
        System.out.println ("Good morning!! ('▽`)");
        return new Afternoon ();
    }
}

class Afternoon implements State {

    public Afternoon () {
        System.out.println (" === It's 15:00 ===");
    }

    public State greeting () {
        System.out.println ("Good afternoon!! ('▽`)");
        return new Evening ();
    }
}

class Evening implements State {

    public Evening () {
        System.out.println (" === It's 20:00 ===");
    }

    public State greeting () {
        System.out.println ("Good evening!! ('▽`)");
        return null;
    }
}


実行結果

 === It's 09:00 ===
Good morning!! ('▽`)
 === It's 15:00 ===
Good afternoon!! ('▽`)
 === It's 20:00 ===
Good evening!! ('▽`)

 === Restore State ===

Good morning!! ('▽`)
 === It's 15:00 ===
Good afternoon!! ('▽`)
 === It's 20:00 ===
Good evening!! ('▽`)