はじめに
「C言語でトライ! デザインパターン」
今回はMementパターン。一言でいうとDBのロールバックですかね。よくよく考えると使い道のあるパターンだなと思います。
デザインパターン一覧
作成したライブラリパッケージの説明
公開コードはこちら
Mementパターン
wikipediaの説明は以下。
memento パターン(英: Memento pattern、日本: メメント パターン)はソフトウェアのデザインパターンの一つで、オブジェクトを以前の状態に(ロールバックにより)戻す能力を提供する。
シンプルですね。テックスコアさんの説明や以下のクラス図を見ても、状態を保持するMememtクラスを使ってOriginatorが元の状態を戻せる機構を設けています。
使い道ってなんだろうな~と過去を振り返ると結構ありました。役に立ちそうなユースケースが結構ありました。
使いどころ
データのロールバック
DBを使っているところだとロールバックの機能を使えばOKですが、簡単なデータストレージ的な機構を持っているシステムだと、DBを使うまでもないのでエラー時に元に戻したい場合にエラー時にガリガリコードで実装しているのを見たりします。
DB使うまでもないならしょうがないよな~とか思っていましたが、「前の状態を記憶しておく」ということ、よくやる気がします。
状態遷移
特に画面の開発で「戻る」ボタンを押したら元の画面状態に戻す。みたいなロールバック処理って色々と必要です。
前のこのViewとこのViewを有効化してこのボタンが使えるようにして~とか。
前に趣味でAndroidアプリ開発をした時に、こういう時のスマートなやり方って何があるんだろう?とずっと悩みながら開発していました。当時は
- 画面状態管理遷移専用のクラスを設けて、その中で状態を持ち画面の移行時に「Nextは何状態」「backは何状態」いうように遷移を返してあげる
みたいなことをしてました。前の状態も意識しつつView配置を変える。こんな感じに
素人なりに頑張った感はあるけど、画面変更に対する変更量が多くて死ぬだろうな~これは
Mementを使わない場合
//各ボタンのイベント管理クラス
public class ActivityListenerPresenter {
public static ActivityListenerPresenterImple mInstance;
public static ActivityListenerPresenterImple getInstance() {
if(mInstance == null) mInstance = new ActivityListenerPresenterImple();
return mInstance;
}
//各ボタンイベントの画面遷移を実施
...
//バック押された
public static boolean goBack() {
return getInstance().goBack();
}
}
public class ActivityListenerPresenterImple {
...
public boolean goBack() {
//状態取得
ActivityListenerState prevState = mActivityStateAction.getPrev();
Log.v(TAG_LOG, "Prev state=" + prevState);
goNext(prevState);
return true;
}
private void goNext(ActivityListenerState state) {
//状態取得
mActivityStateAction = getActivityStateAction(state);
//状態に合わせた画面を表示
mActivityStateAction.showCurrent();
}
private ActivityStateAction getActivityStateAction(ActivityListenerState state) {
Log.v(TAG_LOG, "getActivityStateAction=" + state);
ActivityStateAction action=null;
switch(state) {
case MYVIDEO_LIST:
action = new ActivityStateActionMYVIDEO_LIST();
break;
case MYVIDEO_RUN:
action = new ActivityStateActionMYVIDEO_RUN();
break;
...
}
return action;
}
//各状態でprevが何になるかを頑張って実装
private interface ActivityStateAction{
//実際のView切り替え
public void showCurrent();
//prevに頑張って戻す
public ActivityListenerState getPrev();
}
例えばmementの考え方を使えば、getPrev
が無くても状態遷移時に前の状態を記憶させて元に戻すみたいなことが出来たのかもしれませんね。
こう単純にはいかないでしょうが、こっちの方がコード量や可読性は良さそう
Mementを使う場合の予想
public class ActivityListenerPresenterImple {
...
public boolean goBack() {
//状態取得
mActivityStateAction = setMementPrevActivityStateAction()
//状態に合わせた画面を表示
mActivityStateAction.showCurrent();
return true;
}
private void goNext(ActivityListenerState state) {
//前の状態を保持
mementPrevActivityStateAction(mActivityStateAction);
//状態取得
mActivityStateAction = getActivityStateAction(state);
//状態に合わせた画面を表示
mActivityStateAction.showCurrent();
}
///getActivityStateActionは変わらず
//各状態のインターフェースはshowCurrentだけでよくなるのでは?
private interface ActivityStateAction{
//実際のView切り替え
public void showCurrent();
//public ActivityListenerState getPrev();
}
ライブラリ
色々思い起こすと欲しい場面がちらほら見えるmement, ユースケースがあるならとライブラリ化してみました。
やってる中身はprototypeの時とほぼ同じです。
概要
- ライブラリで思い出(instanceの情報)を記録する。
- 記憶した思い出を掘り起こせるようにする。
基本的にはエラー時のロールバックを想定しています。
クラス設計
今回は毎回使い捨ての想定なので、ちょっとしたクラス設計もありません。なので省略。
いい点
- どんな状態に戻せばいいか覚えていなくていいのが楽
悪い点
- 結局思い出の掘り起こし処理は実装ユーザー任せ
動作環境
Ubuntu 18.04 Desktop, CentOS 7.5.1804で確認。Linuxなら動くと思います。
詳細
API定義
/*MementRegister定義*/
struct mement_register_t;
typedef struct mement_register_t *MementRegister;
/*ユーザーが実装する関数定義。デフォルトはmemcpyとfreeは何もしない(本体インスタンスはMementRegisterと一緒に破棄*/
struct mement_method_t {
/*mementのinstanceはライブラリ内で持つので、そのコンストラクタ*/
void (*constructor) (void *instance, void * base, size_t base_length);
/*mementのcopy*/
void (*copy) (void *broken_instance, void * base, size_t base_length);
/*mementの解放。base実体はMementRegisterと一緒に取得するのでfreeしない*/
void (*free) (void * base, size_t base_length);
};
typedef struct mement_method_t mement_method_t;
/*mement登録*/
MementRegister mement_register(void * base, size_t base_length, mement_method_t * method);
/*mement登録解除*/
void mement_unregister(MementRegister this);
/*mementの思い出し*/
void mement_remember(MementRegister this, void * broken_instance, int is_unregister_mement);
使い方:
- mement_registerで思い出登録
- mement_rememberで思い出す。is_unregister_mementが0以外ならその時点で思い出を忘れます。
- mement_rememberで忘れなかった思い出はmement_unregisterで忘れましょう。
コード
以下に置いてあります。
https://github.com/developer-kikikaikai/design_pattern_for_c/tree/master/mement
サンプルはいらないですよね?保持したデータを思い出すだけです。必要ならmement/testのコードを参考にしてください。
最後に
思い出を記録して思い出せるようにしよう。地味ながら優れた思想だと思います。
という私がmementのgit操作をミスり、コードをテスト込みで丸々削除したし。
大事だね、記録しておくのって